aws-sdk-s3 1.207.0 → 1.208.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/client.rb +1 -1
- data/lib/aws-sdk-s3/customizations.rb +1 -0
- data/lib/aws-sdk-s3/encryption/client.rb +2 -2
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +98 -23
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +8 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
- data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
- data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
- data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
- data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
- data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
- data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
- data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
- data/lib/aws-sdk-s3.rb +1 -1
- metadata +17 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module S3
|
|
5
|
+
module EncryptionV3
|
|
6
|
+
module Errors
|
|
7
|
+
# Generic DecryptionError
|
|
8
|
+
class DecryptionError < RuntimeError; end
|
|
9
|
+
|
|
10
|
+
class EncryptionError < RuntimeError; end
|
|
11
|
+
|
|
12
|
+
# Raised when attempting to decrypt a legacy (V1) encrypted object
|
|
13
|
+
# when using a security_profile that does not support it.
|
|
14
|
+
class NonCommittingDecryptionError < DecryptionError
|
|
15
|
+
def initialize(*_args)
|
|
16
|
+
msg = 'The requested object is ' \
|
|
17
|
+
'was not encrypted with a committing algorithm ' \
|
|
18
|
+
'and decryption is not supported under :require_encrypt_require_decrypt commitment policy. ' \
|
|
19
|
+
'Change your commitment policy to :forbid_encrypt_allow_decrypt or :require_encrypt_allow_decrypt'
|
|
20
|
+
super(msg)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Raised when attempting to decrypt a legacy (V1) encrypted object
|
|
25
|
+
# when using a security_profile that does not support it.
|
|
26
|
+
class LegacyDecryptionError < DecryptionError
|
|
27
|
+
def initialize(*_args)
|
|
28
|
+
msg = 'The requested object is ' \
|
|
29
|
+
'encrypted with V1 encryption schemas that have been disabled ' \
|
|
30
|
+
'by client configuration security_profile = :v2. Retry with ' \
|
|
31
|
+
':v2_and_legacy or re-encrypt the object.'
|
|
32
|
+
super(msg)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class CEKAlgMismatchError < DecryptionError
|
|
37
|
+
def initialize(*_args)
|
|
38
|
+
msg = 'The content encryption algorithm used at encryption time ' \
|
|
39
|
+
'does not match the algorithm stored for decryption time. ' \
|
|
40
|
+
'The object may be altered or corrupted.'
|
|
41
|
+
super(msg)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module S3
|
|
5
|
+
module EncryptionV3
|
|
6
|
+
# @api private
|
|
7
|
+
class IOAuthDecrypter
|
|
8
|
+
# @option options [required, IO#write] :io
|
|
9
|
+
# An IO-like object that responds to {#write}.
|
|
10
|
+
# @option options [required, Integer] :encrypted_content_length
|
|
11
|
+
# The number of bytes to decrypt from the `:io` object.
|
|
12
|
+
# This should be the total size of `:io` minus the length of
|
|
13
|
+
# the cipher auth tag.
|
|
14
|
+
# @option options [required, OpenSSL::Cipher] :cipher An initialized
|
|
15
|
+
# cipher that can be used to decrypt the bytes as they are
|
|
16
|
+
# written to the `:io` object.
|
|
17
|
+
def initialize(options = {})
|
|
18
|
+
@decrypter = IODecrypter.new(options[:cipher], options[:io])
|
|
19
|
+
@max_bytes = options[:encrypted_content_length]
|
|
20
|
+
@bytes_written = 0
|
|
21
|
+
@cipher = options[:cipher]
|
|
22
|
+
@auth_tag = String.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write(chunk)
|
|
26
|
+
chunk = truncate_chunk(chunk)
|
|
27
|
+
return unless chunk.bytesize.positive?
|
|
28
|
+
|
|
29
|
+
@bytes_written += chunk.bytesize
|
|
30
|
+
@decrypter.write(chunk)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def finalize
|
|
34
|
+
@cipher.auth_tag = @auth_tag
|
|
35
|
+
@decrypter.finalize
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def io
|
|
39
|
+
@decrypter.io
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def truncate_chunk(chunk)
|
|
45
|
+
if chunk.bytesize + @bytes_written <= @max_bytes
|
|
46
|
+
chunk
|
|
47
|
+
elsif @bytes_written < @max_bytes
|
|
48
|
+
@auth_tag << chunk[@max_bytes - @bytes_written..-1]
|
|
49
|
+
chunk[0..(@max_bytes - @bytes_written - 1)]
|
|
50
|
+
else
|
|
51
|
+
@auth_tag << chunk
|
|
52
|
+
# If the tag was sent over after the full body has been read,
|
|
53
|
+
# we don't want to accidentally append it.
|
|
54
|
+
''
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module S3
|
|
5
|
+
module EncryptionV3
|
|
6
|
+
# @api private
|
|
7
|
+
class IODecrypter
|
|
8
|
+
# @param [OpenSSL::Cipher] cipher
|
|
9
|
+
# @param [IO#write] io An IO-like object that responds to `#write`.
|
|
10
|
+
def initialize(cipher, io)
|
|
11
|
+
@cipher = cipher
|
|
12
|
+
# Ensure that IO is reset between retries
|
|
13
|
+
@io = io.tap { |io| io.truncate(0) if io.respond_to?(:truncate) }
|
|
14
|
+
@cipher_buffer = String.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [#write]
|
|
18
|
+
attr_reader :io
|
|
19
|
+
|
|
20
|
+
def write(chunk)
|
|
21
|
+
# decrypt and write
|
|
22
|
+
if @cipher.method(:update).arity == 1
|
|
23
|
+
@io.write(@cipher.update(chunk))
|
|
24
|
+
else
|
|
25
|
+
@io.write(@cipher.update(chunk, @cipher_buffer))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def finalize
|
|
30
|
+
@io.write(@cipher.final)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
module Aws
|
|
7
|
+
module S3
|
|
8
|
+
module EncryptionV3
|
|
9
|
+
# Provides an IO wrapper encrypting a stream of data.
|
|
10
|
+
# @api private
|
|
11
|
+
class IOEncrypter
|
|
12
|
+
# @api private
|
|
13
|
+
ONE_MEGABYTE = 1024 * 1024
|
|
14
|
+
|
|
15
|
+
def initialize(cipher, io)
|
|
16
|
+
@encrypted = if io.size <= ONE_MEGABYTE
|
|
17
|
+
encrypt_to_stringio(cipher, io.read)
|
|
18
|
+
else
|
|
19
|
+
encrypt_to_tempfile(cipher, io)
|
|
20
|
+
end
|
|
21
|
+
@size = @encrypted.size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Integer]
|
|
25
|
+
attr_reader :size
|
|
26
|
+
|
|
27
|
+
def read(bytes = nil, output_buffer = nil)
|
|
28
|
+
if @encrypted.is_a?(Tempfile) && @encrypted.closed?
|
|
29
|
+
@encrypted.open
|
|
30
|
+
@encrypted.binmode
|
|
31
|
+
end
|
|
32
|
+
@encrypted.read(bytes, output_buffer)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rewind
|
|
36
|
+
@encrypted.rewind
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @api private
|
|
40
|
+
def close
|
|
41
|
+
@encrypted.close if @encrypted.is_a?(Tempfile)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
##= ../specification/s3-encryption/encryption.md#alg-aes-256-gcm-hkdf-sha512-commit-key
|
|
47
|
+
##% The client MUST append the GCM auth tag to the ciphertext if the underlying crypto provider does not do so automatically.
|
|
48
|
+
|
|
49
|
+
def encrypt_to_stringio(cipher, plain_text)
|
|
50
|
+
if plain_text.empty?
|
|
51
|
+
StringIO.new(cipher.final + cipher.auth_tag)
|
|
52
|
+
else
|
|
53
|
+
StringIO.new(cipher.update(plain_text) + cipher.final + cipher.auth_tag)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def encrypt_to_tempfile(cipher, io)
|
|
58
|
+
encrypted = Tempfile.new(object_id.to_s)
|
|
59
|
+
encrypted.binmode
|
|
60
|
+
##= ../specification/s3-encryption/encryption.md#content-encryption
|
|
61
|
+
##= type=implication
|
|
62
|
+
##% The client MUST validate that the length of the plaintext bytes does not exceed the algorithm suite's cipher's maximum content length in bytes.
|
|
63
|
+
# The expectation is that this is handled by the underlying cryptographic provider.
|
|
64
|
+
# In Ruby this is OpenSSL by default.
|
|
65
|
+
# See OpenSSL: https://github.com/openssl/openssl/blob/master/crypto/modes/gcm128.c#L784
|
|
66
|
+
# The relevant line is:
|
|
67
|
+
# if (mlen > ((U64(1) << 36) - 32) || (sizeof(len) == 8 && mlen < len))
|
|
68
|
+
# return -1;
|
|
69
|
+
while (chunk = io.read(ONE_MEGABYTE, read_buffer ||= String.new))
|
|
70
|
+
if cipher.method(:update).arity == 1
|
|
71
|
+
encrypted.write(cipher.update(chunk))
|
|
72
|
+
else
|
|
73
|
+
encrypted.write(cipher.update(chunk, cipher_buffer ||= String.new))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
encrypted.write(cipher.final)
|
|
77
|
+
encrypted.write(cipher.auth_tag)
|
|
78
|
+
encrypted.rewind
|
|
79
|
+
encrypted
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module S3
|
|
5
|
+
module EncryptionV3
|
|
6
|
+
# This module defines the interface required for a {Client#key_provider}.
|
|
7
|
+
# A key provider is any object that:
|
|
8
|
+
#
|
|
9
|
+
# * Responds to {#encryption_materials} with an {Materials} object.
|
|
10
|
+
#
|
|
11
|
+
# * Responds to {#key_for}, receiving a JSON document String,
|
|
12
|
+
# returning an encryption key. The returned encryption key
|
|
13
|
+
# must be one of:
|
|
14
|
+
#
|
|
15
|
+
# * `OpenSSL::PKey::RSA` - for asymmetric encryption
|
|
16
|
+
# * `String` - 32, 24, or 16 bytes long, for symmetric encryption
|
|
17
|
+
#
|
|
18
|
+
module KeyProvider
|
|
19
|
+
# @return [Materials]
|
|
20
|
+
def encryption_materials; end
|
|
21
|
+
|
|
22
|
+
# @param [String<JSON>] materials_description
|
|
23
|
+
# @return [OpenSSL::PKey::RSA, String] encryption_key
|
|
24
|
+
def key_for(materials_description); end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Aws
|
|
6
|
+
module S3
|
|
7
|
+
module EncryptionV3
|
|
8
|
+
# @api private
|
|
9
|
+
class KmsCipherProvider
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
@kms_key_id = validate_kms_key(options[:kms_key_id])
|
|
12
|
+
@kms_client = options[:kms_client]
|
|
13
|
+
@key_wrap_schema = validate_key_wrap(
|
|
14
|
+
options[:key_wrap_schema]
|
|
15
|
+
)
|
|
16
|
+
@content_encryption_schema = Utils.validate_cek(
|
|
17
|
+
options[:content_encryption_schema]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Array<Hash,Cipher>] Creates and returns a new encryption
|
|
22
|
+
# envelope and encryption cipher.
|
|
23
|
+
def encryption_cipher(options = {})
|
|
24
|
+
validate_key_for_encryption
|
|
25
|
+
encryption_context = build_encryption_context(@content_encryption_schema, options)
|
|
26
|
+
key_data = Aws::Plugins::UserAgent.metric('S3_CRYPTO_V3') do
|
|
27
|
+
@kms_client.generate_data_key(
|
|
28
|
+
key_id: @kms_key_id,
|
|
29
|
+
encryption_context: encryption_context,
|
|
30
|
+
key_spec: 'AES_256'
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
cipher, message_id, commitment_key = Utils.generate_alg_aes_256_gcm_hkdf_sha512_commit_key_cipher(key_data.plaintext)
|
|
34
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#algorithm-suite-and-message-format-version-compatibility
|
|
35
|
+
##% Objects encrypted with ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY MUST use the V3 message format version only.
|
|
36
|
+
envelope = {
|
|
37
|
+
'x-amz-3' => encode64(key_data.ciphertext_blob),
|
|
38
|
+
'x-amz-c' => @content_encryption_schema,
|
|
39
|
+
'x-amz-w' => @key_wrap_schema,
|
|
40
|
+
'x-amz-d' => encode64(commitment_key),
|
|
41
|
+
'x-amz-i' => encode64(message_id),
|
|
42
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
|
|
43
|
+
##% The Encryption Context value MUST be used for wrapping algorithm `kms+context` or `12`.
|
|
44
|
+
'x-amz-t' => Json.dump(encryption_context)
|
|
45
|
+
}
|
|
46
|
+
[envelope, cipher]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Cipher] Given an encryption envelope, returns a
|
|
50
|
+
# decryption cipher.
|
|
51
|
+
def decryption_cipher(envelope, options = {})
|
|
52
|
+
case envelope['x-amz-w']
|
|
53
|
+
when '12'
|
|
54
|
+
cek_alg = envelope['x-amz-c']
|
|
55
|
+
encryption_context =
|
|
56
|
+
if !envelope['x-amz-t'].nil?
|
|
57
|
+
Json.load(envelope['x-amz-t'])
|
|
58
|
+
else
|
|
59
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
|
|
60
|
+
##% If the mapkey x-amz-t is not present, the default Material Description value MUST be set to an empty map (`{}`).
|
|
61
|
+
{}
|
|
62
|
+
end
|
|
63
|
+
##= ../specification/s3-encryption/data-format/content-metadata.md#v3-only
|
|
64
|
+
##% - The wrapping algorithm value "12" MUST be translated to kms+context upon retrieval, and vice versa on write.
|
|
65
|
+
raise Errors::CEKAlgMismatchError if cek_alg != encryption_context['aws:x-amz-cek-alg']
|
|
66
|
+
|
|
67
|
+
if encryption_context != build_encryption_context(cek_alg, options)
|
|
68
|
+
raise Errors::DecryptionError, 'Value of encryption context from'\
|
|
69
|
+
' envelope does not match the provided encryption context'
|
|
70
|
+
end
|
|
71
|
+
when '02'
|
|
72
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
|
73
|
+
' with a KMS key and the x-amz-wrap-alg is AES/GCM.'
|
|
74
|
+
when '22'
|
|
75
|
+
raise ArgumentError, 'Key mismatch - Client is configured' \
|
|
76
|
+
' with a KMS key and the x-amz-wrap-alg is RSA-OAEP-SHA1.'
|
|
77
|
+
when nil
|
|
78
|
+
raise ArgumentError, 'Plaintext passthrough not supported'
|
|
79
|
+
else
|
|
80
|
+
# assert !envelope['x-amz-w'].nil?
|
|
81
|
+
# because of the when above
|
|
82
|
+
raise ArgumentError, 'Unsupported wrapping algorithm: ' \
|
|
83
|
+
"#{envelope['x-amz-w']}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
any_cmk_mode = options[:kms_allow_decrypt_with_any_cmk]
|
|
87
|
+
decrypt_options = {
|
|
88
|
+
ciphertext_blob: decode64(envelope['x-amz-3']),
|
|
89
|
+
encryption_context: encryption_context
|
|
90
|
+
}
|
|
91
|
+
decrypt_options[:key_id] = @kms_key_id unless any_cmk_mode
|
|
92
|
+
|
|
93
|
+
data_key = Aws::Plugins::UserAgent.metric('S3_CRYPTO_V3') do
|
|
94
|
+
@kms_client.decrypt(decrypt_options).plaintext
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
message_id = decode64(envelope['x-amz-i'])
|
|
98
|
+
commitment_key = decode64(envelope['x-amz-d'])
|
|
99
|
+
|
|
100
|
+
Utils.derive_alg_aes_256_gcm_hkdf_sha512_commit_key_cipher(data_key, message_id, commitment_key)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def validate_key_wrap(key_wrap_schema)
|
|
106
|
+
case key_wrap_schema
|
|
107
|
+
when :kms_context then '12'
|
|
108
|
+
else
|
|
109
|
+
raise ArgumentError, "Unsupported key_wrap_schema: #{key_wrap_schema}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def validate_kms_key(kms_key_id)
|
|
114
|
+
if kms_key_id.nil? || kms_key_id.empty?
|
|
115
|
+
raise ArgumentError, 'KMS CMK ID was not specified. ' \
|
|
116
|
+
'Please specify a CMK ID, ' \
|
|
117
|
+
'or set kms_key_id: :kms_allow_decrypt_with_any_cmk to use ' \
|
|
118
|
+
'any valid CMK from the object.'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if kms_key_id.is_a?(Symbol) && kms_key_id != :kms_allow_decrypt_with_any_cmk
|
|
122
|
+
raise ArgumentError, 'kms_key_id must be a valid KMS CMK or be ' \
|
|
123
|
+
'set to :kms_allow_decrypt_with_any_cmk'
|
|
124
|
+
end
|
|
125
|
+
kms_key_id
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_encryption_context(cek_alg, options = {})
|
|
129
|
+
kms_context = (options[:kms_encryption_context] || {})
|
|
130
|
+
.transform_keys(&:to_s)
|
|
131
|
+
if kms_context.include? 'aws:x-amz-cek-alg'
|
|
132
|
+
raise ArgumentError, 'Conflict in reserved KMS Encryption Context ' \
|
|
133
|
+
'key aws:x-amz-cek-alg. This value is reserved for the S3 ' \
|
|
134
|
+
'Encryption Client and cannot be set by the user.'
|
|
135
|
+
end
|
|
136
|
+
{
|
|
137
|
+
'aws:x-amz-cek-alg' => cek_alg
|
|
138
|
+
}.merge(kms_context)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def encode64(str)
|
|
142
|
+
Base64.encode64(str).split("\n") * ''
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def decode64(str)
|
|
146
|
+
Base64.decode64(str)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def validate_key_for_encryption
|
|
150
|
+
return unless @kms_key_id == :kms_allow_decrypt_with_any_cmk
|
|
151
|
+
|
|
152
|
+
raise ArgumentError, 'Unable to encrypt/write objects with '\
|
|
153
|
+
'kms_key_id = :kms_allow_decrypt_with_any_cmk. Provide ' \
|
|
154
|
+
'a valid kms_key_id on client construction.'
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Aws
|
|
6
|
+
module S3
|
|
7
|
+
module EncryptionV3
|
|
8
|
+
class Materials
|
|
9
|
+
# @option options [required, OpenSSL::PKey::RSA, String] :key
|
|
10
|
+
# The master key to use for encrypting/decrypting all objects.
|
|
11
|
+
#
|
|
12
|
+
# @option options [String<JSON>] :description ('{}')
|
|
13
|
+
# The encryption materials description. This is must be
|
|
14
|
+
# a JSON document string.
|
|
15
|
+
#
|
|
16
|
+
def initialize(options = {})
|
|
17
|
+
@key = validate_key(options[:key])
|
|
18
|
+
@description = validate_desc(options[:description])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [OpenSSL::PKey::RSA, String]
|
|
22
|
+
attr_reader :key
|
|
23
|
+
|
|
24
|
+
# @return [String<JSON>]
|
|
25
|
+
attr_reader :description
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def validate_key(key)
|
|
30
|
+
case key
|
|
31
|
+
when OpenSSL::PKey::RSA then key
|
|
32
|
+
when String
|
|
33
|
+
if [32, 24, 16].include?(key.bytesize)
|
|
34
|
+
key
|
|
35
|
+
else
|
|
36
|
+
msg = 'invalid key, symmetric key required to be 16, 24, or '\
|
|
37
|
+
'32 bytes in length, saw length ' + key.bytesize.to_s
|
|
38
|
+
raise ArgumentError, msg
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
msg = 'invalid encryption key, expected an OpenSSL::PKey::RSA key '\
|
|
42
|
+
'(for asymmetric encryption) or a String (for symmetric '\
|
|
43
|
+
'encryption).'
|
|
44
|
+
raise ArgumentError, msg
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate_desc(description)
|
|
49
|
+
Json.load(description)
|
|
50
|
+
description
|
|
51
|
+
rescue Json::ParseError, EncodingError
|
|
52
|
+
msg = 'expected description to be a valid JSON document string'
|
|
53
|
+
raise ArgumentError, msg
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|