slosilo 0.4.1 → 1.0.0

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: 6918af427a277c2bb00cfee84403f527b2f9add6
4
- data.tar.gz: 55e3365667529c34e2999f5313211779ee38e032
3
+ metadata.gz: d243023c094f7eaec65752017e2550d57030d13a
4
+ data.tar.gz: f99b144884f5f3bf351fe4190e06c99f076b961f
5
5
  SHA512:
6
- metadata.gz: 7ccab3fc113dda1efe8a603a80f48309d4f776ade1f0ab45198ef6bc8d53736c54dc0e78e6cd0fbbbf0bcf15e4baf5da89884b8543303f5f7d10e3c1821ec141
7
- data.tar.gz: b313e1b365592f9ec2c60c5992bcd92305856894006e2440661494b90df9faae6a630ce3458b681a3a20f22df569c194472cbe89a8d3643f97638baedff739b8
6
+ metadata.gz: 3b2b0a71040bedaf6e15da8c6fdf6bca34fec490fe41f8834c72ba170ab22cbe7bd8d1d1b93dcfd24855acbf4ad634aa342cdb0374d562277ac9d4df493c6e7a
7
+ data.tar.gz: 8f4741e1f0f4a00cb0b466e6776445830efc11f66947cac7533fc2d65d0060a0116f7b0239c130ff3866e24e628323170bf684ee2285dc1020e21e2a36106663
data/lib/slosilo.rb CHANGED
@@ -3,8 +3,7 @@ require "slosilo/keystore"
3
3
  require "slosilo/symmetric"
4
4
  require "slosilo/attr_encrypted"
5
5
  require "slosilo/random"
6
- require "slosilo/rack/middleware"
7
- require "slosilo/http_request"
6
+ require "slosilo/errors"
8
7
 
9
8
  if defined? Sequel
10
9
  require 'slosilo/adapters/sequel_adapter'
@@ -6,15 +6,21 @@ module Slosilo
6
6
  def model
7
7
  @model ||= create_model
8
8
  end
9
+
10
+ def secure?
11
+ !Slosilo.encryption_key.nil?
12
+ end
9
13
 
10
14
  def create_model
11
15
  model = Sequel::Model(:slosilo_keystore)
12
16
  model.unrestrict_primary_key
13
- model.attr_encrypted :key
17
+ model.attr_encrypted :key if secure?
14
18
  model
15
19
  end
16
20
 
17
21
  def put_key id, value
22
+ fail Error::InsecureKeyStorage unless secure? || !value.private?
23
+
18
24
  attrs = { id: id, key: value.to_der }
19
25
  attrs[:fingerprint] = value.fingerprint if fingerprint_in_db?
20
26
  model.create attrs
@@ -0,0 +1,12 @@
1
+ module Slosilo
2
+ class Error < RuntimeError
3
+ # An error thrown when attempting to store a private key in an unecrypted
4
+ # storage. Set Slosilo.encryption_key to secure the storage or make sure
5
+ # to store just the public keys (using Key#public).
6
+ class InsecureKeyStorage < Error
7
+ def initialize msg = "can't store a private key in a plaintext storage"
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/slosilo/key.rb CHANGED
@@ -98,6 +98,16 @@ module Slosilo
98
98
  def hash
99
99
  to_der.hash
100
100
  end
101
+
102
+ # return a new key with just the public part of this
103
+ def public
104
+ Key.new(@key.public_key)
105
+ end
106
+
107
+ # checks if the keypair contains a private key
108
+ def private?
109
+ @key.private?
110
+ end
101
111
 
102
112
  private
103
113
 
@@ -7,7 +7,9 @@ module Slosilo
7
7
  end
8
8
 
9
9
  def put id, key
10
- adapter.put_key id.to_s, key
10
+ id = id.to_s
11
+ fail ArgumentError, "id can't be empty" if id.empty?
12
+ adapter.put_key id, key
11
13
  end
12
14
 
13
15
  def get opts
@@ -1,3 +1,3 @@
1
1
  module Slosilo
2
- VERSION = "0.4.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/slosilo.gemspec CHANGED
@@ -18,9 +18,10 @@ Gem::Specification.new do |gem|
18
18
  gem.required_ruby_version = '>= 1.9.3'
