aws-sdk-resources 2.0.14.pre → 2.0.15.pre

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7178a784082813d4c136a0a3368a3544bdb14318
4
- data.tar.gz: 49c521f7cfd5386e5d6491efc1e463c71352fb47
3
+ metadata.gz: f052a7ea17d6dae941c7488668075d7faecf9767
4
+ data.tar.gz: cae90c630414ec0552c9b8e4df95449222d048e7
5
5
  SHA512:
6
- metadata.gz: 36866064d42e993de02eb9a1949465cd7585322cb2be6ca2a5b4284e9f0b810454f878568c417832d53c82ba8a39921e740a033a86e1f90d650ecfeeab53ce7f
7
- data.tar.gz: 8f14831679ce01d83360f2116ff1942f1ecf61065ce175dfa72266172a158860421c984457a3d8906e8514945b95ee3685c844cf3a195aeccd916594cb999c3c
6
+ metadata.gz: bc04753ba36090c1c4f521b2c2648d1bfd0f8cfa3071c15cd2ae21ec828cda120aa31c08dfd72941a20e71c12140773ce15fd8b124eb32e2b8e5f1e049a69212
7
+ data.tar.gz: 901efaa0baa5c3f8e52871c4ec57c963400c0cf1ebed22ec8f59d34f2cc2fe2d5d4cbfde9fc34af7da5639656e0da834a78723ce3efca06a8c5c16484c71c824
@@ -6,6 +6,7 @@ module Aws
6
6
  require 'aws-sdk-resources/services/s3/multipart_upload'
7
7
  require 'aws-sdk-resources/services/s3/public_url'
8
8
 
9
+ autoload :Encryption, 'aws-sdk-resources/services/s3/encryption'
9
10
  autoload :FilePart, 'aws-sdk-resources/services/s3/file_part'
10
11
  autoload :FileUploader, 'aws-sdk-resources/services/s3/file_uploader'
11
12
  autoload :MultipartFileUploader, 'aws-sdk-resources/services/s3/multipart_file_uploader'
