diff options
author | Claire McQuin <mcquin@users.noreply.github.com> | 2014-08-07 16:25:40 -0700 |
---|---|---|
committer | Claire McQuin <mcquin@users.noreply.github.com> | 2014-08-07 16:25:40 -0700 |
commit | 0c836a94b0e370ba2d69e67bde5af4aab569fe01 (patch) | |
tree | a20327485754b856f25d785de667063a5ff02d17 /spec | |
parent | 3e922c9536c362264694ee48a2094d758d81d19f (diff) | |
parent | e0575be762f17cad759aaa4cb1cb24e524304fa4 (diff) | |
download | chef-0c836a94b0e370ba2d69e67bde5af4aab569fe01.tar.gz |
Merge pull request #1591 from onddo/CHEF-5356-gcm-2
[CHEF-5356-gcm(2)] Encrypted data bags should use different HMAC key and include the IV in the HMAC
Diffstat (limited to 'spec')
-rw-r--r-- | spec/spec_helper.rb | 3 | ||||
-rw-r--r-- | spec/support/platform_helpers.rb | 12 | ||||
-rw-r--r-- | spec/unit/encrypted_data_bag_item_spec.rb | 154 |
3 files changed, 160 insertions, 9 deletions
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 09e7642d98..7c11957997 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -124,6 +124,9 @@ RSpec.configure do |config| config.filter_run_excluding :requires_root_or_running_windows => true unless (root? || windows?) config.filter_run_excluding :requires_unprivileged_user => true if root? config.filter_run_excluding :uses_diff => true unless has_diff? + config.filter_run_excluding :ruby_gte_20_and_openssl_gte_101 => true unless (ruby_gte_20? && openssl_gte_101?) + config.filter_run_excluding :openssl_lt_101 => true unless openssl_lt_101? + config.filter_run_excluding :ruby_lt_20 => true unless ruby_lt_20? running_platform_arch = `uname -m`.strip diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index ad7104d170..a7c616d7a7 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -7,6 +7,10 @@ def ruby_gte_20? RUBY_VERSION.to_f >= 2.0 end +def ruby_lt_20? + !ruby_gte_20? +end + def ruby_gte_19? RUBY_VERSION.to_f >= 1.9 end @@ -129,3 +133,11 @@ def root? return false if windows? Process.euid == 0 end + +def openssl_gte_101? + OpenSSL::OPENSSL_VERSION_NUMBER >= 10001000 +end + +def openssl_lt_101? + !openssl_gte_101? +end diff --git a/spec/unit/encrypted_data_bag_item_spec.rb b/spec/unit/encrypted_data_bag_item_spec.rb index 1e662a0b7c..24ceb452ef 100644 --- a/spec/unit/encrypted_data_bag_item_spec.rb +++ b/spec/unit/encrypted_data_bag_item_spec.rb @@ -23,7 +23,7 @@ module Version0Encryptor def self.encrypt_value(plaintext_data, key) data = plaintext_data.to_yaml - cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc") + cipher = OpenSSL::Cipher.new("aes-256-cbc") cipher.encrypt cipher.pkcs5_keyivgen(key) encrypted_bytes = cipher.update(data) @@ -39,14 +39,14 @@ describe Chef::EncryptedDataBagItem::Encryptor do let(:key) { "passwd" } it "encrypts to format version 1 by default" do - encryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor) + encryptor.should be_a_instance_of(Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor) end describe "generating a random IV" do it "generates a new IV for each encryption pass" do encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) - # No API in ruby OpenSSL to get the iv it used for the encryption back + # No API in ruby OpenSSL to get the iv is used for the encryption back # out. Instead we test if the encrypted data is the same. If it *is* the # same, we assume the IV was the same each time. encryptor.encrypted_data.should_not eq encryptor2.encrypted_data @@ -56,7 +56,7 @@ describe Chef::EncryptedDataBagItem::Encryptor do describe "when encrypting a non-hash non-array value" do let(:plaintext_data) { 5 } it "serializes the value in a de-serializable way" do - Chef::JSONCompat.from_json(subject.serialized_data)["json_wrapper"].should eq 5 + Chef::JSONCompat.from_json(encryptor.serialized_data)["json_wrapper"].should eq 5 end end @@ -78,10 +78,10 @@ describe Chef::EncryptedDataBagItem::Encryptor do end it "creates a version 2 encryptor" do - encryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor) + encryptor.should be_a_instance_of(Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor) end - it "generates an hmac based on ciphertext including iv" do + it "generates an hmac based on ciphertext with different iv" do encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) encryptor.hmac.should_not eq(encryptor2.hmac) end @@ -92,6 +92,74 @@ describe Chef::EncryptedDataBagItem::Encryptor do end end + describe "when using version 3 format" do + before do + Chef::Config[:data_bag_encrypt_version] = 3 + end + + context "on supported platforms", :ruby_gte_20_and_openssl_gte_101 do + + it "creates a version 3 encryptor" do + encryptor.should be_a_instance_of(Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor) + end + + it "generates different authentication tags" do + encryptor3 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) + encryptor.for_encrypted_item # required to generate the auth_tag + encryptor3.for_encrypted_item + encryptor.auth_tag.should_not eq(encryptor3.auth_tag) + end + + it "includes the auth_tag in the envelope" do + final_data = encryptor.for_encrypted_item + final_data["auth_tag"].should eq(Base64::encode64(encryptor.auth_tag)) + end + + it "throws an error if auth tag is read before encrypting the data" do + lambda { encryptor.auth_tag }.should raise_error(Chef::EncryptedDataBagItem::EncryptionFailure) + end + + end # context on supported platforms + + context "on unsupported platforms" do + let(:aead_algorithm) { Chef::EncryptedDataBagItem::AEAD_ALGORITHM } + + it "throws an error warning about the Ruby version if it has no GCM support" do + # Force OpenSSL with AEAD support + OpenSSL::Cipher.stub(:ciphers).and_return([ aead_algorithm ]) + # Ruby without AEAD support + OpenSSL::Cipher.should_receive(:method_defined?).with(:auth_data=).and_return(false) + lambda { encryptor }.should raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires Ruby/) + end + + it "throws an error warning about the OpenSSL version if it has no GCM support" do + # Force Ruby with AEAD support + OpenSSL::Cipher.stub(:method_defined?).with(:auth_data=).and_return(true) + # OpenSSL without AEAD support + OpenSSL::Cipher.should_receive(:ciphers).and_return([]) + lambda { encryptor }.should raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires an OpenSSL/) + end + + context "on platforms with old Ruby", :ruby_lt_20 do + + it "throws an error warning about the Ruby version" do + lambda { encryptor }.should raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires Ruby/) + end + + end # context on platforms with old Ruby + + context "on platforms with old OpenSSL", :openssl_lt_101 do + + it "throws an error warning about the OpenSSL version" do + lambda { encryptor }.should raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires an OpenSSL/) + end + + end # context on platforms with old OpenSSL + + end # context on unsupported platforms + + end # when using version 3 format + end describe Chef::EncryptedDataBagItem::Decryptor do @@ -101,17 +169,85 @@ describe Chef::EncryptedDataBagItem::Decryptor do let(:encryption_key) { "passwd" } let(:decryption_key) { encryption_key } + context "when decrypting a version 3 (JSON+aes-256-gcm+random iv+auth tag) encrypted value" do + + context "on supported platforms", :ruby_gte_20_and_openssl_gte_101 do + + let(:encrypted_value) do + Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.new(plaintext_data, encryption_key).for_encrypted_item + end + + let(:bogus_auth_tag) { "bogus_auth_tag" } + + it "decrypts the encrypted value" do + decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json) + end + + it "unwraps the encrypted data and returns it" do + decryptor.for_decrypted_item.should eq plaintext_data + end + + it "rejects the data if the authentication tag is wrong" do + encrypted_value["auth_tag"] = bogus_auth_tag + lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) + end + + it "rejects the data if the authentication tag is missing" do + encrypted_value.delete("auth_tag") + lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) + end + + end # context on supported platforms + + context "on unsupported platforms" do + let(:encrypted_value) do + { + "encrypted_data" => "", + "iv" => "", + "version" => 3, + "cipher" => "aes-256-cbc", + } + end + + context "on platforms with old Ruby", :ruby_lt_20 do + + it "throws an error warning about the Ruby version" do + lambda { decryptor }.should raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires Ruby/) + end + + end # context on platforms with old Ruby + + context "on platforms with old OpenSSL", :openssl_lt_101 do + + it "throws an error warning about the OpenSSL version" do + lambda { decryptor }.should raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires an OpenSSL/) + end + + end # context on unsupported platforms + + end # context on platforms with old OpenSSL + + end # context when decrypting a version 3 + context "when decrypting a version 2 (JSON+aes-256-cbc+hmac-sha256+random iv) encrypted value" do let(:encrypted_value) do Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.new(plaintext_data, encryption_key).for_encrypted_item end let(:bogus_hmac) do - digest = OpenSSL::Digest::Digest.new("sha256") + digest = OpenSSL::Digest.new("sha256") raw_hmac = OpenSSL::HMAC.digest(digest, "WRONG", encrypted_value["encrypted_data"]) Base64.encode64(raw_hmac) end + it "decrypts the encrypted value" do + decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json) + end + + it "unwraps the encrypted data and returns it" do + decryptor.for_decrypted_item.should eq plaintext_data + end + it "rejects the data if the hmac is wrong" do encrypted_value["hmac"] = bogus_hmac lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) @@ -131,7 +267,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do end it "selects the correct strategy for version 1" do - decryptor.should be_a_kind_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor + decryptor.should be_a_instance_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor end it "decrypts the encrypted value" do @@ -191,7 +327,7 @@ describe Chef::EncryptedDataBagItem::Decryptor do end it "selects the correct strategy for version 0" do - decryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Decryptor::Version0Decryptor) + decryptor.should be_a_instance_of(Chef::EncryptedDataBagItem::Decryptor::Version0Decryptor) end it "decrypts the encrypted value" do |