slosilo 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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