@@ -0,0 +1,18 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+
5
+ autoload :Client, 'aws-sdk-resources/services/s3/encryption/client'
6
+ autoload :DecryptHandler, 'aws-sdk-resources/services/s3/encryption/decrypt_handler'
7
+ autoload :DefaultKeyProvider, 'aws-sdk-resources/services/s3/encryption/default_key_provider'
8
+ autoload :EncryptHandler, 'aws-sdk-resources/services/s3/encryption/encrypt_handler'
9
+ autoload :Errors, 'aws-sdk-resources/services/s3/encryption/errors'
10
+ autoload :IOEncrypter, 'aws-sdk-resources/services/s3/encryption/io_encrypter'
11
+ autoload :IODecrypter, 'aws-sdk-resources/services/s3/encryption/io_decrypter'
12
+ autoload :KeyProvider, 'aws-sdk-resources/services/s3/encryption/key_provider'
13
+ autoload :Materials, 'aws-sdk-resources/services/s3/encryption/materials'
14
+ autoload :Utils, 'aws-sdk-resources/services/s3/encryption/utils'
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,276 @@
1
+ module Aws
2
+ module S3
3
+
4
+ # Provides an encryption client that encrypts and decrypts data client-side,
5
+ # storing the encrypted data in Amazon S3.
6
+ #
7
+ # This client uses a process called "envelope encryption". Your private
8
+ # encryption keys and your data's plain-text are **never** sent to
9
+ # Amazon S3. **If you loose you encrption keys, you will not be able to
10
+ # un-encrypt your data.**
11
+ #
12
+ # ## Envelope Encryption Overview
13
+ #
14
+ # The goal of envelope encryption is to combine the performance of
15
+ # fast symmetric encryption while maintaining the secure key management
16
+ # that asymmetric keys provide.
17
+ #
18
+ # A one-time-use symmetric key (envelope key) is generated client-side.
19
+ # This is used to encrypt the data client-side. This key is then
20
+ # encrypted by your master key and stored alongside your data in Amazon
21
+ # S3.
22
+ #
23
+ # When accessing your encrypted data with the encryption client,
24
+ # the encrypted envelope key is retrieved and decrypted client-side
25
+ # with your master key. The envelope key is then used to decrypt the
26
+ # data client-side.
27
+ #
28
+ # One of the benefits of envelope encryption is that if your master key
29
+ # is compromised, you have the option of jut re-encrypting the stored
30
+ # envelope symmetric keys, instead of re-encrypting all of the
31
+ # data in your account.
32
+ #
33
+ # ## Basic Usage
34
+ #
35
+ # The encryption client requires an {Aws::S3::Client}. If you do not
36
+ # provide a `:client`, then a client will be constructed for you.
37
+ #
38
+ # require 'openssl'
39
+ # key = OpenSSL::PKey::RSA.new(1024)
40
+ #
41
+ # # encryption client
42
+ # s3 = Aws::S3::Encryption::Client.new(encryption_key: key)
43
+ #
44
+ # # round-trip an object, encrypted/decrypted locally
45
+ # s3.put_object(bucket:'aws-sdk', key:'secret', body:'handshake')
46
+ # s3.get_object(bucket:'aws-sdk', key:'secret').body.read
47
+ # #=> 'handshake'
48
+ #
49
+ # # reading encrypted object without the encryption client
50
+ # # results in the getting the cipher text
51
+ # Aws::S3::Client.new.get_object(bucket:'aws-sdk', key:'secret').body.read
52
+ # #=> "... cipher text ..."
53
+ #
54
+ # ## Keys
55
+ #
56
+ # For client-side encryption to work, you must provide an encryption key:
57
+ #
58
+ # key = OpenSSL::Cipher.new("AES-256-ECB").random_key # symmetric key
59
+ # key = OpenSSL::PKey::RSA.new(1024) # asymmetric key pair
60
+ #
61
+ # s3 = Aws::S3::Encryption::Client.new(encryption_key: key)
62
+ #
63
+ # Alternatively, you can use a {KeyProvider}. A key provider makes
64
+ # it easy to work with multiple keys and simplifies key rotation.
65
+ #
66
+ # ### Key Provider
67
+ #
68
+ # A {KeyProvider} is any object that responds to:
69
+ #
70
+ # * `#encryption_materials`
71
+ # * `#key_for(materials_description)`
72
+ #
73
+ # Here is a trivial implementation of an in-memory key provider.
74
+ # This is provided as a demonstration of the key provider interface,
75
+ # and should not be used in production:
76
+ #
77
+ # class KeyProvider
78
+ #
79
+ # def initialize(default_key_name, keys)
80
+ # @keys = keys
81
+ # @encryption_materials = Aws::S3::Encryption::Materials.new(
82
+ # key: @keys[default_key_name],
83
+ # description: MultiJson.dump(key: default_key_name),
84
+ # )
85
+ # end
86
+ #
87
+ # attr_reader :encryption_materials
88
+ #
89
+ # def key_for(matdesc)
90
+ # key_name = MultiJson.load(matdesc)['key']
91
+ # if key = @keys[key_name]
92
+ # key
93
+ # else
94
+ # raise "encryption key not found for: #{matdesc.inspect}"
95
+ # end
96
+ # end
97
+ # end
98
+ #
99
+ # Given the above key provider, you can create an encryption client that
100
+ # chooses the key to use based on the materials description stored with
101
+ # the encrypted object. This makes it possible to use multiple keys
102
+ # and simplifies key rotation.
103
+ #
104
+ # # uses "new-key" for encrypting objects, uses either for decrypting
105
+ # keys = KeyProvider.new('new-key', {
106
+ # "old-key" => Base64.decode64("kM5UVbhE/4rtMZJfsadYEdm2vaKFsmV2f5+URSeUCV4="),
107
+ # "new-key" => Base64.decode64("w1WLio3agRWRTSJK/Ouh8NHoqRQ6fn5WbSXDTHjXMSo="),
108
+ # }),
109
+ #
110
+ # # chooses the key based on the materials description stored
111
+ # # with the encrypted object
112
+ # s3 = Aws::S3::Encryption::Client.new(key_provider: keys)
113
+ #
114
+ # ## Materials Description
115
+ #
116
+ # A materials description is JSON document string that is stored
117
+ # in the metadata (or instruction file) of an encrypted object.
118
+ # The {DefaultKeyProvider} uses the empty JSON document `"{}"`.
119
+ #
120
+ # When building a key provider, you are free to store whatever
121
+ # information you need to identify the master key that was used
122
+ # to encrypt the object.
123
+ #
124
+ # ## Envelope Location
125
+ #
126
+ # By default, the encryption client store the encryption envelope
127
+ # with the object, as metadata. You can choose to have the envelope
128
+ # stored in a separate "instruction file". An instruction file
129
+ # is an object, with the key of the encrypted object, suffixed with
130
+ # `".instruction"`.
131
+ #
132
+ # Specify the `:envelope_location` option as `:instruction_file` to
133
+ # use an instruction file for storing the envelope.
134
+ #
135
+ # # default behavior
136
+ # s3 = Aws::S3::Encryption::Client.new(
137
+ # key_provider: ...,
138
+ # envelope_location: :metadata,
139
+ # )
140
+ #
141
+ # # store envelope in a separate object
142
+ # s3 = Aws::S3::Encryption::Client.new(
143
+ # key_provider: ...,
144
+ # envelope_location: :instruction_file,
145
+ # instruction_file_suffix: '.instruction' # default
146
+ # )
147
+ #
148
+ # When using an instruction file, multiple requests are made when
149
+ # putting and getting the object. **This may cause issues if you are
150
+ # issuing concurrent PUT and GET requests to an encrypted object.**
151
+ #
152
+ module Encryption
153
+
154
+ class Client
155
+
156
+ # Creates a new encryption client. You must provide on of the following
157
+ # options:
158
+ #
159
+ # * `:key_provider`
160
+ # * `:encryption_key`
161
+ #
162
+ # @option opitons [S3::Client] :client A basic S3 client that is used
163
+ # to make api calls. If a `:client` is not provided, a new {S3::Client}
164
+ # will be constructed.
165
+ #
166
+ # @option options [#key_for] :key_provider Any object that responds
167
+ # to `#key_for`. This method should accept a materials description
168
+ # JSON document string and return return an encryption key.
169
+ #
170
+ # @option options [OpenSSL::PKey::RSA, String] :encryption_key The master
171
+ # key to use for encrypting/decrypting all objects.
172
+ #
173
+ # @option options [Symbol] :envelope_location (:metadata) Where to
174
+ # store the envelope encryption keys. By default, the envelope is
175
+ # stored with the encrypted object. If you pass `:instruction_file`,
176
+ # then the envelope is stored in a seperate object in Amazon S3.
177
+ #
178
+ # @option options [String] :instruction_file_suffix ('.instruction')
179
+ # When `:envelope_location` is `:instruction_file` then the
180
+ # instruction file uses the object key with this suffix appended.
181
+ #
182
+ def initialize(options = {})
183
+ @client = options[:client] || S3::Client.new
184
+ @key_provider = extract_key_provider(options)
185
+ @envelope_location = extract_location(options)
186
+ @instruction_file_suffix = extract_suffix(options)
187
+ end
188
+
189
+ # @return [S3::Client]
190
+ attr_reader :client
191
+
192
+ # @return [KeyProvider]
193
+ attr_reader :key_provider
194
+
195
+ # @return [Symbol<:metadata, :instruction_file>]
196
+ attr_reader :envelope_location
197
+
198
+ # @return [String] When {#envelope_location} is `:instruction_file`,
199
+ # the envelope is stored in the object with the object key suffixed
200
+ # by this string.
201
+ attr_reader :instruction_file_suffix
202
+
203
+ # Calls {S3::Client#put_object}. The `:body` option will be encrypted
204
+ # client-side before sending data to Amazon S3.
205
+ # @see S3::Client#put_object
206
+ def put_object(options = {})
207
+ req = @client.build_request(:put_object, options)
208
+ req.handlers.add(EncryptHandler, priority: 95)
209
+ req.context[:encryption] = {
210
+ materials: @key_provider.encryption_materials,
211
+ envelope_location: @envelope_location,
212
+ instruction_file_suffix: @instruction_file_suffix,
213
+ }
214
+ req.send_request
215
+ end
216
+
217
+ # Performs an `#get_object` request on the `#config.client`. The
218
+ # response body is decrypted as it is read from the HTTP response
219
+ # socket. See {Client#get_object} for documentation on valid
220
+ # options.
221
+ def get_object(options = {})
222
+ if options[:range]
223
+ raise NotImplementedError, '#get_object with :range not supported yet'
224
+ end
225
+
226
+ location = options.delete(:envelope_location) || @envelope_location
227
+ suffix = options.delete(:instruction_file_suffix) || @instruction_file_suffix
228
+
229
+ req = @client.build_request(:get_object, options)
230
+ req.handlers.add(DecryptHandler)
231
+ req.context[:encryption] = {
232
+ key_provider: @key_provider,
233
+ envelope_location: location,
234
+ instruction_file_suffix: suffix,
235
+ }
236
+ req.send_request
237
+ end
238
+
239
+ private
240
+
241
+ def extract_key_provider(options)
242
+ if options[:key_provider]
243
+ options[:key_provider]
244
+ elsif options[:encryption_key]
245
+ DefaultKeyProvider.new(options)
246
+ else
247
+ msg = "you must pass a :key_provider or :encryption_key"
248
+ raise ArgumentError, msg
249
+ end
250
+ end
251
+
252
+ def extract_location(options)
253
+ location = options[:envelope_location] || :metadata
254
+ if [:metadata, :instruction_file].include?(location)
255
+ location
256
+ else
257
+ msg = ":envelope_location must be :metadata or :instruction_file "
258
+ msg << "got #{location.inspect}"
259
+ raise ArgumentError, msg
260
+ end
261
+ end
262
+
263
+ def extract_suffix(options)
264
+ suffix = options[:instruction_file_suffix] || '.instruction'
265
+ if String === suffix
266
+ suffix
267
+ else
268
+ msg = ":instruction_file_suffix must be a String"
269
+ raise ArgumentError, msg
270
+ end
271
+ end
272
+
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,65 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+ class Config < Struct.new(
5
+ :client,
6
+ :key_provider,
7
+ :materials_description,
8
+ :materials_location,
9
+ :instruction_file_suffix)
10
+
11
+ # @api private
12
+ DEFAULTS = {
13
+ client: lambda { S3::Client.new },
14
+ key_provider: lambda {
15
+ msg = "must specify an :encryption_key or :key_provider"
16
+ raise ArgumentError, msg
17
+ },
18
+ materials_description: '{}',
19
+ materials_location: :metadata,
20
+ instruction_file_suffix: '.instruction',
21
+ }
22
+
23
+ def encryption_key= master_key
24
+ self[:key_provider] = DefaultKeyProvider.new(master_key)
25
+ end
26
+
27
+ # @param [Symbol] location :metadata Must be one of
28
+ # the following values:
29
+ # * :metadata
30
+ # * :instruction_file
31
+ def materials_location= location
32
+ unless [:metadata, :instruction_file].include?(location)
33
+ msg = ":materials_location must be :metadata or :instruction_file "
34
+ msg << "got #{location.inspect}"
35
+ raise ArgumentError, msg
36
+ end
37
+ super
38
+ end
39
+
40
+ class << self
41
+
42
+ def build(options)
43
+ config = Config.new
44
+ options.each do |opt_name, opt_value|
45
+ config.send("#{opt_name}=", opt_value)
46
+ end
47
+ apply_defaults(config)
48
+ config
49
+ end
50
+
51
+ private
52
+
53
+ def apply_defaults(config)
54
+ DEFAULTS.each do |key, value|
55
+ if config[key].nil?
56
+ config[key] = value.is_a?(Proc) ? value.call : value
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,84 @@
1
+ require 'base64'
2
+
3
+ module Aws
4
+ module S3
5
+ module Encryption
6
+ # @api private
7
+ class DecryptHandler < Seahorse::Client::Handler
8
+
9
+ def call(context)
10
+ attach_http_event_listeners(context)
11
+ @handler.call(context)
12
+ end
13
+
14
+ private
15
+
16
+ def attach_http_event_listeners(context)
17
+ context.http_response.on_headers(200) do
18
+ cipher = decryption_cipher(context)
19
+ body = context.http_response.body
20
+ context.http_response.body = IODecrypter.new(cipher, body)
21
+ end
22
+
23
+ context.http_response.on_success(200) do
24
+ decrypter = context.http_response.body
25
+ decrypter.finalize
26
+ decrypter.io.rewind if decrypter.io.respond_to?(:rewind)
27
+ context.http_response.body = decrypter.io
28
+ end
29
+
30
+ context.http_response.on_error do
31
+ if context.http_response.body.is_a?(IODecrypter)
32
+ context.http_response.body = context.http_response.body.io
33
+ end
34
+ end
35
+ end
36
+
37
+ def decryption_cipher(context)
38
+ if envelope = get_encryption_envelope(context)
39
+ key_provider = context[:encryption][:key_provider]
40
+ key = key_provider.key_for(envelope['x-amz-matdesc'])
41
+ envelope_key = Utils.decrypt(key, decode64(envelope['x-amz-key']))
42
+ envelope_iv = decode64(envelope['x-amz-iv'])
43
+ Utils.aes_decryption_cipher(:CBC, envelope_key, envelope_iv)
44
+ else
45
+ raise Errors::DecryptionError, "unable to locate encyrption envelope"
46
+ end
47
+ end
48
+
49
+ def get_encryption_envelope(context)
50
+ if context[:encryption][:envelope_location] == :metadata
51
+ envelope_from_metadata(context) || envelope_from_instr_file(context)
52
+ else
53
+ envelope_from_instr_file(context) || envelope_from_metadata(context)
54
+ end
55
+ end
56
+
57
+ def envelope_from_metadata(context)
58
+ keys = %w(x-amz-key x-amz-iv x-amz-matdesc)
59
+ envelope = keys.each.with_object({}) do |key, hash|
60
+ if value = context.http_response.headers["x-amz-meta-#{key}"]
61
+ hash[key] = value
62
+ end
63
+ end
64
+ envelope.keys == keys ? envelope : nil
65
+ end
66
+
67
+ def envelope_from_instr_file(context)
68
+ suffix = context[:encryption][:instruction_file_suffix]
69
+ MultiJson.load(context.client.get_object(
70
+ bucket: context.params[:bucket],
71
+ key: context.params[:key] + suffix
72
+ ).body.read)
73
+ rescue S3::Errors::ServiceError, MultiJson::ParseError
74
+ nil
75
+ end
76
+
77
+ def decode64(str)
78
+ Base64.decode64(str)
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,38 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+
5
+ # The default key provider is constructed with a single key
6
+ # that is used for both encryption and decryption, ignoring
7
+ # the possible per-object envelope encryption materials description.
8
+ # @api private
9
+ class DefaultKeyProvider
10
+
11
+ include KeyProvider
12
+
13
+ # @option options [required, OpenSSL::PKey::RSA, String] :encryption_key
14
+ # The master key to use for encrypting objects.
15
+ # @option options [String<JSON>] :materials_description ('{}')
16
+ # A description of the encyrption key.
17
+ def initialize(options = {})
18
+ @encryption_materials = Materials.new(
19
+ key: options[:encryption_key],
20
+ description: options[:materials_description] || '{}'
21
+ )
22
+ end
23
+
24
+ # @return [Materials]
25
+ def encryption_materials
26
+ @encryption_materials
27
+ end
28
+
29
+ # @param [String<JSON>] materials_description
30
+ # @return Returns the key given in the constructor.
31
+ def key_for(materials_description)
32
+ @encryption_materials.key
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,71 @@
1
+ require 'base64'
2
+
3
+ module Aws
4
+ module S3
5
+ module Encryption
6
+ # @api private
7
+ class EncryptHandler < Seahorse::Client::Handler
8
+
9
+ def call(context)
10
+ cipher = Utils.aes_encryption_cipher(:CBC)
11
+ context[:encryption][:cipher] = cipher
12
+ envelope = {
13
+ 'x-amz-key' => encode64(encrypt(context, envelope_key(cipher))),
14
+ 'x-amz-iv' => encode64(envelope_iv(cipher)),
15
+ 'x-amz-matdesc' => context[:encryption][:materials].description,
16
+ }
17
+ apply_encryption_envelope(context, envelope)
18
+ apply_encryption_cipher(context, cipher)
19
+ @handler.call(context)
20
+ end
21
+
22
+ private
23
+
24
+ def apply_encryption_envelope(context, envelope)
25
+ if context[:encryption][:envelope_location] == :metadata
26
+ context.params[:metadata] ||= {}
27
+ context.params[:metadata].update(envelope)
28
+ else # :instruction_file
29
+ suffix = context[:encryption][:instruction_file_suffix]
30
+ context.client.put_object(
31
+ bucket: context.params[:bucket],
32
+ key: context.params[:key] + suffix,
33
+ body: MultiJson.dump(envelope)
34
+ )
35
+ end
36
+ end
37
+
38
+ def apply_encryption_cipher(context, cipher)
39
+ io = context.params[:body] || ''
40
+ io = StringIO.new(io) if String === io
41
+ context.params[:body] = IOEncrypter.new(cipher, io)
42
+ context.params[:metadata] ||= {}
43
+ context.params[:metadata]['x-amz-unencrypted-content-length'] = io.size
44
+ if md5 = context.params.delete(:content_md5)
45
+ context.params[:metadata]['x-amz-unencrypted-content-md5'] = md5
46
+ end
47
+ context.http_response.on_headers do
48
+ context.params[:body].close
49
+ end
50
+ end
51
+
52
+ def envelope_key(cipher)
53
+ cipher.key = cipher.random_key
54
+ end
55
+
56
+ def envelope_iv(cipher)
57
+ cipher.iv = cipher.random_iv
58
+ end
59
+
60
+ def encode64(str)
61
+ Base64.encode64(str).split("\n") * ""
62
+ end
63
+
64
+ def encrypt(context, data)
65
+ Utils.encrypt(context[:encryption][:materials].key, data)
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+ module Errors
5
+
6
+ class DecryptionError < RuntimeError; end
7
+
8
+ class EncryptionError < RuntimeError; end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+ # @api private
5
+ class IODecrypter
6
+
7
+ # @param [OpenSSL::Cipher] cipher
8
+ # @param [#write] io An IO-like object that responds to {#write}.
9
+ def initialize(cipher, io)
10
+ @orig_cipher = cipher.clone
11
+ @cipher = cipher.clone
12
+ @io = io
13
+ reset_cipher
14
+ end
15
+
16
+ # @return [#write]
17
+ attr_reader :io
18
+
19
+ def write(chunk)
20
+ @io.write(@cipher.update(chunk))
21
+ end
22
+
23
+ def finalize
24
+ @io.write(@cipher.final)
25
+ end
26
+
27
+ private
28
+
29
+ def reset_cipher
30
+ @cipher = @orig_cipher.clone
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,69 @@
1
+ require 'stringio'
2
+ require 'tempfile'
3
+
4
+ module Aws
5
+ module S3
6
+ module Encryption
7
+
8
+ # Provides an IO wrapper encrpyting a stream of data.
9
+ # It is possible to use this same object for decrypting. You must
10
+ # initialize it with a decryptiion cipher in that case and the
11
+ # IO object must contain cipher text instead of plain text.
12
+ # @api private
13
+ class IOEncrypter
14
+
15
+ # @api private
16
+ ONE_MEGABYTE = 1024 * 1024
17
+
18
+ def initialize(cipher, io)
19
+ @encrypted = io.size <= ONE_MEGABYTE ?
20
+ encrypt_to_stringio(cipher, io.read) :
21
+ encrypt_to_tempfile(cipher, io)
22
+ @size = @encrypted.size
23
+ end
24
+
25
+ # @return [Integer]
26
+ attr_reader :size
27
+
28
+ def read(bytes = nil, output_buffer = nil)
29
+ if Tempfile === @encrypted && @encrypted.closed?
30
+ @encrypted.open
31
+ @encrypted.binmode
32
+ end
33
+ @encrypted.read(bytes, output_buffer)
34
+ end
35
+
36
+ def rewind
37
+ @encrypted.rewind
38
+ end
39
+
40
+ # @api private
41
+ def close
42
+ @encrypted.close if Tempfile === @encrypted
43
+ end
44
+
45
+ private
46
+
47
+ def encrypt_to_stringio(cipher, plain_text)
48
+ if plain_text.empty?
49
+ StringIO.new(cipher.final)
50
+ else
51
+ StringIO.new(cipher.update(plain_text) + cipher.final)
52
+ end
53
+ end
54
+
55
+ def encrypt_to_tempfile(cipher, io)
56
+ encrypted = Tempfile.new(self.object_id.to_s)
57
+ encrypted.binmode
58
+ while chunk = io.read(ONE_MEGABYTE)
59
+ encrypted.write(cipher.update(chunk))
60
+ end
61
+ encrypted.write(cipher.final)
62
+ encrypted.rewind
63
+ encrypted
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ module Aws
2
+ module S3
3
+ module Encryption
4
+
5
+ # This module defines the interface required for a {Client#key_provider}.
6
+ # A key provider is any object that:
7
+ #
8
+ # * Responds to {#encryption_materials} with an {Materials} object.
9
+ #
10
+ # * Responds to {#key_for}, receiving a JSON document String,
11
+ # returning an ecryption key. The returned encryption key
12
+ # must be one of:
13
+ #
14
+ # * `OpenSSL::PKey::RSA` - for asymmetric encryption
15
+ # * `String` - 32, 24, or 16 bytes long, for symmetric encryption
16
+ #
17
+ module KeyProvider
18
+
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
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,67 @@
1
+ require 'base64'
2
+
3
+ module Aws
4
+ module S3
5
+ module Encryption
6
+ class Materials
7
+
8
+ # @option options [required, OpenSSL::PKey::RSA, String] :encryption_key
9
+ # The master key to use for encrypting/decrypting all objects.
10
+ #
11
+ # @option options [String<JSON>] :materials_description ('{}')
12
+ # The encryption materials description. This is must be
13
+ # a JSON document string.
14
+ #
15
+ # @option options [Symbol] :materials_location (:metadata) Where to
16
+ # store the envelope encryption keys. This must be one of
17
+ # the following values:
18
+ #
19
+ # * `:metadata`
20
+ # * `:instruction_file`
21
+ #
22
+ # @option options [String] :instruction_file_suffix ('.instruction')
23
+ #
24
+ def initialize(options = {})
25
+ @key = validate_key(options[:key])
26
+ @description = validate_desc(options[:description])
27
+ end
28
+
29
+ # @return [OpenSSL::PKey::RSA, String]
30
+ attr_reader :key
31
+
32
+ # @return [String<JSON>]
33
+ attr_reader :description
34
+
35
+ private
36
+
37
+ def validate_key(key)
38
+ case key
39
+ when OpenSSL::PKey::RSA then key
40
+ when String
41
+ if [32, 24, 16].include?(key.bytesize)
42
+ key
43
+ else
44
+ msg = "invalid key, symmetric key required to be 16, 24, or "
45
+ msg << "32 bytes in length, saw length 31"
46
+ raise ArgumentError, msg
47
+ end
48
+ else
49
+ msg = "invalid encryption key, expected an OpenSSL::PKey::RSA key "
50
+ msg << "(for asymmetric encryption) or a String (for symmetric "
51
+ msg << "encryption)."
52
+ raise ArgumentError, msg
53
+ end
54
+ end
55
+
56
+ def validate_desc(description)
57
+ MultiJson.load(description)
58
+ description
59
+ rescue MultiJson::ParseError
60
+ msg = "expected description to be a valid JSON document string"
61
+ raise ArgumentError, msg
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,80 @@
1
+ require 'openssl'
2
+
3
+ module Aws
4
+ module S3
5
+ module Encryption
6
+ # @api private
7
+ module Utils
8
+
9
+ UNSAFE_MSG = "unsafe encryption, data is longer than key length"
10
+
11
+ class << self
12
+
13
+ def encrypt(key, data)
14
+ case key
15
+ when OpenSSL::PKey::RSA # asymmetric encryption
16
+ warn(UNSAFE_MSG) if key.public_key.n.num_bits < cipher_size(data)
17
+ key.public_encrypt(data)
18
+ when String # symmetric encryption
19
+ warn(UNSAFE_MSG) if cipher_size(key) < cipher_size(data)
20
+ cipher = aes_encryption_cipher(:ECB, key)
21
+ cipher.update(data) + cipher.final
22
+ end
23
+ end
24
+
25
+ def decrypt(key, data)
26
+ rsa = OpenSSL::PKey::RSA
27
+ begin
28
+ case key
29
+ when OpenSSL::PKey::RSA # asymmetric decryption
30
+ key.private_decrypt(data)
31
+ when String # symmetric Decryption
32
+ cipher = aes_cipher(:decrypt, :ECB, key, nil)
33
+ cipher.update(data) + cipher.final
34
+ end
35
+ rescue OpenSSL::Cipher::CipherError
36
+ msg = 'decryption failed, possible incorrect key'
37
+ raise Errors::DecryptionError, msg
38
+ end
39
+ end
40
+
41
+ # @param [String] block_mode "CBC" or "ECB"
42
+ # @param [OpenSSL::PKey::RSA, String, nil] key
43
+ # @param [String, nil] iv The initialization vector
44
+ def aes_encryption_cipher(block_mode, key = nil, iv = nil)
45
+ aes_cipher(:encrypt, block_mode, key, iv)
46
+ end
47
+
48
+ # @param [String] block_mode "CBC" or "ECB"
49
+ # @param [OpenSSL::PKey::RSA, String, nil] key
50
+ # @param [String, nil] iv The initialization vector
51
+ def aes_decryption_cipher(block_mode, key = nil, iv = nil)
52
+ aes_cipher(:decrypt, block_mode, key, iv)
53
+ end
54
+
55
+ # @param [String] mode "encrypt" or "decrypt"
56
+ # @param [String] block_mode "CBC" or "ECB"
57
+ # @param [OpenSSL::PKey::RSA, String, nil] key
58
+ # @param [String, nil] iv The initialization vector
59
+ def aes_cipher(mode, block_mode, key, iv)
60
+ cipher = key ?
61
+ OpenSSL::Cipher.new("AES-#{cipher_size(key)}-#{block_mode}") :
62
+ OpenSSL::Cipher.new("AES-256-#{block_mode}")
63
+ cipher.send(mode) # encrypt or decrypt
64
+ cipher.key = key if key
65
+ cipher.iv = iv if iv
66
+ cipher
67
+ end
68
+
69
+ # @param [String] key
70
+ # @return [Integer]
71
+ # @raise ArgumentError
72
+ def cipher_size(key)
73
+ key.bytesize * 8
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-sdk-resources
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.14.pre
4
+ version: 2.0.15.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amazon Web Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-12 00:00:00.000000000 Z
11
+ date: 2014-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 2.0.14
19
+ version: 2.0.15
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 2.0.14
26
+ version: 2.0.15
27
27
  description: Provides resource-oriented abstractions for AWS.
