diff --git a/app/services/encryption/encryptors/attribute_encryptor.rb b/app/services/encryption/encryptors/attribute_encryptor.rb index 6b5915ecd6a..e42af01af9b 100644 --- a/app/services/encryption/encryptors/attribute_encryptor.rb +++ b/app/services/encryption/encryptors/attribute_encryptor.rb @@ -9,29 +9,23 @@ def initialize end def encrypt(plaintext) - unless Figaro.env.attribute_encryption_without_kms == 'true' - return deprecated_encryptor.encrypt(plaintext) - end - aes_encrypted_ciphertext = aes_cipher.encrypt(plaintext, current_key) encode(aes_encrypted_ciphertext) end def decrypt(ciphertext) - return deprecated_encryptor.decrypt(ciphertext) if legacy?(ciphertext) raise EncryptionError, 'ciphertext invalid' unless valid_base64_encoding?(ciphertext) decoded_ciphertext = decode(ciphertext) try_decrypt(decoded_ciphertext) end def stale? - deprecated_encryptor&.stale? || stale + @stale end private attr_reader :aes_cipher - attr_accessor :stale def try_decrypt(decoded_ciphertext) all_keys.each do |key| @@ -42,16 +36,12 @@ def try_decrypt(decoded_ciphertext) end def try_decrypt_with_key(decoded_ciphertext, key) - self.stale = key != current_key + @stale = key != current_key aes_cipher.decrypt(decoded_ciphertext, key) rescue EncryptionError nil end - def legacy?(ciphertext) - Encryption::KmsClient.looks_like_kms?(ciphertext) || ciphertext.index('.') - end - def current_key Figaro.env.attribute_encryption_key end @@ -63,10 +53,6 @@ def all_keys def old_keys JSON.parse(Figaro.env.attribute_encryption_key_queue) end - - def deprecated_encryptor - @_deprecated_encryptor ||= DeprecatedAttributeEncryptor.new - end end end end diff --git a/app/services/encryption/encryptors/deprecated_attribute_encryptor.rb b/app/services/encryption/encryptors/deprecated_attribute_encryptor.rb deleted file mode 100644 index 50c3fb2904e..00000000000 --- a/app/services/encryption/encryptors/deprecated_attribute_encryptor.rb +++ /dev/null @@ -1,68 +0,0 @@ -module Encryption - module Encryptors - class DeprecatedAttributeEncryptor - def encrypt(plaintext) - user_access_key = self.class.load_or_init_user_access_key( - key: current_key, cost: current_cost - ) - UserAccessKeyEncryptor.new(user_access_key).encrypt(plaintext) - end - - def decrypt(ciphertext) - encryption_keys_with_cost.each do |key_with_cost| - key = key_with_cost.fetch(:key) - cost = key_with_cost.fetch(:cost) - result = try_decrypt(ciphertext, key: key, cost: cost) - return result unless result.nil? - end - raise Encryption::EncryptionError, 'unable to decrypt attribute with any key' - end - - def stale? - stale - end - - def self.load_or_init_user_access_key(key:, cost:) - @_scypt_hashes_by_key ||= {} - scrypt_hash = @_scypt_hashes_by_key["#{key}:#{cost}"] - return UserAccessKey.new(scrypt_hash: scrypt_hash) if scrypt_hash.present? - uak = UserAccessKey.new( - password: key, salt: OpenSSL::Digest::SHA256.hexdigest(key), cost: cost - ) - @_scypt_hashes_by_key["#{key}:#{cost}"] = uak.as_scrypt_hash - uak - end - - private - - attr_accessor :stale - - def try_decrypt(ciphertext, key:, cost:) - user_access_key = self.class.load_or_init_user_access_key(key: key, cost: cost) - begin - result = UserAccessKeyEncryptor.new(user_access_key).decrypt(ciphertext) - self.stale = key != current_key - result - rescue Encryption::EncryptionError - nil - end - end - - def encryption_keys_with_cost - @encryption_keys_with_cost ||= [{ key: current_key, cost: current_cost }] + old_keys - end - - def current_key - Figaro.env.attribute_encryption_key - end - - def current_cost - Figaro.env.attribute_cost - end - - def old_keys - JSON.parse(Figaro.env.attribute_encryption_key_queue, symbolize_names: true) - end - end - end -end diff --git a/app/services/encryption/encryptors/deprecated_session_encryptor.rb b/app/services/encryption/encryptors/deprecated_session_encryptor.rb deleted file mode 100644 index 893de29cbc5..00000000000 --- a/app/services/encryption/encryptors/deprecated_session_encryptor.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Encryption - module Encryptors - class DeprecatedSessionEncryptor - def encrypt(plaintext) - user_access_key = self.class.load_or_init_user_access_key - UserAccessKeyEncryptor.new(user_access_key).encrypt(plaintext) - end - - def decrypt(ciphertext) - user_access_key = self.class.load_or_init_user_access_key - UserAccessKeyEncryptor.new(user_access_key).decrypt(ciphertext) - end - - def self.load_or_init_user_access_key - if @user_access_key_scrypt_hash.present? - return UserAccessKey.new(scrypt_hash: @user_access_key_scrypt_hash) - end - - key = Figaro.env.session_encryption_key - user_access_key = UserAccessKey.new( - password: key, salt: OpenSSL::Digest::SHA256.hexdigest(key), cost: '4000$8$4$' - ) - @user_access_key_scrypt_hash = user_access_key.as_scrypt_hash - user_access_key - end - end - end -end diff --git a/app/services/encryption/encryptors/session_encryptor.rb b/app/services/encryption/encryptors/session_encryptor.rb index 496871d8b3f..8ddb13b4bbf 100644 --- a/app/services/encryption/encryptors/session_encryptor.rb +++ b/app/services/encryption/encryptors/session_encryptor.rb @@ -10,18 +10,12 @@ def encrypt(plaintext) end def decrypt(ciphertext) - return deprecated_encryptor.decrypt(ciphertext) if legacy?(ciphertext) - aes_ciphertext = KmsClient.new.decrypt(decode(ciphertext)) aes_encryptor.decrypt(aes_ciphertext, aes_encryption_key) end private - def legacy?(ciphertext) - ciphertext.index('.') - end - def aes_encryptor AesEncryptor.new end @@ -29,10 +23,6 @@ def aes_encryptor def aes_encryption_key Figaro.env.session_encryption_key[0...32] end - - def deprecated_encryptor - DeprecatedSessionEncryptor.new - end end end end diff --git a/config/application.yml.example b/config/application.yml.example index 3feafffdc21..d3008d344c4 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -120,7 +120,6 @@ development: attribute_cost: '4000$8$4$' # SCrypt::Engine.calibrate(max_time: 0.5) attribute_encryption_key: '2086dfbd15f5b0c584f3664422a1d3409a0d2aa6084f65b6ba57d64d4257431c124158670c7655e45cabe64194f7f7b6c7970153c285bdb8287ec0c4f7553e25' attribute_encryption_key_queue: '[{ "key": "11111111111111111111111111111111", "cost": "4000$8$4$" }, { "key": "22222222222222222222222222222222", "cost": "4000$8$4$" }]' - attribute_encryption_without_kms: 'true' available_locales: 'en es fr' aws_kms_key_id: 'alias/login-dot-gov-development-keymaker' aws_region: 'us-east-1' @@ -256,7 +255,6 @@ production: attribute_cost: '4000$8$4$' # SCrypt::Engine.calibrate(max_time: 0.5) attribute_encryption_key: # generate via `rake secret` attribute_encryption_key_queue: # '[{ "key": "old-key-one", "cost": "4000$8$4$" }, { "key": "old-key-one", "cost": "4000$8$4$" }]' - attribute_encryption_without_kms: 'true' available_locales: 'en es fr' aws_kms_key_id: aws_region: @@ -377,7 +375,6 @@ test: attribute_cost: '800$8$1$' # SCrypt::Engine.calibrate(max_time: 0.01) attribute_encryption_key: '2086dfbd15f5b0c584f3664422a1d3409a0d2aa6084f65b6ba57d64d4257431c124158670c7655e45cabe64194f7f7b6c7970153c285bdb8287ec0c4f7553e25' attribute_encryption_key_queue: '[{ "key": "11111111111111111111111111111111", "cost": "4000$8$4$" }, { "key": "22222222222222222222222222222222", "cost": "4000$8$4$" }]' - attribute_encryption_without_kms: 'true' available_locales: 'en es fr' aws_kms_key_id: 'alias/login-dot-gov-test-keymaker' aws_region: 'us-east-1' diff --git a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb index 46a10900812..40d1239f44a 100644 --- a/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/backup_code_verification_controller_spec.rb @@ -58,7 +58,6 @@ it 'renders the show page' do post :create, params: payload expect(response).to render_template(:show) - puts flash[:error] expect(flash[:error]).to eq t('two_factor_authentication.invalid_backup_code') end end diff --git a/spec/services/encrypted_attribute_spec.rb b/spec/services/encrypted_attribute_spec.rb index c3e208640fa..6ffdb43ba45 100644 --- a/spec/services/encrypted_attribute_spec.rb +++ b/spec/services/encrypted_attribute_spec.rb @@ -48,25 +48,16 @@ describe '#stale?' do it 'returns true when email was encrypted with old key' do - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('false') encrypted_with_old_key = encrypted_email rotate_attribute_encryption_key expect(EncryptedAttribute.new(encrypted_with_old_key).stale?).to eq true end - it 'returns true with legacy encryption and old key now switched to encryption without kms' do - encrypted_with_old_key = encrypted_email - rotate_attribute_encryption_key - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('true') - - expect(EncryptedAttribute.new(encrypted_with_old_key).stale?).to eq true - end - it 'returns false when email was encrypted with current key' do - ee = EncryptedAttribute.new(encrypted_email) + encrypted_with_old_key = EncryptedAttribute.new(encrypted_email) - expect(ee.stale?).to eq false + expect(encrypted_with_old_key.stale?).to eq false end end end diff --git a/spec/services/encryption/encryptors/attribute_encryptor_spec.rb b/spec/services/encryption/encryptors/attribute_encryptor_spec.rb index e7c3ee19275..bb0f56c2a66 100644 --- a/spec/services/encryption/encryptors/attribute_encryptor_spec.rb +++ b/spec/services/encryption/encryptors/attribute_encryptor_spec.rb @@ -16,91 +16,22 @@ end describe '#encrypt' do - context 'with old kms based encryption' do - before do - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('false') - end - - it 'returns encrypted text' do - ciphertext = subject.encrypt(plaintext) - - expect(ciphertext).to_not eq(plaintext) - end - end - - context 'with new non kms based encrytpion' do - before do - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('true') - end - - it 'returns encrypted text' do - ciphertext = subject.encrypt(plaintext) + it 'returns encrypted text' do + ciphertext = subject.encrypt(plaintext) - expect(ciphertext).to_not eq(plaintext) - end + expect(ciphertext).to_not eq(plaintext) end end describe '#decrypt' do - context 'with old kms based encryption' do - let(:ciphertext) do - subject.encrypt(plaintext) - end - - before do - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('false') - # Memoize the ciphertext and purge the key pool so that encryption does not - # affect expected call counts - ciphertext - end - - context 'with a ciphertext made with the current key' do - it 'decrypts the ciphertext' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - end - - context 'after rotating keys' do - before do - rotate_attribute_encryption_key - end - - it 'tries to decrypt with successive keys until it is successful' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - end - - context 'it migrates legacy encrypted data after rotating keys' do - before do - rotate_attribute_encryption_key - end - - it 'tries to decrypt with successive keys until it is successful' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - end - - context 'with a ciphertext made with a key that does not exist' do - before do - rotate_attribute_encryption_key_with_invalid_queue - end - - it 'raises and encryption error' do - expect { subject.decrypt(ciphertext) }.to \ - raise_error(Encryption::EncryptionError, 'unable to decrypt attribute with any key') - end - end - end - context 'with new new non kms based encryption' do let(:ciphertext) do subject.encrypt(plaintext) end before do - # Memoize the ciphertext and purge the key pool so that encryption does not - # affect expected call counts - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('true') + # Memoize the ciphertext so that encryption does not affect expected + # call counts ciphertext end @@ -120,16 +51,6 @@ end end - context 'it migrates legacy encrypted data after rotating keys' do - before do - rotate_attribute_encryption_key - end - - it 'tries to decrypt with successive keys until it is successful' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - end - context 'with a ciphertext made with a key that does not exist' do before do rotate_attribute_encryption_key_with_invalid_queue @@ -151,19 +72,9 @@ expect(subject.stale?).to eq(false) end - it 'returns true if an old key was last used to decrypt something' do - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('false') - ciphertext = subject.encrypt(plaintext) - rotate_attribute_encryption_key - subject.decrypt(ciphertext) - - expect(subject.stale?).to eq(true) - end - it 'returns true if old key used to decrypt and we turn on new encryption' do ciphertext = subject.encrypt(plaintext) rotate_attribute_encryption_key - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('true') subject.decrypt(ciphertext) expect(subject.stale?).to eq(true) diff --git a/spec/services/encryption/encryptors/deprecated_attribute_encryptor_spec.rb b/spec/services/encryption/encryptors/deprecated_attribute_encryptor_spec.rb deleted file mode 100644 index 0b4eb8a507d..00000000000 --- a/spec/services/encryption/encryptors/deprecated_attribute_encryptor_spec.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'rails_helper' - -describe Encryption::Encryptors::DeprecatedAttributeEncryptor do - let(:plaintext) { 'some secret text' } - let(:current_key) { '1' * 32 } - let(:current_cost) { '400$8$1$' } - let(:retired_key) { '2' * 32 } - let(:retired_cost) { '2000$8$1$' } - - before do - described_class.instance_variable_set(:@_scypt_hashes_by_key, nil) - - allow(Figaro.env).to receive(:attribute_encryption_key).and_return(current_key) - allow(Figaro.env).to receive(:attribute_cost).and_return(current_cost) - allow(Figaro.env).to receive(:attribute_encryption_key_queue).and_return( - [{ key: retired_key, cost: retired_cost }].to_json - ) - end - - describe '#encrypt' do - it 'returns encrypted text' do - ciphertext = subject.encrypt(plaintext) - - expect(ciphertext).to_not eq(plaintext) - end - - it 'only computes an scrypt hash the first time a key is used' do - expect(SCrypt::Engine).to receive(:hash_secret).once.and_call_original - - subject.encrypt(plaintext) - - expect(SCrypt::Engine).to_not receive(:hash_secret) - - subject.encrypt(plaintext) - end - end - - describe '#decrypt' do - let(:ciphertext) do - result = subject.encrypt(plaintext) - described_class.instance_variable_set(:@_scypt_hashes_by_key, nil) - result - end - - before do - # Memoize the ciphertext and purge the key pool so that encryption does not - # affect expected call counts - ciphertext - end - - context 'with a ciphertext made with the current key' do - it 'decrypts the ciphertext' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - - it 'only computes an scrypt hash the first time a keys is used' do - expect(SCrypt::Engine).to receive(:hash_secret).once.and_call_original - - subject.decrypt(ciphertext) - - expect(SCrypt::Engine).to_not receive(:hash_secret) - - subject.decrypt(ciphertext) - end - end - - context 'after rotating keys' do - before do - rotate_attribute_encryption_key - end - - it 'tries to decrypt with successive keys until it is successful' do - expect(Encryption::UserAccessKey).to receive(:new).twice.and_call_original - - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - end - - context 'with a ciphertext made with a key that does not exist' do - before do - rotate_attribute_encryption_key_with_invalid_queue - end - - it 'raises and encryption error' do - expect { subject.decrypt(ciphertext) }.to raise_error( - Encryption::EncryptionError, 'unable to decrypt attribute with any key' - ) - end - end - end - - describe '#stale?' do - it 'returns false if the current key last was used to decrypt something' do - ciphertext = subject.encrypt(plaintext) - subject.decrypt(ciphertext) - - expect(subject.stale?).to eq(false) - end - - it 'returns true if an old key was last used to decrypt something' do - ciphertext = subject.encrypt(plaintext) - rotate_attribute_encryption_key - subject.decrypt(ciphertext) - - expect(subject.stale?).to eq(true) - end - end - - describe '.load_or_init_user_access_key' do - it 'does not return the same key object for the same salt and cost' do - expect(SCrypt::Engine).to receive(:hash_secret).once.and_call_original - - key1 = described_class.load_or_init_user_access_key(key: current_key, cost: current_cost) - key2 = described_class.load_or_init_user_access_key(key: current_key, cost: current_cost) - - expect(key1.as_scrypt_hash).to eq(key2.as_scrypt_hash) - expect(key1).to_not eq(key2) - end - end -end diff --git a/spec/services/encryption/encryptors/deprecated_session_encryptor.rb b/spec/services/encryption/encryptors/deprecated_session_encryptor.rb deleted file mode 100644 index ef88164af91..00000000000 --- a/spec/services/encryption/encryptors/deprecated_session_encryptor.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'rails_helper' - -describe Encryption::Encryptors::DeprecatedSessionEncryptor do - let(:plaintext) { '{ "foo": "bar" }' } - - before do - described_class.instance_variable_set(:@user_access_key_scrypt_hash, nil) - end - - describe '#encrypt' do - it 'returns encrypted text' do - ciphertext = subject.encrypt(plaintext) - - expect(ciphertext).to_not eq(plaintext) - end - - it 'only computes an scrypt hash on the first encryption' do - expect(SCrypt::Engine).to receive(:hash_secret).once.and_call_original - - subject.encrypt(plaintext) - - expect(SCrypt::Engine).to_not receive(:hash_secret) - - subject.encrypt(plaintext) - end - end - - describe '#decrypt' do - let(:ciphertext) do - result = subject.encrypt(plaintext) - described_class.instance_variable_set(:@user_access_key_scrypt_hash, nil) - result - end - - before do - # Memoize the ciphertext and purge memoized key so that encryption does not - # affect expected call counts - ciphertext - end - - it 'returns a decrypted ciphertext' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - - it 'only computes and scrypt hash on the first decryption' do - expect(SCrypt::Engine).to receive(:hash_secret).once.and_call_original - - subject.decrypt(ciphertext) - - expect(SCrypt::Engine).to_not receive(:hash_secret) - - subject.decrypt(ciphertext) - end - end - - describe '.load_or_init_user_access_key' do - it 'does not return the same key object for the same salt and cost' do - expect(SCrypt::Engine).to receive(:hash_secret).once.and_call_original - - key1 = described_class.load_or_init_user_access_key - key2 = described_class.load_or_init_user_access_key - - expect(key1.as_scrypt_hash).to eq(key2.as_scrypt_hash) - expect(key1).to_not eq(key2) - end - end - - it 'makes a roundtrip across multiple encryptors' do - encryptor1 = described_class.new - encryptor2 = described_class.new - - # Memoize user access key scrypt hash - encryptor1.decrypt(encryptor1.encrypt('asdf')) - encryptor2.decrypt(encryptor2.encrypt('1234')) - - encrypted_text = encryptor1.encrypt(plaintext) - expect(encryptor2.decrypt(encrypted_text)).to eq(plaintext) - end -end diff --git a/spec/services/encryption/encryptors/session_encryptor_spec.rb b/spec/services/encryption/encryptors/session_encryptor_spec.rb index 6d5cd79fd4e..379269ee587 100644 --- a/spec/services/encryption/encryptors/session_encryptor_spec.rb +++ b/spec/services/encryption/encryptors/session_encryptor_spec.rb @@ -25,20 +25,10 @@ end describe '#decrypt' do - context 'with a legacy ciphertext' do - let(:ciphertext) { Encryption::Encryptors::DeprecatedSessionEncryptor.new.encrypt(plaintext) } + let(:ciphertext) { Encryption::Encryptors::SessionEncryptor.new.encrypt(plaintext) } - it 'decrypts the ciphertext' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end - end - - context 'with a 2L-KMS ciphertext' do - let(:ciphertext) { Encryption::Encryptors::SessionEncryptor.new.encrypt(plaintext) } - - it 'decrypts the ciphertext' do - expect(subject.decrypt(ciphertext)).to eq(plaintext) - end + it 'decrypts the ciphertext' do + expect(subject.decrypt(ciphertext)).to eq(plaintext) end end end diff --git a/spec/services/pii/cacher_spec.rb b/spec/services/pii/cacher_spec.rb index ec31714a24c..21fee95e0ff 100644 --- a/spec/services/pii/cacher_spec.rb +++ b/spec/services/pii/cacher_spec.rb @@ -11,7 +11,6 @@ describe '#save' do before do - allow(Figaro.env).to receive(:attribute_encryption_without_kms).and_return('false') allow(FeatureManagement).to receive(:use_kms?).and_return(false) profile.save! end