19
19
 
20
20
  gem.add_development_dependency 'rake'
21
- gem.add_development_dependency 'rspec'
22
- gem.add_development_dependency 'ci_reporter'
21
+ gem.add_development_dependency 'rspec', '~> 2.14'
22
+ gem.add_development_dependency 'ci_reporter', '~> 1.9'
23
23
  gem.add_development_dependency 'simplecov'
24
+ gem.add_development_dependency 'io-grab', '~> 0.0.1'
24
25
  gem.add_development_dependency 'sequel' # for sequel tests
25
26
  gem.add_development_dependency 'sqlite3' # for sequel tests
26
27
  end
data/spec/key_spec.rb CHANGED
@@ -7,6 +7,7 @@ describe Slosilo::Key do
7
7
  its(:to_der) { should == rsa.to_der }
8
8
  its(:to_s) { should == rsa.public_key.to_pem }
9
9
  its(:fingerprint) { should == key_fingerprint }
10
+ it { should be_private }
10
11
 
11
12
  context "with identical key" do
12
13
  let(:other) { Slosilo::Key.new rsa.to_der }
@@ -38,6 +39,16 @@ describe Slosilo::Key do
38
39
  end
39
40
  end
40
41
 
42
+ describe '#public' do
43
+ it "returns a key with just the public half" do
44
+ pkey = subject.public
45
+ expect(pkey).to be_a(Slosilo::Key)
46
+ expect(pkey).to_not be_private
47
+ expect(pkey.key).to_not be_private
48
+ expect(pkey.to_der).to eq(rsa.public_key.to_der)
49
+ end
50
+ end
51
+
41
52
  let(:plaintext) { 'quick brown fox jumped over the lazy dog' }
42
53
  describe '#encrypt' do
43
54
  it "generates a symmetric encryption key and encrypts the plaintext with the public key" do
@@ -10,6 +10,14 @@ describe Slosilo::Keystore do
10
10
  adapter['test'].to_der.should == rsa.to_der
11
11
  end
12
12
 
13
+ it "refuses to store a key with a nil id" do
14
+ expect { subject.put(nil, key) }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it "refuses to store a key with an empty id" do
18
+ expect { subject.put('', key) }.to raise_error(ArgumentError)
19
+ end
20
+
13
21
  it "passes the Slosilo key to the adapter" do
14
22
  adapter.should_receive(:put_key).with "test", key
15
23
  subject.put :test, key
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'sequel'
3
- require 'io_helper'
3
+ require 'io/grab'
4
4
 
5
5
  require 'slosilo/adapters/sequel_adapter'
6
6
 
@@ -56,65 +56,120 @@ describe Slosilo::Adapters::SequelAdapter do
56
56
  end
57
57
  end
58
58
 
59
- context do
59
+ shared_context "database" do
60
60
  let(:db) { Sequel.sqlite }
61
61
  before do
62
- Slosilo::encryption_key = Slosilo::Symmetric.new.random_key
63
62
  subject.unstub :create_model
64
- Sequel::Model.cache_anonymous_models = false
63
+ begin
64
+ Sequel::Model.cache_anonymous_models = false
65
+ rescue NoMethodError # sequel 4.0 moved the method
66
+ Sequel.cache_anonymous_models = false
67
+ end
65
68
  Sequel::Model.db = db
66
69
  end
70
+ end
67
71
 
68
- context "with old schema" do
69
- before do
70
- db.create_table :slosilo_keystore do
71
- String :id, primary_key: true
72
- bytea :key, null: false
73
- end
74
- subject.put_key 'test', key
75
- end
76
-
77
- context "after migration" do
78
- before { subject.migrate! }
72
+ shared_context "encryption key" do
73
+ before do
74
+ Slosilo.encryption_key = Slosilo::Symmetric.new.random_key
75
+ end
76
+ end
79
77
 
80
- it "supports look up by id" do
81
- subject.get_key("test").should == key
82
- end
78
+ context "with old schema" do
79
+ include_context "encryption key"
80
+ include_context "database"
83
81
 
