diff options
Diffstat (limited to 'lib/chef/encrypted_data_bag_item')
-rw-r--r-- | lib/chef/encrypted_data_bag_item/decryptor.rb | 47 | ||||
-rw-r--r-- | lib/chef/encrypted_data_bag_item/encryption_failure.rb | 22 | ||||
-rw-r--r-- | lib/chef/encrypted_data_bag_item/encryptor.rb | 76 |
3 files changed, 134 insertions, 11 deletions
diff --git a/lib/chef/encrypted_data_bag_item/decryptor.rb b/lib/chef/encrypted_data_bag_item/decryptor.rb index 69b8d62e3b..82d04c0d67 100644 --- a/lib/chef/encrypted_data_bag_item/decryptor.rb +++ b/lib/chef/encrypted_data_bag_item/decryptor.rb @@ -45,6 +45,8 @@ class Chef::EncryptedDataBagItem format_version = format_version_of(encrypted_value) assert_format_version_acceptable!(format_version) case format_version + when 3 + Version3Decryptor.new(encrypted_value, key) when 2 Version2Decryptor.new(encrypted_value, key) when 1 @@ -83,6 +85,11 @@ class Chef::EncryptedDataBagItem @key = key end + # Returns the used decryption algorithm + def algorithm + ALGORITHM + end + def for_decrypted_item YAML.load(decrypted_data) end @@ -102,7 +109,7 @@ class Chef::EncryptedDataBagItem def openssl_decryptor @openssl_decryptor ||= begin - d = OpenSSL::Cipher::Cipher.new(ALGORITHM) + d = OpenSSL::Cipher::Cipher.new(algorithm) d.decrypt d.pkcs5_keyivgen(key) d @@ -110,7 +117,7 @@ class Chef::EncryptedDataBagItem end end - class Version1Decryptor + class Version1Decryptor < Version0Decryptor attr_reader :encrypted_data attr_reader :key @@ -149,8 +156,9 @@ class Chef::EncryptedDataBagItem def openssl_decryptor @openssl_decryptor ||= begin assert_valid_cipher! - d = OpenSSL::Cipher::Cipher.new(ALGORITHM) + d = OpenSSL::Cipher::Cipher.new(algorithm) d.decrypt + # We must set key before iv: https://bugs.ruby-lang.org/issues/8221 d.key = Digest::SHA256.digest(key) d.iv = iv d @@ -159,11 +167,11 @@ class Chef::EncryptedDataBagItem def assert_valid_cipher! # In the future, chef may support configurable ciphers. For now, only - # aes-256-cbc is supported. + # aes-256-cbc and aes-256-gcm are supported. requested_cipher = @encrypted_data["cipher"] - unless requested_cipher == ALGORITHM + unless requested_cipher == algorithm raise UnsupportedCipher, - "Cipher '#{requested_cipher}' is not supported by this version of Chef. Available ciphers: ['#{ALGORITHM}']" + "Cipher '#{requested_cipher}' is not supported by this version of Chef. Available ciphers: ['#{ALGORITHM}', '#{AEAD_ALGORITHM}']" end end end @@ -197,5 +205,32 @@ class Chef::EncryptedDataBagItem valid == 0 end end + + class Version3Decryptor < Version1Decryptor + + # Returns the used decryption algorithm + def algorithm + AEAD_ALGORITHM + end + + def auth_tag + auth_tag_b64 = @encrypted_data["auth_tag"] + if auth_tag_b64.nil? + raise DecryptionFailure, "Error decrypting data bag value: invalid authentication tag. Most likely the data is corrupted" + end + Base64.decode64(auth_tag_b64) + end + + def openssl_decryptor + @openssl_decryptor ||= begin + d = super + d.auth_tag = auth_tag + d.auth_data = '' + d + end + end + + end + end end diff --git a/lib/chef/encrypted_data_bag_item/encryption_failure.rb b/lib/chef/encrypted_data_bag_item/encryption_failure.rb new file mode 100644 index 0000000000..380e9bc1f7 --- /dev/null +++ b/lib/chef/encrypted_data_bag_item/encryption_failure.rb @@ -0,0 +1,22 @@ +# +# Author:: Xabier de Zuazo (<xabier@onddo.com>) +# Copyright:: Copyright (c) 2014 Onddo Labs, SL. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef::EncryptedDataBagItem + class EncryptionFailure < StandardError + end +end diff --git a/lib/chef/encrypted_data_bag_item/encryptor.rb b/lib/chef/encrypted_data_bag_item/encryptor.rb index 9686e84b34..a3f3011c21 100644 --- a/lib/chef/encrypted_data_bag_item/encryptor.rb +++ b/lib/chef/encrypted_data_bag_item/encryptor.rb @@ -22,6 +22,7 @@ require 'openssl' require 'ffi_yajl' require 'chef/encrypted_data_bag_item' require 'chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format' +require 'chef/encrypted_data_bag_item/encryption_failure' class Chef::EncryptedDataBagItem @@ -40,9 +41,11 @@ class Chef::EncryptedDataBagItem Version1Encryptor.new(value, secret, iv) when 2 Version2Encryptor.new(value, secret, iv) + when 3 + Version3Encryptor.new(value, secret, iv) else raise UnsupportedEncryptedDataBagItemFormat, - "Invalid encrypted data bag format version `#{format_version}'. Supported versions are '1', '2'" + "Invalid encrypted data bag format version `#{format_version}'. Supported versions are '1', '2', '3'" end end @@ -65,6 +68,11 @@ class Chef::EncryptedDataBagItem @iv = iv && Base64.decode64(iv) end + # Returns the used encryption algorithm + def algorithm + ALGORITHM + end + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for # using as the value in an encrypted data bag item. def for_encrypted_item @@ -72,7 +80,7 @@ class Chef::EncryptedDataBagItem "encrypted_data" => encrypted_data, "iv" => Base64.encode64(iv), "version" => 1, - "cipher" => ALGORITHM + "cipher" => algorithm } end @@ -88,11 +96,12 @@ class Chef::EncryptedDataBagItem # it for the specified iv and encryption key. def openssl_encryptor @openssl_encryptor ||= begin - encryptor = OpenSSL::Cipher::Cipher.new(ALGORITHM) + encryptor = OpenSSL::Cipher::Cipher.new(algorithm) encryptor.encrypt + # We must set key before iv: https://bugs.ruby-lang.org/issues/8221 + encryptor.key = Digest::SHA256.digest(key) @iv ||= encryptor.random_iv encryptor.iv = @iv - encryptor.key = Digest::SHA256.digest(key) encryptor end end @@ -125,7 +134,7 @@ class Chef::EncryptedDataBagItem "hmac" => hmac, "iv" => Base64.encode64(iv), "version" => 2, - "cipher" => ALGORITHM + "cipher" => algorithm } end @@ -138,5 +147,62 @@ class Chef::EncryptedDataBagItem end end end + + class Version3Encryptor < Version1Encryptor + + def initialize(plaintext_data, key, iv=nil) + super + @auth_tag = nil + end + + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for + # using as the value in an encrypted data bag item. + def for_encrypted_item + { + "encrypted_data" => encrypted_data, + "iv" => Base64.encode64(iv), + "auth_tag" => Base64.encode64(auth_tag), + "version" => 3, + "cipher" => algorithm + } + end + + # Returns the used encryption algorithm + def algorithm + AEAD_ALGORITHM + end + + # Returns a wrapped and encrypted version of +plaintext_data+ suitable for + # Returns the auth_tag. + def auth_tag + # Generated auth_tag comes from OpenSSL::Cipher::Cipher#auth_tag + # This must be generated after the data is encrypted + if @auth_tag.nil? + raise EncryptionFailure, "Internal Error: GCM authentication tag read before encryption" + end + @auth_tag + end + + # Generates (and memoizes) an OpenSSL::Cipher::Cipher object and configures + # it for the specified iv and encryption key using AEAD + def openssl_encryptor + @openssl_encryptor ||= begin + encryptor = super + encryptor.auth_data = '' + encryptor + end + end + + # Encrypts, Base64 encodes +serialized_data+ and gets the authentication tag + def encrypted_data + @encrypted_data ||= begin + enc_data_b64 = super + @auth_tag = openssl_encryptor.auth_tag + enc_data_b64 + end + end + + end + end end |