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

Sign up to get free protection for your applications and to get access to all the features.
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