84
- it "supports look up by fingerprint, without a warning" do
85
- STDERR.grab do
86
- subject.get_by_fingerprint(key.fingerprint).should == [key, 'test']
87
- end.should be_empty
88
- end
82
+ before do
83
+ db.create_table :slosilo_keystore do
84
+ String :id, primary_key: true
85
+ bytea :key, null: false
89
86
  end
87
+ subject.put_key 'test', key
88
+ end
89
+
90
+ context "after migration" do
91
+ before { subject.migrate! }
90
92
 
91
93
  it "supports look up by id" do
92
94
  subject.get_key("test").should == key
93
95
  end
94
96
 
95
- it "supports look up by fingerprint, but issues a warning" do
96
- STDERR.grab do
97
+ it "supports look up by fingerprint, without a warning" do
98
+ $stderr.grab do
97
99
  subject.get_by_fingerprint(key.fingerprint).should == [key, 'test']
98
- end.should_not be_empty
100
+ end.should be_empty
99
101
  end
100
102
  end
101
103
 
102
- context "with current schema" do
103
- before do
104
- Sequel.extension :migration
105
- require 'slosilo/adapters/sequel_adapter/migration.rb'
106
- Sequel::Migration::descendants.first.apply db, :up
107
- subject.put_key 'test', key
108
- end
104
+ it "supports look up by id" do
105
+ subject.get_key("test").should == key
106
+ end
109
107
 
108
+ it "supports look up by fingerprint, but issues a warning" do
109
+ $stderr.grab do
110
+ subject.get_by_fingerprint(key.fingerprint).should == [key, 'test']
111
+ end.should_not be_empty
112
+ end
113
+ end
110
114
 
111
- it "supports look up by id" do
112
- subject.get_key("test").should == key
113
- end
115
+ shared_context "current schema" do
116
+ include_context "database"
117
+ before do
118
+ Sequel.extension :migration
119
+ require 'slosilo/adapters/sequel_adapter/migration.rb'
120
+ Sequel::Migration.descendants.first.apply db, :up
121
+ end
122
+ end
114
123
 
115
- it "supports look up by fingerprint" do
116
- subject.get_by_fingerprint(key.fingerprint).should == [key, 'test']
117
- end
124
+ context "with current schema" do
125
+ include_context "encryption key"
126
+ include_context "current schema"
127
+ before do
128
+ subject.put_key 'test', key
129
+ end
130
+
131
+ it "supports look up by id" do
132
+ subject.get_key("test").should == key
133
+ end
134
+
135
+ it "supports look up by fingerprint" do
136
+ subject.get_by_fingerprint(key.fingerprint).should == [key, 'test']
137
+ end
138
+ end
139
+
140
+ context "with an encryption key", :wip do
141
+ include_context "encryption key"
142
+ include_context "current schema"
143
+
144
+ it { should be_secure }
145
+
146
+ it "saves the keys in encrypted form" do
147
+ subject.put_key 'test', key
148
+
149
+ expect(db[:slosilo_keystore][id: 'test'][:key]).to_not eq(key.to_der)
150
+ expect(subject.get_key 'test').to eq(key)
151
+ end
152
+ end
153
+
154
+ context "without an encryption key", :wip do
155
+ before do
156
+ Slosilo.encryption_key = nil
157
+ end
158
+
159
+ include_context "current schema"
160
+
161
+ it { should_not be_secure }
162
+
163
+ it "refuses to store a private key" do
164
+ expect { subject.put_key 'test', key }.to raise_error(Slosilo::Error::InsecureKeyStorage)
165
+ end
166
+
167
+ it "saves the keys in plaintext form" do
168
+ pkey = key.public
169
+ subject.put_key 'test', pkey
170
+
171
+ expect(db[:slosilo_keystore][id: 'test'][:key]).to eq(pkey.to_der)
172
+ expect(subject.get_key 'test').to eq(pkey)
118
173
  end
119
174
  end
120
175
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require "simplecov"
2
2
  SimpleCov.start
3
3
 
4
+ RSpec.configure do |c|
5
+ c.treat_symbols_as_metadata_keys_with_true_values = true
6
+ end
7
+
4
8
  require 'slosilo'
5
9
 
6
10
  shared_context "with mock adapter" do