28
28
  email:
29
29
  executables: []
@@ -53,6 +53,18 @@ files:
53
53
  - lib/aws-sdk-resources/services/ec2/instance.rb
54
54
  - lib/aws-sdk-resources/services/ec2.rb
55
55
  - lib/aws-sdk-resources/services/s3/bucket.rb
56
+ - lib/aws-sdk-resources/services/s3/encryption/client.rb
57
+ - lib/aws-sdk-resources/services/s3/encryption/config.rb
58
+ - lib/aws-sdk-resources/services/s3/encryption/decrypt_handler.rb
59
+ - lib/aws-sdk-resources/services/s3/encryption/default_key_provider.rb
60
+ - lib/aws-sdk-resources/services/s3/encryption/encrypt_handler.rb
61
+ - lib/aws-sdk-resources/services/s3/encryption/errors.rb
62
+ - lib/aws-sdk-resources/services/s3/encryption/io_decrypter.rb
63
+ - lib/aws-sdk-resources/services/s3/encryption/io_encrypter.rb
64
+ - lib/aws-sdk-resources/services/s3/encryption/key_provider.rb
65
+ - lib/aws-sdk-resources/services/s3/encryption/materials.rb
66
+ - lib/aws-sdk-resources/services/s3/encryption/utils.rb
67
+ - lib/aws-sdk-resources/services/s3/encryption.rb
56
68
  - lib/aws-sdk-resources/services/s3/file_part.rb
57
69
  - lib/aws-sdk-resources/services/s3/file_uploader.rb
58
70
  - lib/aws-sdk-resources/services/s3/multipart_file_uploader.rb