aws-s3-cse 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -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)
@@ -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.
@@ -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
+
@@ -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
@@ -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
+
@@ -0,0 +1,7 @@
1
+ require 'aws/s3'
2
+
3
+ require 'aws/s3/model'
4
+ require 'aws/s3/encryption_errors'
5
+ require 'aws/s3/encrypted_config'
6
+ require 'aws/s3/encrypted_client'
7
+ require 'aws/s3/crypter'
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ $: << File.join(File.dirname(File.dirname(__FILE__)), "lib")
2
+
3
+ require 'rspec'
4
+ require 'rspec/mocks'
5
+ require 'rspec-spies'
6
+ require 'aws-s3-cse'
7
+
8
+ AWS.eager_autoload!
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: []