metadata CHANGED
@@ -1,97 +1,111 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slosilo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafał Rzepecki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-24 00:00:00.000000000 Z
11
+ date: 2014-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '2.14'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '2.14'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: ci_reporter
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '1.9'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '1.9'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: simplecov
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: io-grab
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: sequel
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - '>='
87
+ - - ">="
74
88
  - !ruby/object:Gem::Version
75
89
  version: '0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - '>='
94
+ - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: sqlite3
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - '>='
101
+ - - ">="
88
102
  - !ruby/object:Gem::Version
89
103
  version: '0'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - '>='
108
+ - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
111
  description: This gem provides an easy way of storing and retrieving encryption keys
@@ -102,8 +116,8 @@ executables: []
102
116
  extensions: []
103
117
  extra_rdoc_files: []
104
118
  files:
105
- - .gitignore
106
- - .kateproject
119
+ - ".gitignore"
120
+ - ".kateproject"
107
121
  - Gemfile
108
122
  - LICENSE
109
123
  - README.md
@@ -116,22 +130,17 @@ files:
116
130
  - lib/slosilo/adapters/sequel_adapter.rb
117
131
  - lib/slosilo/adapters/sequel_adapter/migration.rb
118
132
  - lib/slosilo/attr_encrypted.rb
119
- - lib/slosilo/http_request.rb
133
+ - lib/slosilo/errors.rb
120
134
  - lib/slosilo/key.rb
121
135
  - lib/slosilo/keystore.rb
122
- - lib/slosilo/rack/middleware.rb
123
136
  - lib/slosilo/random.rb
124
137
  - lib/slosilo/symmetric.rb
125
138
  - lib/slosilo/version.rb
126
139
  - lib/tasks/slosilo.rake
127
140
  - slosilo.gemspec
128
141
  - spec/file_adapter_spec.rb
129
- - spec/http_request_spec.rb
130
- - spec/http_stack_spec.rb
131
- - spec/io_helper.rb
132
142
  - spec/key_spec.rb
133
143
  - spec/keystore_spec.rb
134
- - spec/rack_middleware_spec.rb
135
144
  - spec/random_spec.rb
136
145
  - spec/sequel_adapter_spec.rb
137
146
  - spec/slosilo_spec.rb
@@ -147,28 +156,24 @@ require_paths:
147
156
  - lib
148
157
  required_ruby_version: !ruby/object:Gem::Requirement
149
158
  requirements:
150
- - - '>='
159
+ - - ">="
151
160
  - !ruby/object:Gem::Version
152
161
  version: 1.9.3
153
162
  required_rubygems_version: !ruby/object:Gem::Requirement
154
163
  requirements:
155
- - - '>='
164
+ - - ">="
156
165
  - !ruby/object:Gem::Version
157
166
  version: '0'
158
167
  requirements: []
159
168
  rubyforge_project:
160
- rubygems_version: 2.0.3
169
+ rubygems_version: 2.2.2
161
170
  signing_key:
162
171
  specification_version: 4
163
172
  summary: Store SSL keys in a database
164
173
  test_files:
165
174
  - spec/file_adapter_spec.rb
166
- - spec/http_request_spec.rb
167
- - spec/http_stack_spec.rb
168
- - spec/io_helper.rb
169
175
  - spec/key_spec.rb
170
176
  - spec/keystore_spec.rb
171
- - spec/rack_middleware_spec.rb
172
177
  - spec/random_spec.rb
173
178
  - spec/sequel_adapter_spec.rb
174
179
  - spec/slosilo_spec.rb
