summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorClaire McQuin <mcquin@users.noreply.github.com>2014-08-07 16:25:40 -0700
committerClaire McQuin <mcquin@users.noreply.github.com>2014-08-07 16:25:40 -0700
commit0c836a94b0e370ba2d69e67bde5af4aab569fe01 (patch)
treea20327485754b856f25d785de667063a5ff02d17 /spec
parent3e922c9536c362264694ee48a2094d758d81d19f (diff)
parente0575be762f17cad759aaa4cb1cb24e524304fa4 (diff)
downloadchef-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.rb3
-rw-r--r--spec/support/platform_helpers.rb12
-rw-r--r--spec/unit/encrypted_data_bag_item_spec.rb154
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