require_relative '../spec_helper'
require 'base64'
require 'openssl'

module Aws
  module S3
    module EncryptionV2

      describe Client do
        # Captures the data (metadata and body) put to an s3 object
        def stub_put(s3_client)
          data = {}
          s3_client.stub_responses(:put_object, lambda { |context|
            data[:metadata] = context.params[:metadata]
            data[:enc_body] = context.params[:body].read
            {}
          })
          data
        end

        # Given data from stub_put, stub a get for the same object
        # during get get_object is called twice, once to get the full body and
        # again with a range to get just the auth_tag
        def stub_get(s3_client, data, stub_auth_tag)
          resp_headers = Hash[*data[:metadata].map { |k, v| ["x-amz-meta-#{k.to_s}", v] }.flatten(1)]
          resp_headers['content-length'] = data[:enc_body].length
          if stub_auth_tag
            auth_tag = data[:enc_body].unpack('C*')[-16, 16].pack('C*')
          else
            auth_tag = nil
          end
          s3_client.stub_responses(
            :get_object,
            {status_code: 200, body: data[:enc_body], headers: resp_headers},
            {body: auth_tag}
          )
        end

        def stub_decrypt(kms_client, opts)
          kms_client.stub_responses(
            :decrypt, lambda do |context|
            if opts[:any_kms_key]
              expect(context.params['key_id']).to be_nil
            else
              if opts[:raise] && context.params['key_id'] != opts[:response][:key_id]
                raise Aws::KMS::Errors::IncorrectKeyException.new(context, '')
              else
                expect(context.params[:key_id]).to eq(opts[:response][:key_id])
              end
            end
            opts[:response]
            end
          )
        end

        let(:plaintext) { 'super secret plain text' }
        let(:test_bucket) { 'test_bucket' }
        let(:test_object) { 'test_object' }

        let(:s3_client) { S3::Client.new(stub_responses: true) }

        context 'when using a symmetric (AES) key' do
          let(:key) do
            OpenSSL::Cipher.new('aes-256-gcm').random_key
          end

          let(:options) do
            {
              client: s3_client,
              encryption_key: key,
              key_wrap_schema: :aes_gcm,
              content_encryption_schema: :aes_gcm_no_padding,
              security_profile: :v2
            }
          end

          it 'can encrypt and decrypt plain text' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            expect(data[:metadata]['x-amz-cek-alg']).to eq('AES/GCM/NoPadding')
            expect(data[:metadata]['x-amz-wrap-alg']).to eq('AES/GCM')

            stub_get(s3_client, data, true)
            decrypted = client.get_object(bucket: test_bucket, key: test_object).body.read
            expect(decrypted).to eq(plaintext)
          end

          it 'supports #get_object with a block and raises a warning the first time' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)

            stub_get(s3_client, data, true)
            expect_any_instance_of(Aws::S3::EncryptionV2::DecryptHandler).to receive(:warn)
            decrypted = ''
            client.get_object(bucket: test_bucket, key: test_object) do |chunk|
              decrypted += chunk
            end
            expect(decrypted).to eq(plaintext)

            # it does not warn a second time
            expect_any_instance_of(Aws::S3::EncryptionV2::DecryptHandler).not_to receive(:warn)
            stub_get(s3_client, data, true)
            decrypted = ''
            client.get_object(bucket: test_bucket, key: test_object) do |chunk|
              decrypted += chunk
            end
          end

          it 'can can use envelope_location: instruction_file' do
            client = Aws::S3::EncryptionV2::Client.new(
              options.merge(envelope_location: :instruction_file)
            )
            data = {}
            s3_client.stub_responses(:put_object, lambda { |context|
              if context.params[:key].include? '.instruction'
                data[:metadata] = JSON.load(context.params[:body])
              else
                data[:enc_body] = context.params[:body].read
              end
              {}
            })
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)

            resp_headers = { 'content-length' => data[:enc_body].length }
            auth_tag = data[:enc_body].unpack('C*')[-16, 16].pack('C*')

            s3_client.stub_responses(
              :get_object,
              {status_code: 200, body: data[:enc_body], headers: resp_headers},
              {body: Json.dump(data[:metadata])},
              {body: auth_tag}
            )
            decrypted = client.get_object(bucket: test_bucket, key: test_object).body.read
            expect(decrypted).to eq(plaintext)
          end

          context 'security_profile: v2' do
            it 'raises a DecryptionError when reading a legacy object' do
              client_v1 = Aws::S3::Encryption::Client.new(encryption_key: key, client: s3_client)
              client_v2 = Aws::S3::EncryptionV2::Client.new(options)

              data = stub_put(s3_client)
              client_v1.put_object(bucket: test_bucket, key: test_object, body: plaintext)

              stub_get(s3_client, data, false)
              expect do
                client_v2.get_object(bucket: test_bucket, key: test_object)
              end.to raise_error(Errors::LegacyDecryptionError)
            end
          end

          context 'security_profile: v2_and_legacy' do
            let(:legacy_options) { options.merge(security_profile: :v2_and_legacy) }

            it 'can decrypt an object encrypted using legacy algorithm' do
              client_v1 = Aws::S3::Encryption::Client.new(encryption_key: key, client: s3_client)

              expect_any_instance_of(Aws::S3::EncryptionV2::Client).to receive(:warn)
              client_v2 = Aws::S3::EncryptionV2::Client.new(legacy_options)

              data = stub_put(s3_client)
              client_v1.put_object(bucket: test_bucket, key: test_object, body: plaintext)

              stub_get(s3_client, data, false)
              decrypted = client_v2.get_object(bucket: test_bucket, key: test_object).body.read
              expect(decrypted).to eq(plaintext)
            end
          end

          it 'decrypts the object with response target under retry' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            s3_client.handlers.add(Aws::Plugins::RetryErrors::LegacyHandler, step: :sign, priority: 99)

            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)

            resp_headers = Hash[*data[:metadata].map { |k, v| ["x-amz-meta-#{k.to_s}", v] }.flatten(1)]
            resp_headers['content-length'] = data[:enc_body].length
            auth_tag = data[:enc_body].unpack('C*')[-16, 16].pack('C*')

            s3_client.stub_responses(
              :get_object,
              Seahorse::Client::NetworkingError.new(RuntimeError.new),
              {status_code: 200, body: data[:enc_body], headers: resp_headers},
              {body: auth_tag}
            )
            decrypted = StringIO.new
            client.get_object(bucket: test_bucket, key: test_object, response_target: decrypted)
            expect(decrypted.read).to eq(plaintext)
          end

          # Error cases
          it 'raises a DecryptionError when the envelope is missing' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            stub_get(s3_client, {metadata: {}, enc_body: 'encrypted'}, false)
            expect do
              client.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(Errors::DecryptionError,
                                   /unable to locate encryption envelope/)
          end

          it 'raises a DecryptionError when given an unsupported cek algorithm' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            data[:metadata]['x-amz-cek-alg'] = 'BAD/ALG'

            stub_get(s3_client, data, true)
            expect do
              client.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(Errors::DecryptionError,
                                   /unsupported content encrypting key/)
          end

          it 'raises a DecryptionError when given an unsupported wrap algorithm' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            data[:metadata]['x-amz-wrap-alg'] = 'BAD/ALG'

            stub_get(s3_client, data, true)
            expect do
              client.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(Errors::DecryptionError,
                                   /unsupported key wrapping algorithm/)
          end

          it 'raises a DecryptionError when the envelope is missing fields' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            data[:metadata].delete('x-amz-iv')

            stub_get(s3_client, data, true)
            expect do
              client.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(Errors::DecryptionError,
                                   /incomplete v2 encryption envelope/)
          end

          it 'raises an CipherError when a bit in the encrypted content modified' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            data[:enc_body][0] = [(data[:enc_body].unpack('C1')[0]) ^ 1].pack('C1')

            stub_get(s3_client, data, true)
            expect do
              client.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(OpenSSL::Cipher::CipherError)
          end

          it 'raises an ArgumentError when the client has an RSA key' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)

            client_rsa = Aws::S3::EncryptionV2::Client.new(
              options.merge(encryption_key: OpenSSL::PKey::RSA.new(1024), key_wrap_schema: :rsa_oaep_sha1)
            )
            stub_get(s3_client, data, true)
            expect do
              client_rsa.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(ArgumentError)
          end

          it 'raises an ArgumentError when the client has a KMS key' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)

            client_kms = Aws::S3::EncryptionV2::Client.new(
              kms_key_id: 'kms_key_id', client: s3_client,
              key_wrap_schema: :kms_context,
              content_encryption_schema: :aes_gcm_no_padding,
              security_profile: :v2,
              kms_client: KMS::Client.new(stub_responses: true)
            )
            stub_get(s3_client, data, true)
            expect do
              client_kms.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(ArgumentError)
          end

          it 'raises an ArgumentError when given an invalid key' do
            expect do
              Aws::S3::EncryptionV2::Client.new(options.merge(
                encryption_key: 'too-short'))
            end.to raise_exception(ArgumentError, /invalid key, symmetric key/)
          end

          it 'raises an ArgumentError when given kms_encryption_context' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            expect do
              client.put_object(
                bucket: test_bucket, key: test_object, body: plaintext,
                kms_encryption_context: {context: 'test'}
              )
            end.to raise_error(ArgumentError, /kms_encryption_context/)
          end
        end

        context 'when using an asymmetric (RSA) key' do
          let(:key) do
            OpenSSL::PKey::RSA.new(1024)
          end

          let(:options) do
            {
              client: s3_client,
              encryption_key: key,
              key_wrap_schema: :rsa_oaep_sha1,
              content_encryption_schema: :aes_gcm_no_padding,
              security_profile: :v2
            }
          end

          it 'can encrypt and decrypt plain text' do
            client = Aws::S3::EncryptionV2::Client.new(options)

            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            expect(data[:metadata]).to include('x-amz-key-v2')
            expect(data[:metadata]['x-amz-wrap-alg']).to eq('RSA-OAEP-SHA1')
            expect(data[:metadata]['x-amz-cek-alg']).to eq('AES/GCM/NoPadding')

            stub_get(s3_client, data, true)
            decrypted = client.get_object(bucket: test_bucket, key: test_object).body.read
            expect(decrypted).to eq(plaintext)
          end

          context 'security_profile: v2' do
            it 'raises a DecryptionError when reading a legacy object' do
              client_v1 = Aws::S3::Encryption::Client.new(encryption_key: key, client: s3_client)
              client_v2 = Aws::S3::EncryptionV2::Client.new(options)

              data = stub_put(s3_client)
              client_v1.put_object(bucket: test_bucket, key: test_object, body: plaintext)

              stub_get(s3_client, data, false)
              expect do
                client_v2.get_object(bucket: test_bucket, key: test_object)
              end.to raise_error(Errors::LegacyDecryptionError)
            end
          end

          context 'security_profile: v2_and_legacy' do
            let(:legacy_options) { options.merge(security_profile: :v2_and_legacy) }

            it 'can decrypt an object encrypted using legacy algorithm' do
              client_v1 = Aws::S3::Encryption::Client.new(encryption_key: key, client: s3_client)

              expect_any_instance_of(Aws::S3::EncryptionV2::Client).to receive(:warn)
              client_v2 = Aws::S3::EncryptionV2::Client.new(legacy_options)

              data = stub_put(s3_client)
              client_v1.put_object(bucket: test_bucket, key: test_object, body: plaintext)

              stub_get(s3_client, data, false)
              decrypted = client_v2.get_object(bucket: test_bucket, key: test_object).body.read
              expect(decrypted).to eq(plaintext)
            end
          end

          it 'raises an ArgumentError when the client has an AES key' do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)

            client_aes = Aws::S3::EncryptionV2::Client.new(
              options.merge(
                encryption_key: OpenSSL::Cipher.new('aes-256-gcm').random_key,
                key_wrap_schema: :aes_gcm
              )
            )
            stub_get(s3_client, data, true)
            expect do
              client_aes.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(ArgumentError)
          end
        end

        context 'when using a KMS Key' do
          let(:kms_client) { KMS::Client.new(stub_responses: true) }
          let(:kms_key_id) { 'kms_key_id' }
          let(:kms_ciphertext_blob) do
            Base64.decode64("AQIDAHiWj6qDEnwihp7W7g6VZb1xqsat5jdSUdEaGhgZepHdLAGASCQI7LZz\nz7GzCpm6y4sHAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEH\nATAeBglghkgBZQMEAS4wEQQMJMJe6d8DkRTWwlvtAgEQgDtBCwiibCTS8pb7\n6BYKklVjy+CmO9q3r6y4u/9jJ8lk9eg5GwiskmcBtPMcWogMzx/vh+/65Cjb\nsQBpLQ==\n")
          end

          let(:kms_plaintext) do
            Base64.decode64("5V7JWe+UDRhv66TaDg+tP6JONf/GkTdXk6Jq61weM+w=\n")
          end

          let(:options) do
            {
              client: s3_client,
              kms_key_id: kms_key_id,
              key_wrap_schema: :kms_context,
              content_encryption_schema: :aes_gcm_no_padding,
              security_profile: :v2,
              kms_client: kms_client
            }
          end

          it 'can encrypt and decrypt plain text' do
            client = Aws::S3::EncryptionV2::Client.new(options)

            data = stub_put(s3_client)

            kms_client.stub_responses(
              :generate_data_key,
              {
                key_id: kms_key_id,
                ciphertext_blob: kms_ciphertext_blob,
                plaintext: kms_plaintext
              }
            )
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            expect(data[:metadata]['x-amz-cek-alg']).to eq('AES/GCM/NoPadding')
            expect(data[:metadata]['x-amz-wrap-alg']).to eq('kms+context')

            stub_get(s3_client, data, true)

            stub_decrypt(kms_client, any_kms_key: false, response:
              {
                key_id: kms_key_id,
                plaintext: kms_plaintext,
                encryption_algorithm: "SYMMETRIC_DEFAULT"
              })

            decrypted = client.get_object(bucket: test_bucket, key: test_object).body.read
            expect(decrypted).to eq(plaintext)
          end

          it 'can encrypt and decrypt non-current versions' do
            client = Aws::S3::EncryptionV2::Client.new(options)

            data = stub_put(s3_client)

            kms_client.stub_responses(
              :generate_data_key,
              {
                key_id: kms_key_id,
                ciphertext_blob: kms_ciphertext_blob,
                plaintext: kms_plaintext
              }
            )
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            expect(data[:metadata]['x-amz-cek-alg']).to eq('AES/GCM/NoPadding')
            expect(data[:metadata]['x-amz-wrap-alg']).to eq('kms+context')

            stub_get(s3_client, data, true)

            stub_decrypt(kms_client, any_kms_key: false, response:
              {
                key_id: kms_key_id,
                plaintext: kms_plaintext,
                encryption_algorithm: "SYMMETRIC_DEFAULT"
              })

            expect(s3_client).to receive(:get_object)
                                   .with(hash_including(version_id: 'version_id'))
                                   .and_call_original

            decrypted = client.get_object(bucket: test_bucket, key: test_object,
                                          version_id: 'version_id').body.read
            expect(decrypted).to eq(plaintext)
          end

          context 'security_profile: v2' do
            it 'raises a DecryptionError when reading a legacy object' do
              client_v1 = Aws::S3::Encryption::Client.new(
                kms_key_id: kms_key_id, client: s3_client, kms_client: kms_client
              )
              client_v2 = Aws::S3::EncryptionV2::Client.new(options)

              data = stub_put(s3_client)
              kms_client.stub_responses(
                :generate_data_key,
                {
                  key_id: kms_key_id,
                  ciphertext_blob: kms_ciphertext_blob,
                  plaintext: kms_plaintext
                }
              )
              client_v1.put_object(bucket: test_bucket, key: test_object, body: plaintext)

              stub_get(s3_client, data, true)
              stub_decrypt(kms_client, any_kms_key: false, response:
                {
                  key_id: kms_key_id,
                  plaintext: kms_plaintext,
                  encryption_algorithm: "SYMMETRIC_DEFAULT"
                })
              expect do
                client_v2.get_object(bucket: test_bucket, key: test_object)
              end.to raise_error(Errors::LegacyDecryptionError)
            end
          end

          context 'security_profile: v2_and_legacy' do
            let(:legacy_options) { options.merge(security_profile: :v2_and_legacy) }

            it 'can decrypt an object encrypted using legacy algorithm' do
              client_v1 = Aws::S3::Encryption::Client.new(
                kms_key_id: kms_key_id, client: s3_client, kms_client: kms_client
              )

              expect_any_instance_of(Aws::S3::EncryptionV2::Client).to receive(:warn)
              client_v2 = Aws::S3::EncryptionV2::Client.new(legacy_options)

              data = stub_put(s3_client)
              kms_client.stub_responses(
                :generate_data_key,
                {
                  key_id: kms_key_id,
                  ciphertext_blob: kms_ciphertext_blob,
                  plaintext: kms_plaintext
                }
              )
              client_v1.put_object(bucket: test_bucket, key: test_object, body: plaintext)

              stub_get(s3_client, data, true)
              stub_decrypt(kms_client, any_kms_key: false, response:
                {
                  key_id: kms_key_id,
                  plaintext: kms_plaintext,
                  encryption_algorithm: "SYMMETRIC_DEFAULT"
                })
              decrypted = client_v2.get_object(bucket: test_bucket, key: test_object).body.read
              expect(decrypted).to eq(plaintext)
            end
          end

          it 'raises an ArgumentError when the client is configured with an AES key' do
            client = Aws::S3::EncryptionV2::Client.new(options)

            data = stub_put(s3_client)
            kms_client.stub_responses(
              :generate_data_key,
              {
                key_id: kms_key_id,
                ciphertext_blob: kms_ciphertext_blob,
                plaintext: kms_plaintext
              }
            )
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)

            client_aes = Aws::S3::EncryptionV2::Client.new(
              encryption_key: OpenSSL::Cipher.new('aes-256-gcm').random_key,
              key_wrap_schema: :aes_gcm,
              content_encryption_schema: :aes_gcm_no_padding,
              security_profile: :v2,
              client: s3_client
            )
            stub_get(s3_client, data, true)
            expect do
              client_aes.get_object(bucket: test_bucket, key: test_object)
            end.to raise_exception(ArgumentError)
          end

          it 'raises an IncorrectKeyException when given the wrong key', rbs_test: :skip do
            client = Aws::S3::EncryptionV2::Client.new(options)
            data = stub_put(s3_client)

            kms_client.stub_responses(
              :generate_data_key,
              {
                key_id: kms_key_id,
                ciphertext_blob: kms_ciphertext_blob,
                plaintext: kms_plaintext
              }
            )
            client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
            expect(data[:metadata]['x-amz-cek-alg']).to eq('AES/GCM/NoPadding')
            expect(data[:metadata]['x-amz-wrap-alg']).to eq('kms+context')

            client_wrong_key = Aws::S3::EncryptionV2::Client.new(options.merge(
              kms_key_id: 'wrong-key'
            ))

            stub_get(s3_client, data, true)
            stub_decrypt(kms_client, any_kms_key: false, raise: true, response:
              {
                key_id: kms_key_id,
                plaintext: kms_plaintext,
                encryption_algorithm: "SYMMETRIC_DEFAULT"
              })

            expect do
              client_wrong_key.get_object(bucket: test_bucket, key: test_object)
            end.to raise_error(Aws::KMS::Errors::IncorrectKeyException)
          end

          context 'kms_allow_decrypt_with_any_cmk' do
            it 'can decrypt with kms_key_id = kms_allow_decrypt_with_any_cmk' do
              client = Aws::S3::EncryptionV2::Client.new(options)
              data = stub_put(s3_client)

              kms_client.stub_responses(
                :generate_data_key,
                {
                  key_id: kms_key_id,
                  ciphertext_blob: kms_ciphertext_blob,
                  plaintext: kms_plaintext
                }
              )
              client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
              expect(data[:metadata]['x-amz-cek-alg']).to eq('AES/GCM/NoPadding')
              expect(data[:metadata]['x-amz-wrap-alg']).to eq('kms+context')

              client_any_cmk = Aws::S3::EncryptionV2::Client.new(options.merge(
                kms_key_id: :kms_allow_decrypt_with_any_cmk
              ))

              stub_get(s3_client, data, true)
              stub_decrypt(kms_client, any_kms_key: true, response:
                {
                  key_id: 'wrong-key',
                  plaintext: kms_plaintext,
                  encryption_algorithm: "SYMMETRIC_DEFAULT"
                })

              decrypted = client_any_cmk.get_object(bucket: test_bucket, key: test_object).body.read
              expect(decrypted).to eq(plaintext)
            end

            it 'can decrypt when given a different kms key with get_object override' do
              client = Aws::S3::EncryptionV2::Client.new(options)
              data = stub_put(s3_client)

              kms_client.stub_responses(
                :generate_data_key,
                {
                  key_id: kms_key_id,
                  ciphertext_blob: kms_ciphertext_blob,
                  plaintext: kms_plaintext
                }
              )
              client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
              expect(data[:metadata]['x-amz-cek-alg']).to eq('AES/GCM/NoPadding')
              expect(data[:metadata]['x-amz-wrap-alg']).to eq('kms+context')

              client_wrong_key = Aws::S3::EncryptionV2::Client.new(options.merge(
                kms_key_id: 'wrong-key'
              ))

              stub_get(s3_client, data, true)
              stub_decrypt(kms_client, any_kms_key: true, response:
                {
                  key_id: kms_key_id,
                  plaintext: kms_plaintext,
                  encryption_algorithm: "SYMMETRIC_DEFAULT"
                })

              decrypted = client_wrong_key.get_object(
                bucket: test_bucket, key: test_object,
                kms_allow_decrypt_with_any_cmk: true
              ).body.read
              expect(decrypted).to eq(plaintext)
            end

            it 'raises an ArgumentError when encrypting with kms_key_id = kms_allow_decrypt_with_any_cmk' do
              client = Aws::S3::EncryptionV2::Client.new(options.merge(
                kms_key_id: :kms_allow_decrypt_with_any_cmk
              ))

              expect do
                client.put_object(bucket: test_bucket, key: test_object, body: plaintext)
              end.to raise_error(ArgumentError, /kms_allow_decrypt_with_any_cmk/)
            end

            it 'raises an ArgumentError when aws:x-amz-cek-alg is set in the user provided kms_encryption_context' do
              client = Aws::S3::EncryptionV2::Client.new(options)

              expect do
                client.put_object(
                  bucket: test_bucket, key: test_object, body: plaintext,
                  kms_encryption_context: {'aws:x-amz-cek-alg' => 'error'})
              end.to raise_error(ArgumentError,
                                 /Conflict in reserved KMS Encryption Context/)
            end
          end
        end
      end
    end
  end
end