@@ -1,59 +0,0 @@
1
- module Slosilo
2
- # A mixin module which simplifies generating signed and encrypted requests.
3
- # It's designed to be mixed into a standard Net::HTTPRequest object
4
- # and ensures the request is signed and optionally encrypted before execution.
5
- # Requests prepared this way will be recognized by Slosilo::Rack::Middleware.
6
- #
7
- # As an example, you can use it with RestClient like so:
8
- # RestClient.add_before_execution_proc do |req, params|
9
- # require 'slosilo'
10
- # req.extend Slosilo::HTTPRequest
11
- # req.keyname = :somekey
12
- # end
13
- #
14
- # The request won't be encrypted unless you set the destination keyname.
15
-
16
- module HTTPRequest
17
- # Encrypt the request with key named @keyname from Slosilo::Keystore.
18
- # If calling this manually, make sure to encrypt before signing.
19
- def encrypt!
20
- return unless @keyname
21
- return unless body && !body.empty?
22
- self.body, key = Slosilo[@keyname].encrypt body
23
- self['X-Slosilo-Key'] = Base64::urlsafe_encode64 key
24
- end
25
-
26
- # Sign the request with :own key from Slosilo::Keystore.
27
- # If calling this manually, make sure to encrypt before signing.
28
- def sign!
29
- token = Slosilo[:own].signed_token signed_data
30
- self['Timestamp'] = token["timestamp"]
31
- self['X-Slosilo-Signature'] = token["signature"]
32
- end
33
-
34
- # Build the data hash to sign.
35
- def signed_data
36
- data = { "path" => path, "body" => [body].pack('m0') }
37
- if key = self['X-Slosilo-Key']
38
- data["key"] = key
39
- end
40
- if authz = self['Authorization']
41
- data["authorization"] = authz
42
- end
43
- data
44
- end
45
-
46
- # Encrypt, sign and execute the request.
47
- def exec *a
48
- # we need to hook here because the body might be set
49
- # in several ways and here it's hopefully finalized
50
- encrypt!
51
- sign!
52
- super *a
53
- end
54
-
55
- # Name of the key used to encrypt the request.
56
- # Use it to establish the identity of the receiver.
57
- attr_accessor :keyname
58
- end
59
- end
@@ -1,123 +0,0 @@
1
- module Slosilo
2
- module Rack
3
- # Con perform verification of request signature and decryption of request body.
4
- #
5
- # Signature verification and body decryption are enabled with constructor switches and are
6
- # therefore performed (or not) for all requests.
7
- #
8
- # When signature verification is performed, the following elements are included in the
9
- # signature string:
10
- #
11
- # 1. Request path and query string
12
- # 2. base64 encoded request body
13
- # 3. Request timestamp from HTTP_TIMESTAMP
14
- # 4. Body encryption key from HTTP_X_SLOSILO_KEY (if present)
15
- #
16
- # When body decryption is performed, an encryption key for the message body is encrypted
17
- # with this service's public key and placed in HTTP_X_SLOSILO_KEY. This middleware
18
- # decryps the key using our :own private key, and then decrypts the body using the decrypted key.
19
- class Middleware
20
- class EncryptionError < SecurityError
21
- end
22
- class SignatureError < SecurityError
23
- end
24
-
25
- def initialize app, opts = {}
26
- @app = app
27
- @encryption_required = opts[:encryption_required] || false
28
- @signature_required = opts[:signature_required] || false
29
- end
30
-
31
- def call env
32
- @env = env
33
- @body = env['rack.input'].read rescue ""
34
-
35
- begin
36
- verify
37
- decrypt
38
- rescue EncryptionError
39
- return error 403, $!.message
40
- rescue SignatureError
41
- return error 401, $!.message
42
- end
43
-
44
- @app.call env
45
- end
46
-
47
- private
48
- def verify
49
- if signature
50
- raise SignatureError, "Bad signature" unless Slosilo.token_valid?(token)
51
- else
52
- raise SignatureError, "Signature required" if signature_required?
53
- end
54
- end
55
-
56
- attr_reader :env
57
-
58
- def token
59
- return nil unless signature
60
- t = { "data" => { "path" => path, "body" => [body].pack('m0') }, "timestamp" => timestamp, "signature" => signature }
61
- t["data"]["key"] = encoded_key if encoded_key
62
- t['data']['authorization'] = env['HTTP_AUTHORIZATION'] if env['HTTP_AUTHORIZATION']
63
- t
64
- end
65
-
66
- def path
67
- env['SCRIPT_NAME'] + env['PATH_INFO'] + query_string
68
- end
69
-
70
- def query_string
71
- if env['QUERY_STRING'].empty?
72
- ''
73
- else
74
- '?' + env['QUERY_STRING']
75
- end
76
- end
77
-
78
- attr_reader :body
79
-
80
- def timestamp
81
- env['HTTP_TIMESTAMP']
82
- end
83
-
84
- def signature
85
- env['HTTP_X_SLOSILO_SIGNATURE']
86
- end
87
-
88
- def encoded_key
89
- env['HTTP_X_SLOSILO_KEY']
90
- end
91
-
92
- def key
93
- if encoded_key
94
- Base64::urlsafe_decode64(encoded_key)
95
- else
96
- raise EncryptionError, "Encryption required" if encryption_required?
97
- end
98
- end
99
-
100
- def decrypt
101
- return unless key
102
- plaintext = Slosilo[:own].decrypt body, key
103
- env['rack.input'] = StringIO.new plaintext
104
- rescue EncryptionError
105
- raise unless body.empty? || body.nil?
106
- rescue Exception => e
107
- raise EncryptionError, "Bad encryption", e.backtrace
108
- end
109
-
110
- def error status, message
111
- [status, { 'Content-Type' => 'text/plain', 'Content-Length' => message.length.to_s }, [message] ]
112
- end
113
-
114
- def encryption_required?
115
- @encryption_required
116
- end
117
-
118
- def signature_required?
119
- @signature_required
120
- end
121
- end
122
- end
123
- end
@@ -1,107 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Slosilo::HTTPRequest do
4
- let(:keyname) { :bacon }
5
- let(:encrypt) { subject.encrypt! }
6
- subject { Hash.new }
7
- before do
8
- subject.extend Slosilo::HTTPRequest
9
- subject.keyname = keyname
10
- end
11
-
12
- describe "#sign!" do
13
- let(:own_key) { double "own key" }
14
- before { Slosilo.stub(:[]).with(:own).and_return own_key }
15
-
16
- let(:signed_data) { "this is the truest truth" }
17
- before { subject.stub signed_data: signed_data }
18
- let(:timestamp) { "long time ago" }
19
- let(:signature) { "seal of approval" }
20
- let(:token) { { "data" => signed_data, "timestamp" => timestamp, "signature" => signature } }
21
-
22
- it "makes a token out of the data to sign and inserts headers" do
23
- own_key.stub(:signed_token).with(signed_data).and_return token
24
- subject.should_receive(:[]=).with 'Timestamp', timestamp
25
- subject.should_receive(:[]=).with 'X-Slosilo-Signature', signature
26
- subject.sign!
27
- end
28
- end
29
-
30
- describe "#signed_data" do
31
- before { subject.stub path: :path, body: 'body' }
32
- context "when X-Slosilo-Key not present" do
33
- its(:signed_data) { should == { "path" => :path, "body" => "Ym9keQ==" } }
34
- end
35
-
36
- context "when X-Slosilo-Key is present" do
37
- before { subject.merge! 'X-Slosilo-Key' => :key }
38
- its(:signed_data) { should == { "path" => :path, "body" => "Ym9keQ==", "key" => :key } }
39
- end
40
- end
41
-
42
- describe "#encrypt!" do
43
- context "when key not set" do
44
- before { subject.keyname = nil }
45
- it "does nothing" do
46
- subject.should_not_receive(:body=)
47
- encrypt
48
- end
49
- end
50
-
51
- context "when requested key does not exist" do
52
- before { Slosilo.stub(:[]).and_return nil }
53
- it "raises error" do
54
- expect{ encrypt }.to raise_error
55
- end
56
- end
57
-
58
- context "when the key exists" do
59
- let(:key) { double "key" }
60
- context "when the body is not empty" do
61
- let(:plaintext) { "Keep your solutions close, and your problems closer." }
62
- let(:ciphertext) { "And, when you want something, all the universe conspires in helping you to achieve it." }
63
- let(:skey) { "make me sound like a fool instead" }
64
- before do
65
- subject.stub body: plaintext
66
- key.stub(:encrypt).with(plaintext).and_return([ciphertext, skey])
67
- Slosilo.stub(:[]).with(keyname).and_return key
68
- end
69
-
70
- it "encrypts the message body and adds the X-Slosilo-Key header" do
71
- subject.should_receive(:body=).with ciphertext
72
- subject.should_receive(:[]=).with 'X-Slosilo-Key', Base64::urlsafe_encode64(skey)
73
- encrypt
74
- end
75
- end
76
-
77
- context "when the body is empty" do
78
- before { subject.stub body: "" }
79
- it "doesn't set the key header" do
80
- subject.should_not_receive(:[]=).with 'X-Slosilo-Key'
81
- encrypt
82
- end
83
- end
84
- end
85
- end
86
-
87
- describe "#exec" do
88
- class Subject
89
- def exec *a
90
- "ok, got it"
91
- end
92
-
93
- def initialize keyname
94
- extend Slosilo::HTTPRequest
95
- self.keyname = keyname
96
- end
97
- end
98
-
99
- subject { Subject.new keyname }
100
-
101
- it "encrypts, then signs and delegates to the superclass" do
102
- subject.should_receive(:encrypt!).once.ordered
103
- subject.should_receive(:sign!).once.ordered
104
- subject.exec(:foo).should == "ok, got it"
105
- end
106
- end
107
- end
@@ -1,44 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe "http request stack" do
4
- include_context "with example key"
5
- include_context "with mock adapter"
6
- before { Slosilo[:own] = key }
7
-
8
- class MockRequest < Hash
9
- def exec *a
10
- end
11
-
12
- def [] name
13
- name = name.sub(/^HTTP_/,'').gsub('_', '-').split(/(\W)/).map(&:capitalize).join
14
- result = super name
15
- end
16
-
17
- def initialize
18
- extend Slosilo::HTTPRequest
19
- self['Authorization'] = "Simon says it's fine"
20
- end
21
- end
22
-
23
- subject { MockRequest.new }
24
- let(:path) { '/some/path' }
25
-
26
- context "with authorization header" do
27
- it "works" do
28
- mw = Slosilo::Rack::Middleware.new lambda{|_|:ok}, signature_required: true
29
- subject.stub path: path, body: ''
30
- mw.stub path: path
31
- subject.send :exec
32
- mw.call(subject).should == :ok
33
- end
34
-
35
- it "detects tampering" do
36
- mw = Slosilo::Rack::Middleware.new lambda{|_|:ok}, signature_required: true
37
- subject.stub path: path, body: ''
38
- mw.stub path: path
39
- subject.send :exec
40
- subject['Authorization'] = "Simon changed his mind"
41
- mw.call(subject).should_not == :ok
42
- end
43
- end
44
- end
data/spec/io_helper.rb DELETED
@@ -1,18 +0,0 @@
1
- class IO
2
- def grab &block
3
- @grabbed_output = ""
4
- class << self
5
- def write arg
6
- @grabbed_output += arg
7
- end
8
- end
9
-
10
- begin
11
- yield
12
- ensure
13
- singleton_class.send :remove_method, :write
14
- end
15
-
16
- @grabbed_output
17
- end
18
- end
@@ -1,109 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Slosilo::Rack::Middleware do
4
- include_context "with example key"
5
- mock_own_key
6
-
7
- let(:app) { double "app" }
8
- subject { Slosilo::Rack::Middleware.new app }
9
-
10
- describe '#path' do
11
- context "when QUERY_STRING is empty" do
12
- let(:env) { { 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/bar', 'QUERY_STRING' => '' } }
13
- before { subject.stub env: env }
14
- its(:path) { should == '/foo/bar' }
15
- end
16
- context "when QUERY_STRING is not" do
17
- let(:env) { { 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/bar', 'QUERY_STRING' => 'baz' } }
18
- before { subject.stub env: env }
19
- its(:path) { should == '/foo/bar?baz' }
20
- end
21
- end
22
-
23
- describe '#call' do
24
- let(:call) { subject.call(env) }
25
- let(:path) { "/this/is/the/path" }
26
- before { subject.stub path: path }
27
- context "when no X-Slosilo-Key is given" do
28
- let(:env) { {} }
29
- let(:result) { double "result" }
30
- it "passes the env verbatim" do
31
- app.should_receive(:call).with(env).and_return(result)
32
- call.should == result
33
- end
34
-
35
- context "and X-Slosilo-Signature is given" do
36
- let(:body) { "the body" }
37
- let(:timestamp) { "long time ago" }
38
- let(:signature) { "in blood" }
39
- let(:env) { {'rack.input' => StringIO.new(body), 'HTTP_TIMESTAMP' => timestamp, 'HTTP_X_SLOSILO_SIGNATURE' => signature } }
40
- let(:token) { { "data" => { "path" => path, "body" => "dGhlIGJvZHk=" }, "timestamp" => timestamp, "signature" => signature } }
41
- context "when the signature is valid" do
42
- before { Slosilo.stub(:token_valid?).with(token).and_return true }
43
- it "passes the env verbatim" do
44
- app.should_receive(:call).with(env).and_return(result)
45
- call.should == result
46
- end
47
- end
48
- context "when the signature is invalid" do
49
- before { Slosilo.stub(:token_valid?).with(token).and_return false }
50
- it "returns 401" do
51
- call[0].should == 401
52
- end
53
- end
54
- end
55
-
56
- context "but encryption is required" do
57
- subject { Slosilo::Rack::Middleware.new app, encryption_required: true }
58
- context "and the body is not empty" do
59
- let(:env) { {'rack.input' => StringIO.new('foo') } }
60
- it "returns 403" do
61
- status, headers, body = call
62
- status.should == 403
63
- end
64
- end
65
- context "but the body is empty" do
66
- subject { Slosilo::Rack::Middleware.new app, encryption_required: true, signature_required: false }
67
- let(:body) { "" }
68
- let(:timestamp) { "long time ago" }
69
- let(:env) { {'rack.input' => StringIO.new(body) } }
70
- it "passes the env verbatim" do
71
- app.should_receive(:call).with(env).and_return(result)
72
- call.should == result
73
- end
74
- end
75
- end
76
- end
77
-
78
- context "when no X-Slosilo-Signature is given" do
79
- context "but signature is required" do
80
- let(:env) {{}}
81
- subject { Slosilo::Rack::Middleware.new app, signature_required: true }
82
- it "returns 401" do
83
- status, headers, body = call
84
- status.should == 401
85
- end
86
- end
87
- end
88
-
89
- let(:plaintext) { "If you were taught that elves caused rain, every time it rained, you'd see the proof of elves." }
90
- let(:skey) { "Eiho7xIoFj-Qwqc0swcQQJzJyM1sSv_b6VdRIoHCPRUwemB0v5MNyOirU_5dQ_bNzlmSlo8HDvfAnMgapwpIBH__uDUV_3nCkzrzQVV3-bSp6owJnqebeSQxJMoVMKEWqqek3ZCBPo0OB63A8mkYGu9955gDEDOnlxLkETGb3SmDQIVJtiMmAkUWN0fh9z1M9Ycw9FfworaHKQXRLw6z6Rl-Yoe_TDaiKVlGIYjQKpCz8h_I5lRdrhPJaP53d0yQuKMK3PBHMzE77IikZyQ3VZdoqI9XqzUJF27KehxJ_BCx0oAcPaxG6I7WWe3Xb7K7MhE4HgzqVZACDLhYfm_0XA==" }
91
- let(:ciphertext) { "0\xDE\xE1\xBA=\x06+K\xE0\xCAD\xC6\xE3 d\xC7kx\x90\r\ni\xDCXmS!EP\xAB\xEF\xAA\x13{\x85f\x8FU,\xB3zO\x1F\x85\f\x0E\xAE\xF8\x10`\x1C\x94\xAB@\xFA\xBC\xC0/\x1F\xA6nX\xFF-m\xF4\xC3f\xBB\xCA\x05\xC82\x18l\xC3\xF0v\x96\v\x8F\xFC\xB2\xC7wX;\xF6v\xDCX:\xCC\xF8\xD7\x99\xC8\x1A\xBA\x9F\xDB\xE7\x0F\xF2\xC9f\aaGs\xEFc" }
92
- context "when X-Slosilo-Key is given" do
93
- context "when the key decrypts cleanly" do
94
- let(:env) { {'HTTP_X_SLOSILO_KEY' => skey, 'rack.input' => StringIO.new(ciphertext) } }
95
- it "passes the decrypted contents" do
96
- app.should_receive(:call).with(rack_environment_with_input(plaintext)).and_return(:result)
97
- call.should == :result
98
- end
99
- end
100
- context "when the key is invalid" do
101
- let(:env) { {'HTTP_X_SLOSILO_KEY' => "broken #{skey}", 'rack.input' => StringIO.new(ciphertext) } }
102
- it "returns 403 status" do
103
- status, headers, body = call
104
- status.should == 403
105
- end
106
- end
107
- end
108
- end
109
- end