aws-s3-cse 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +24 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/aws-s3-cse.gemspec +75 -0
- data/lib/aws-s3-cse.rb +7 -0
- data/lib/aws/s3/crypter.rb +35 -0
- data/lib/aws/s3/encrypted_client.rb +73 -0
- data/lib/aws/s3/encrypted_config.rb +31 -0
- data/lib/aws/s3/encryption_errors.rb +30 -0
- data/lib/aws/s3/model.rb +73 -0
- data/spec/aws/s3/crypter_spec.rb +70 -0
- data/spec/aws/s3/encrypted_client_spec.rb +209 -0
- data/spec/aws/s3/model_spec.rb +91 -0
- data/spec/spec_helper.rb +8 -0
- metadata +181 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
|
9
|
+
gem 'aws-sdk'
|
10
|
+
gem 'builder'
|
11
|
+
|
12
|
+
group :development do
|
13
|
+
gem "jeweler", "~> 1.8.3"
|
14
|
+
gem "rspec", ">= 2.8.0"
|
15
|
+
gem "rspec-mocks", ">= 2.9.0"
|
16
|
+
gem "rspec-spies", ">= 2.1.0"
|
17
|
+
gem "rcov", "0.9.9"
|
18
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
aws-sdk (1.3.9)
|
5
|
+
httparty (~> 0.7)
|
6
|
+
json (~> 1.4)
|
7
|
+
nokogiri (>= 1.4.4)
|
8
|
+
uuidtools (~> 2.1)
|
9
|
+
builder (3.0.0)
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
git (1.2.5)
|
12
|
+
httparty (0.8.1)
|
13
|
+
multi_json
|
14
|
+
multi_xml
|
15
|
+
jeweler (1.8.3)
|
16
|
+
bundler (~> 1.0)
|
17
|
+
git (>= 1.2.5)
|
18
|
+
rake
|
19
|
+
rdoc
|
20
|
+
json (1.6.6)
|
21
|
+
multi_json (1.2.0)
|
22
|
+
multi_xml (0.4.2)
|
23
|
+
nokogiri (1.5.2)
|
24
|
+
rake (0.9.2.2)
|
25
|
+
rcov (0.9.9)
|
26
|
+
rdoc (3.12)
|
27
|
+
json (~> 1.4)
|
28
|
+
rspec (2.9.0)
|
29
|
+
rspec-core (~> 2.9.0)
|
30
|
+
rspec-expectations (~> 2.9.0)
|
31
|
+
rspec-mocks (~> 2.9.0)
|
32
|
+
rspec-core (2.9.0)
|
33
|
+
rspec-expectations (2.9.1)
|
34
|
+
diff-lcs (~> 1.1.3)
|
35
|
+
rspec-mocks (2.9.0)
|
36
|
+
rspec-spies (2.1.0)
|
37
|
+
rspec (~> 2.0)
|
38
|
+
uuidtools (2.1.2)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
aws-sdk
|
45
|
+
builder
|
46
|
+
jeweler (~> 1.8.3)
|
47
|
+
rcov (= 0.9.9)
|
48
|
+
rspec (>= 2.8.0)
|
49
|
+
rspec-mocks (>= 2.9.0)
|
50
|
+
rspec-spies (>= 2.1.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Tom Nijmeijer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= aws-s3-cse
|
2
|
+
|
3
|
+
Provides a bare-bones Ruby implementation of the client-side encryption for the AWS S3 service. When writing data to AWS the data is encrypted using a randomly generated envelope key and initialization vector. The envelope key is encrypted using a private key provided by the user and added as metadata to the object together with the initialization vector. When reading the object the envelope key is decrypted using the user-provided public key. The data is then decrypted using the envelope key.
|
4
|
+
|
5
|
+
The following config options are added to AWS.config:
|
6
|
+
* s3_private_key - the private key with which to encrypt the envelope key
|
7
|
+
* s3_public_key - the public key with which to decrypt the envelope key
|
8
|
+
* s3_client_side_encryption - boolean indicating whether to use client side encryption or not
|
9
|
+
|
10
|
+
== Contributing to aws-s3-cse
|
11
|
+
|
12
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
13
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
14
|
+
* Fork the project.
|
15
|
+
* Start a feature/bugfix branch.
|
16
|
+
* Commit and push until you are happy with your contribution.
|
17
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
18
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
19
|
+
|
20
|
+
== Copyright
|
21
|
+
|
22
|
+
Copyright (c) 2012 Tom Nijmeijer. See LICENSE.txt for
|
23
|
+
further details.
|
24
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "aws-s3-cse"
|
18
|
+
gem.homepage = "http://github.com/tcnijmeijer/aws-s3-cse"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = "Provides a ruby implementation of the Client Side Encryption client for AWS-S3"
|
21
|
+
gem.description = "Provides a ruby implementation of the Client Side Encryption client for AWS-S3"
|
22
|
+
gem.email = "tom@nijmeijer.org"
|
23
|
+
gem.authors = ["Tom Nijmeijer"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core/rake_task'
|
29
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
30
|
+
spec.pattern = './spec/**/*_spec.rb'
|
31
|
+
end
|
32
|
+
|
33
|
+
RSpec::Core::RakeTask.new(:rcov) do |rcov|
|
34
|
+
rcov.pattern = "./spec/**/*_spec.rb"
|
35
|
+
rcov.rcov = true
|
36
|
+
rcov.rspec_opts = "--format doc --color"
|
37
|
+
rcov.rcov_opts = "-x gem,spec"
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/aws-s3-cse.gemspec
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "aws-s3-cse"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Tom Nijmeijer"]
|
12
|
+
s.date = "2012-04-27"
|
13
|
+
s.description = "Provides a ruby implementation of the Client Side Encryption client for AWS-S3"
|
14
|
+
s.email = "tom@nijmeijer.org"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"aws-s3-cse.gemspec",
|
28
|
+
"lib/aws-s3-cse.rb",
|
29
|
+
"lib/aws/s3/crypter.rb",
|
30
|
+
"lib/aws/s3/encrypted_client.rb",
|
31
|
+
"lib/aws/s3/encrypted_config.rb",
|
32
|
+
"lib/aws/s3/encryption_errors.rb",
|
33
|
+
"lib/aws/s3/model.rb",
|
34
|
+
"spec/aws/s3/crypter_spec.rb",
|
35
|
+
"spec/aws/s3/encrypted_client_spec.rb",
|
36
|
+
"spec/aws/s3/model_spec.rb",
|
37
|
+
"spec/spec_helper.rb"
|
38
|
+
]
|
39
|
+
s.homepage = "http://github.com/tcnijmeijer/aws-s3-cse"
|
40
|
+
s.licenses = ["MIT"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = "1.8.21"
|
43
|
+
s.summary = "Provides a ruby implementation of the Client Side Encryption client for AWS-S3"
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
s.specification_version = 3
|
47
|
+
|
48
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
49
|
+
s.add_runtime_dependency(%q<aws-sdk>, [">= 0"])
|
50
|
+
s.add_runtime_dependency(%q<builder>, [">= 0"])
|
51
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
52
|
+
s.add_development_dependency(%q<rspec>, [">= 2.8.0"])
|
53
|
+
s.add_development_dependency(%q<rspec-mocks>, [">= 2.9.0"])
|
54
|
+
s.add_development_dependency(%q<rspec-spies>, [">= 2.1.0"])
|
55
|
+
s.add_development_dependency(%q<rcov>, ["= 0.9.9"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<aws-sdk>, [">= 0"])
|
58
|
+
s.add_dependency(%q<builder>, [">= 0"])
|
59
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
60
|
+
s.add_dependency(%q<rspec>, [">= 2.8.0"])
|
61
|
+
s.add_dependency(%q<rspec-mocks>, [">= 2.9.0"])
|
62
|
+
s.add_dependency(%q<rspec-spies>, [">= 2.1.0"])
|
63
|
+
s.add_dependency(%q<rcov>, ["= 0.9.9"])
|
64
|
+
end
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<aws-sdk>, [">= 0"])
|
67
|
+
s.add_dependency(%q<builder>, [">= 0"])
|
68
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
69
|
+
s.add_dependency(%q<rspec>, [">= 2.8.0"])
|
70
|
+
s.add_dependency(%q<rspec-mocks>, [">= 2.9.0"])
|
71
|
+
s.add_dependency(%q<rspec-spies>, [">= 2.1.0"])
|
72
|
+
s.add_dependency(%q<rcov>, ["= 0.9.9"])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
data/lib/aws-s3-cse.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
class S3
|
5
|
+
class Crypter
|
6
|
+
attr_reader :cipher
|
7
|
+
|
8
|
+
def initialize(cipher_name = 'aes-256-cbc')
|
9
|
+
@cipher = OpenSSL::Cipher::Cipher.new(cipher_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def encrypt_data(data)
|
13
|
+
cipher.encrypt
|
14
|
+
|
15
|
+
key = cipher.random_key
|
16
|
+
iv = cipher.random_iv
|
17
|
+
edata = cipher.update(data)
|
18
|
+
edata << cipher.final
|
19
|
+
|
20
|
+
[edata, key, iv]
|
21
|
+
end
|
22
|
+
|
23
|
+
def decrypt_data(edata, key, iv)
|
24
|
+
cipher.decrypt
|
25
|
+
|
26
|
+
cipher.key = key
|
27
|
+
cipher.iv = iv
|
28
|
+
|
29
|
+
data = cipher.update(edata)
|
30
|
+
data << cipher.final
|
31
|
+
data.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
class S3
|
5
|
+
class EncryptedClient < Client
|
6
|
+
attr_reader :private_encryption_key
|
7
|
+
attr_reader :public_encryption_key
|
8
|
+
|
9
|
+
HEADER_META = "x-amz-meta"
|
10
|
+
HEADER_KEY = "x-amz-key"
|
11
|
+
HEADER_IV = "x-amz-iv"
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
config = (options[:config] || AWS.config).with(options)
|
15
|
+
@private_encryption_key = config.s3_private_key
|
16
|
+
@public_encryption_key = config.s3_public_key
|
17
|
+
raise "missing public and/or private key" unless private_encryption_key && public_encryption_key
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def put_object(options = {})
|
22
|
+
if block_given?
|
23
|
+
buffer = StringIO.new
|
24
|
+
yield buffer
|
25
|
+
options[:data] = buffer.string
|
26
|
+
end
|
27
|
+
|
28
|
+
edata, key, iv = crypter.encrypt_data(options[:data])
|
29
|
+
key = @private_encryption_key.private_encrypt(key)
|
30
|
+
|
31
|
+
options[:metadata] ||= {}
|
32
|
+
options[:metadata][HEADER_KEY] = URI.encode(Base64.encode64(key))
|
33
|
+
options[:metadata][HEADER_IV] = URI.encode(Base64.encode64(iv))
|
34
|
+
options[:data] = edata
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_object(options = {})
|
39
|
+
response = super
|
40
|
+
|
41
|
+
ekey = response.http_response.headers["#{HEADER_META}-#{HEADER_KEY}"]
|
42
|
+
iv = response.http_response.headers["#{HEADER_META}-#{HEADER_IV}"]
|
43
|
+
|
44
|
+
if ekey && iv
|
45
|
+
ekey = Base64.decode64(URI.decode(ekey))
|
46
|
+
iv = Base64.decode64(URI.decode(iv))
|
47
|
+
edata = response.data
|
48
|
+
|
49
|
+
begin
|
50
|
+
key = @public_encryption_key.public_decrypt(ekey)
|
51
|
+
rescue Exception => e
|
52
|
+
raise Errors::DecryptionError.new(@public_encryption_key, ekey, e)
|
53
|
+
end
|
54
|
+
|
55
|
+
data = crypter.decrypt_data(edata, key, iv)
|
56
|
+
Core::MetaUtils.extend_method(response, :data) { data }
|
57
|
+
else
|
58
|
+
raise Errors::UnencryptedData.new(response.http_request, response.http_response)
|
59
|
+
end
|
60
|
+
|
61
|
+
response
|
62
|
+
end
|
63
|
+
|
64
|
+
def crypter=(crypter)
|
65
|
+
@crypter = crypter
|
66
|
+
end
|
67
|
+
|
68
|
+
def crypter
|
69
|
+
@crypter ||= Crypter.new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
AWS::Core::Configuration.module_eval do
|
2
|
+
|
3
|
+
add_option :s3_client_side_encryption, nil
|
4
|
+
add_option :s3_private_key, nil
|
5
|
+
add_option :s3_public_key, nil
|
6
|
+
|
7
|
+
needs = [
|
8
|
+
:signer,
|
9
|
+
:http_handler,
|
10
|
+
:s3_endpoint,
|
11
|
+
:s3_port,
|
12
|
+
:s3_private_key,
|
13
|
+
:s3_public_key,
|
14
|
+
:max_retries,
|
15
|
+
:stub_requests?,
|
16
|
+
:proxy_uri,
|
17
|
+
:use_ssl?,
|
18
|
+
:ssl_verify_peer?,
|
19
|
+
:ssl_ca_file,
|
20
|
+
:user_agent_prefix,
|
21
|
+
:logger,
|
22
|
+
:log_formatter,
|
23
|
+
:log_level,
|
24
|
+
]
|
25
|
+
|
26
|
+
create_block = lambda do |config|
|
27
|
+
AWS::S3::EncryptedClient.new(:config => config)
|
28
|
+
end
|
29
|
+
|
30
|
+
add_option_with_needs :s3_encrypted_client, needs, &create_block
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module AWS
|
2
|
+
class S3
|
3
|
+
module Errors
|
4
|
+
|
5
|
+
class UnencryptedData < AWS::Errors::Base
|
6
|
+
include AWS::Errors::ClientError
|
7
|
+
|
8
|
+
def code; "UnencryptedData"; end
|
9
|
+
|
10
|
+
def initialize(req, resp)
|
11
|
+
super(req, resp, "UnencryptedData")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class DecryptionError < StandardError
|
16
|
+
attr_reader :public_key
|
17
|
+
attr_reader :env_key
|
18
|
+
attr_reader :original
|
19
|
+
|
20
|
+
def initialize(public_key, env_key, original)
|
21
|
+
@public_key = public_key
|
22
|
+
@env_key = env_key
|
23
|
+
@original = original
|
24
|
+
super("Could not decrypt envelope key using the given public key")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/aws/s3/model.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module AWS
|
2
|
+
class S3
|
3
|
+
module Model
|
4
|
+
def client
|
5
|
+
if @config.s3_client_side_encryption
|
6
|
+
@config.send(:s3_encrypted_client)
|
7
|
+
else
|
8
|
+
@config.send(:s3_client)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Bucket
|
14
|
+
include S3::Model
|
15
|
+
end
|
16
|
+
|
17
|
+
class BucketCollection
|
18
|
+
include S3::Model
|
19
|
+
end
|
20
|
+
|
21
|
+
class MultipartUpload
|
22
|
+
include S3::Model
|
23
|
+
end
|
24
|
+
|
25
|
+
class MultipartUploadCollection
|
26
|
+
include S3::Model
|
27
|
+
end
|
28
|
+
|
29
|
+
class ObjectCollection
|
30
|
+
include S3::Model
|
31
|
+
end
|
32
|
+
|
33
|
+
class ObjectMetadata
|
34
|
+
include S3::Model
|
35
|
+
end
|
36
|
+
|
37
|
+
class ObjectUploadCollection
|
38
|
+
include S3::Model
|
39
|
+
end
|
40
|
+
|
41
|
+
class ObjectVersion
|
42
|
+
include S3::Model
|
43
|
+
end
|
44
|
+
|
45
|
+
class ObjectVersionCollection
|
46
|
+
include S3::Model
|
47
|
+
end
|
48
|
+
|
49
|
+
class PresignedPost
|
50
|
+
include S3::Model
|
51
|
+
end
|
52
|
+
|
53
|
+
class S3Object
|
54
|
+
include S3::Model
|
55
|
+
end
|
56
|
+
|
57
|
+
class Tree::ChildCollection
|
58
|
+
include S3::Model
|
59
|
+
end
|
60
|
+
|
61
|
+
module Tree::Parent
|
62
|
+
include S3::Model
|
63
|
+
end
|
64
|
+
|
65
|
+
class UploadedPart
|
66
|
+
include S3::Model
|
67
|
+
end
|
68
|
+
|
69
|
+
class UploadedPartCollection
|
70
|
+
include S3::Model
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
class S3
|
5
|
+
|
6
|
+
describe Crypter do
|
7
|
+
let(:crypter) { Crypter.new }
|
8
|
+
let(:cipher) { crypter.cipher }
|
9
|
+
|
10
|
+
context "#encrypt_data" do
|
11
|
+
before :all do
|
12
|
+
RSpec::Mocks.setup(cipher)
|
13
|
+
cipher.stub!(:random_key).and_return("random_key")
|
14
|
+
cipher.stub!(:random_iv).and_return("random_iv")
|
15
|
+
cipher.stub!(:update).and_return("edat")
|
16
|
+
cipher.stub!(:final).and_return("a")
|
17
|
+
@result = crypter.encrypt_data("data")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should generate a random key" do
|
21
|
+
cipher.should have_received(:random_key)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should generate a random iv" do
|
25
|
+
cipher.should have_received(:random_iv)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should encrypt the data" do
|
29
|
+
cipher.should have_received(:update).with("data")
|
30
|
+
cipher.should have_received(:final)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return the encrypted data, key and iv" do
|
34
|
+
@result[0].should == "edata"
|
35
|
+
@result[1].should == "random_key"
|
36
|
+
@result[2].should == "random_iv"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "#decrypt_data" do
|
41
|
+
before :all do
|
42
|
+
RSpec::Mocks.setup(cipher)
|
43
|
+
cipher.stub!(:key=)
|
44
|
+
cipher.stub!(:iv=)
|
45
|
+
cipher.stub!(:update).and_return("dat")
|
46
|
+
cipher.stub!(:final).and_return("a")
|
47
|
+
@result = crypter.decrypt_data("edata", "random_key", "random_iv")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should set the key" do
|
51
|
+
cipher.should have_received(:key=).with("random_key")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should set the iv" do
|
55
|
+
cipher.should have_received(:iv=).with("random_iv")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should decrypt the data" do
|
59
|
+
cipher.should have_received(:update).with("edata")
|
60
|
+
cipher.should have_received(:final)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return the unencrypted data" do
|
64
|
+
@result.should == "data"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
class S3
|
5
|
+
|
6
|
+
describe EncryptedClient do
|
7
|
+
describe "getting one" do
|
8
|
+
let(:credentials) do
|
9
|
+
{ :access_key_id => "access key id",
|
10
|
+
:secret_access_key => "secret access key" }
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) }
|
14
|
+
|
15
|
+
it "accepts the assymetric key via a hash" do
|
16
|
+
client = EncryptedClient.new(credentials.merge(:s3_private_key => rsa_key, :s3_public_key => rsa_key))
|
17
|
+
|
18
|
+
client.public_encryption_key.should == rsa_key
|
19
|
+
client.private_encryption_key.should == rsa_key
|
20
|
+
end
|
21
|
+
|
22
|
+
it "accepts the assymetric key as part of a config" do
|
23
|
+
config = AWS.config.with(credentials).with(:s3_private_key => rsa_key, :s3_public_key => rsa_key)
|
24
|
+
client = EncryptedClient.new(:config => config)
|
25
|
+
|
26
|
+
client.public_encryption_key.should == rsa_key
|
27
|
+
client.private_encryption_key.should == rsa_key
|
28
|
+
end
|
29
|
+
|
30
|
+
it "accepts the assymetric key as part of the default AWS.config" do
|
31
|
+
AWS.config(:s3_private_key => rsa_key, :s3_public_key => rsa_key)
|
32
|
+
client = EncryptedClient.new(credentials)
|
33
|
+
|
34
|
+
client.public_encryption_key.should == rsa_key
|
35
|
+
client.private_encryption_key.should == rsa_key
|
36
|
+
|
37
|
+
AWS.config(:s3_private_key => nil, :s3_public_key => nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "requires an assymetric key" do
|
41
|
+
lambda do
|
42
|
+
EncryptedClient.new(credentials)
|
43
|
+
end.should raise_error(/missing public and\/or private key/)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should be accessible from the configuration' do
|
47
|
+
config = AWS.config.with(:access_key_id => 'foo', :secret_access_key => 'bar',
|
48
|
+
:s3_private_key => rsa_key, :s3_public_key => rsa_key)
|
49
|
+
config.s3_encrypted_client.should be_a(S3::EncryptedClient)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context '#put_object' do
|
54
|
+
let :rsa_key do
|
55
|
+
key = OpenSSL::PKey::RSA.generate(1024)
|
56
|
+
key.stub(:private_encrypt).and_return("EKEY")
|
57
|
+
key
|
58
|
+
end
|
59
|
+
|
60
|
+
let :credentials do
|
61
|
+
{ :access_key_id => "access key id", :secret_access_key => "secret access key" }
|
62
|
+
end
|
63
|
+
|
64
|
+
let :opts do
|
65
|
+
{:bucket_name => 'foo', :key => 'some/key', :data => 'HELLO'}
|
66
|
+
end
|
67
|
+
|
68
|
+
let :crypter do
|
69
|
+
crypter = Crypter.new
|
70
|
+
crypter.stub(:encrypt_data).and_return(["HARRO", "KEY", "VECTOR"])
|
71
|
+
crypter
|
72
|
+
end
|
73
|
+
|
74
|
+
let :client do
|
75
|
+
client = EncryptedClient.new(credentials.merge(:s3_private_key => rsa_key, :s3_public_key => rsa_key))
|
76
|
+
client.crypter = crypter
|
77
|
+
client
|
78
|
+
end
|
79
|
+
|
80
|
+
before :all do
|
81
|
+
|
82
|
+
class Client
|
83
|
+
def super_calls
|
84
|
+
@super_calls ||= []
|
85
|
+
end
|
86
|
+
|
87
|
+
def put_object opts
|
88
|
+
self.super_calls << [:put_object, opts]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
client.put_object(:data => "HELLO", :key => "file", :bucket_name => "bucket")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should encrypt the data using a Crypter" do
|
96
|
+
crypter.should have_received(:encrypt_data).with("HELLO")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should encrypt the envelope key using the private encryption key" do
|
100
|
+
rsa_key.should have_received(:private_encrypt).with("KEY")
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should call super" do
|
104
|
+
client.super_calls.size.should == 1
|
105
|
+
client.super_calls.first.first.should == :put_object
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should call super with encrypted data" do
|
109
|
+
args = client.super_calls.first.last
|
110
|
+
args[:data].should == "HARRO"
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should call super with the encrypted envelope key as metadata" do
|
114
|
+
args = client.super_calls.first.last
|
115
|
+
args[:metadata]["x-amz-key"].should == URI.encode(Base64.encode64("EKEY"))
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should call super with the initialization vector as metadata" do
|
119
|
+
args = client.super_calls.first.last
|
120
|
+
args[:metadata]["x-amz-iv"].should == URI.encode(Base64.encode64("VECTOR"))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context '#get_object' do
|
125
|
+
let :rsa_key do
|
126
|
+
key = OpenSSL::PKey::RSA.generate(1024)
|
127
|
+
key.stub(:public_decrypt).and_return("KEY")
|
128
|
+
key
|
129
|
+
end
|
130
|
+
|
131
|
+
let :credentials do
|
132
|
+
{ :access_key_id => "access key id", :secret_access_key => "secret access key" }
|
133
|
+
end
|
134
|
+
|
135
|
+
let :crypter do
|
136
|
+
crypter = Crypter.new
|
137
|
+
crypter.stub(:decrypt_data).and_return("HELLO")
|
138
|
+
crypter
|
139
|
+
end
|
140
|
+
|
141
|
+
let :client do
|
142
|
+
client = EncryptedClient.new(credentials.merge(:s3_private_key => rsa_key, :s3_public_key => rsa_key))
|
143
|
+
client.crypter = crypter
|
144
|
+
client
|
145
|
+
end
|
146
|
+
|
147
|
+
before :all do
|
148
|
+
|
149
|
+
class Client
|
150
|
+
def super_calls
|
151
|
+
@super_calls ||= []
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_object opts
|
155
|
+
self.super_calls << [:get_object, opts]
|
156
|
+
|
157
|
+
if opts[:key] == "file"
|
158
|
+
resp = AWS::Core::Http::Response.new
|
159
|
+
resp.body = "HARRO"
|
160
|
+
resp.headers['x-amz-meta-x-amz-key'] = URI.encode(Base64.encode64('EKEY'))
|
161
|
+
resp.headers['x-amz-meta-x-amz-iv'] = URI.encode(Base64.encode64('VECTOR'))
|
162
|
+
resp = AWS::Core::Response.new(nil, resp)
|
163
|
+
Core::MetaUtils.extend_method(resp, :data) { resp.http_response.body }
|
164
|
+
else
|
165
|
+
resp = AWS::Core::Http::Response.new
|
166
|
+
resp.body = "HELLO"
|
167
|
+
resp = AWS::Core::Response.new(nil, resp)
|
168
|
+
Core::MetaUtils.extend_method(resp, :data) { resp.http_response.body }
|
169
|
+
end
|
170
|
+
|
171
|
+
resp
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
@response = client.get_object(:key => "file", :bucket_name => "bucket")
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should call super" do
|
179
|
+
client.super_calls.size.should == 1
|
180
|
+
client.super_calls.first.first.should == :get_object
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should decrypt the envelope key using the private encryption key" do
|
184
|
+
rsa_key.should have_received(:public_decrypt).with("EKEY")
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should decrypt the data using a Crypter" do
|
188
|
+
crypter.should have_received(:decrypt_data).with("HARRO", "KEY", "VECTOR")
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should return the unencrypted data" do
|
192
|
+
@response.data.should == "HELLO"
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should raise an error if the resource is unencrypted" do
|
196
|
+
lambda { client.get_object(:key => "other_file", :bucket_name => "bucket") }.
|
197
|
+
should raise_error(Errors::UnencryptedData)
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should handle an error when decryption the envelope key" do
|
201
|
+
rsa_key.stub(:public_decrypt).and_raise(OpenSSL::PKey::RSAError.new)
|
202
|
+
lambda { client.get_object(:key => "file", :bucket_name => "bucket") }.
|
203
|
+
should raise_error(Errors::DecryptionError)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
class S3
|
5
|
+
shared_examples_for "an S3 model" do |*args|
|
6
|
+
let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) }
|
7
|
+
|
8
|
+
let(:config) do
|
9
|
+
AWS.config(:access_key_id => "access key id", :secret_access_key => "secret access key",
|
10
|
+
:s3_private_key => rsa_key, :s3_public_key => rsa_key)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:instance) do
|
14
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
15
|
+
options[:config] = config
|
16
|
+
args << options
|
17
|
+
described_class.new(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should include the S3::Model module" do
|
21
|
+
described_class.should include(S3::Model)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns a normal S3::Client in client side encryption is off" do
|
25
|
+
AWS.config(:s3_client_side_encryption => false)
|
26
|
+
instance.client.class.should == S3::Client
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns a S3::EncryptedClient in client side encryption is on" do
|
30
|
+
AWS.config(:s3_client_side_encryption => true)
|
31
|
+
instance.client.class.should == S3::EncryptedClient
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Bucket do
|
36
|
+
it_should_behave_like "an S3 model", 'foo'
|
37
|
+
end
|
38
|
+
|
39
|
+
describe BucketCollection do
|
40
|
+
it_should_behave_like "an S3 model"
|
41
|
+
end
|
42
|
+
|
43
|
+
describe MultipartUpload do
|
44
|
+
it_should_behave_like "an S3 model", Object.new, "123"
|
45
|
+
end
|
46
|
+
|
47
|
+
describe MultipartUploadCollection do
|
48
|
+
it_should_behave_like "an S3 model", Object.new
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ObjectCollection do
|
52
|
+
it_should_behave_like "an S3 model", Object.new
|
53
|
+
end
|
54
|
+
|
55
|
+
describe ObjectMetadata do
|
56
|
+
it_should_behave_like "an S3 model", Object.new
|
57
|
+
end
|
58
|
+
|
59
|
+
describe ObjectUploadCollection do
|
60
|
+
it_should_behave_like "an S3 model", S3Object.new(Bucket.new('bu'), 'foo')
|
61
|
+
end
|
62
|
+
|
63
|
+
describe ObjectVersion do
|
64
|
+
it_should_behave_like "an S3 model", Object.new, 'version_id'
|
65
|
+
end
|
66
|
+
|
67
|
+
describe ObjectVersionCollection do
|
68
|
+
it_should_behave_like "an S3 model", Object.new
|
69
|
+
end
|
70
|
+
|
71
|
+
describe PresignedPost do
|
72
|
+
it_should_behave_like "an S3 model", Object.new
|
73
|
+
end
|
74
|
+
|
75
|
+
describe S3Object do
|
76
|
+
it_should_behave_like "an S3 model", Object.new, 'foo'
|
77
|
+
end
|
78
|
+
|
79
|
+
describe Tree::ChildCollection do
|
80
|
+
it_should_behave_like "an S3 model", Object.new, []
|
81
|
+
end
|
82
|
+
|
83
|
+
describe UploadedPart do
|
84
|
+
it_should_behave_like "an S3 model", Object.new, 1
|
85
|
+
end
|
86
|
+
|
87
|
+
describe UploadedPartCollection do
|
88
|
+
it_should_behave_like "an S3 model", Object.new
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aws-s3-cse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tom Nijmeijer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: builder
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: jeweler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.8.3
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.8.3
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.8.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.8.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec-mocks
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.9.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 2.9.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec-spies
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.1.0
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.1.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rcov
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.9.9
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - '='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.9.9
|
126
|
+
description: Provides a ruby implementation of the Client Side Encryption client for
|
127
|
+
AWS-S3
|
128
|
+
email: tom@nijmeijer.org
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files:
|
132
|
+
- LICENSE.txt
|
133
|
+
- README.rdoc
|
134
|
+
files:
|
135
|
+
- .document
|
136
|
+
- Gemfile
|
137
|
+
- Gemfile.lock
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.rdoc
|
140
|
+
- Rakefile
|
141
|
+
- VERSION
|
142
|
+
- aws-s3-cse.gemspec
|
143
|
+
- lib/aws-s3-cse.rb
|
144
|
+
- lib/aws/s3/crypter.rb
|
145
|
+
- lib/aws/s3/encrypted_client.rb
|
146
|
+
- lib/aws/s3/encrypted_config.rb
|
147
|
+
- lib/aws/s3/encryption_errors.rb
|
148
|
+
- lib/aws/s3/model.rb
|
149
|
+
- spec/aws/s3/crypter_spec.rb
|
150
|
+
- spec/aws/s3/encrypted_client_spec.rb
|
151
|
+
- spec/aws/s3/model_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
153
|
+
homepage: http://github.com/tcnijmeijer/aws-s3-cse
|
154
|
+
licenses:
|
155
|
+
- MIT
|
156
|
+
post_install_message:
|
157
|
+
rdoc_options: []
|
158
|
+
require_paths:
|
159
|
+
- lib
|
160
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
segments:
|
167
|
+
- 0
|
168
|
+
hash: -2608648413502752359
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
171
|
+
requirements:
|
172
|
+
- - ! '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
requirements: []
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 1.8.21
|
178
|
+
signing_key:
|
179
|
+
specification_version: 3
|
180
|
+
summary: Provides a ruby implementation of the Client Side Encryption client for AWS-S3
|
181
|
+
test_files: []
|