slosilo 0.1.2

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.
@@ -0,0 +1,22 @@
1
+ namespace :slosilo do
2
+ desc "Dump a public key"
3
+ task :dump, [:name] => :environment do |t, args|
4
+ args.with_defaults(:name => :own)
5
+ puts Slosilo[args[:name]]
6
+ end
7
+
8
+ desc "Enroll a key"
9
+ task :enroll, [:name] => :environment do |t, args|
10
+ key = Slosilo::Key.new STDIN.read
11
+ Slosilo[args[:name]] = key
12
+ puts key
13
+ end
14
+
15
+ desc "Generate a key pair"
16
+ task :generate, [:name] => :environment do |t, args|
17
+ args.with_defaults(:name => :own)
18
+ key = Slosilo::Key.new
19
+ Slosilo[args[:name]] = key
20
+ puts key
21
+ end
22
+ end
data/slosilo.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/slosilo/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Rafa\305\202 Rzepecki"]
6
+ gem.email = ["divided.mind@gmail.com"]
7
+ gem.description = %q{This gem provides an easy way of storing and retrieving encryption keys in the database.}
8
+ gem.summary = %q{Store SSL keys in a database}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "slosilo"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Slosilo::VERSION
17
+ gem.required_ruby_version = '~> 1.9.3'
18
+
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'rspec'
21
+ gem.add_development_dependency 'ci_reporter'
22
+ gem.add_development_dependency 'simplecov'
23
+ gem.add_development_dependency 'sequel' # for sequel tests
24
+ gem.add_development_dependency 'sqlite3' # for sequel tests
25
+ end
@@ -0,0 +1,107 @@
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
@@ -0,0 +1,44 @@
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/key_spec.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slosilo::Key do
4
+ include_context "with example key"
5
+
6
+ subject { key }
7
+ its(:to_der) { should == rsa.to_der }
8
+ its(:to_s) { should == rsa.public_key.to_pem }
9
+
10
+ let(:plaintext) { 'quick brown fox jumped over the lazy dog' }
11
+ describe '#encrypt' do
12
+ it "generates a symmetric encryption key and encrypts the plaintext with the public key" do
13
+ ctxt, skey = subject.encrypt plaintext
14
+ pskey = rsa.private_decrypt skey
15
+ Slosilo::Symmetric.new.decrypt(ctxt, key: pskey).should == plaintext
16
+ end
17
+ end
18
+
19
+ describe '#decrypt' do
20
+ let(:ciphertext) { "\x8B\xE6\xEC\x8C\xAB\xA4\xC0\x8EF\"\x0F\xD5Yh\xA1\aq\x00\xF5\xAC\xAB\v\a\xEC\xF6G\xA6\e\x14N\xFF\x11\x98\xDA\x19\xB5\x8994_:\xA0\xF8\x06l\xDC\x9B\xB1\xE8z\x83\xCC\x9A\x02E\x02tOhu\x92F]h" }
21
+ let(:skey) { "\x15\xFF\xD4>\x9C\xF4\x0F\xB6\x04C\x18\xC1\x96\xC3\xE0T\xA3\xF5\xE8\x17\xA6\xE0\x86~rrw\xC3\xDF\x11)$\x9B\r@\x0E\xE4Zv\rw-\xFC\x1C\x84\x17\xBD\xDB\x12u\xCD\r\xFEo\xD5\xC8[\x8FEA\\\xA9\xA2F#\x8BH -\xFA\xF7\xA9\xEBf\x97\xAAT}\x8E*\xC0r\x944p\xD0\x9A\xE7\xBD\a;O\f\xEF\xE4B.y\xFA\xB4e@O\xBB\x15>y\xC9\t=\x01\xE1J\xF3X\xA9\x9E3\x04^H\x1F\xFF\x19C\x93ve)@\xF7\t_\xCF\xDE\xF1\xB7]\x83lL\xB8%A\x93p{\xE9Y\xBAu\xCE\x99T\xDC\xDF\xE7\x0FD%\xB9AXb\x1CW\x94$P\xBB\xE1g\xDEE\t\xC4\x92\x9E\xFEt\xDF\xD0\xEA\x03\xC4\x12\xA9\x02~u\xF4\x92 ;\xA0\xCE.\b+)\x05\xEDo\xA5cF\xF8\x12\xD7F\x97\xE44\xBF\xF1@\xA5\xC5\xA8\xE5\a\xE8ra<\x04\xB5\xA2\f\t\xF7T\x97\e\xF5(^\xAB\xA5%84\x10\xD1\x13e<\xCA/\xBF}%\xF6\xB1%\xB2" }
22
+
23
+ it "decrypts the symmetric key and then uses it to decrypt the ciphertext" do
24
+ subject.decrypt(ciphertext, skey).should == plaintext
25
+ end
26
+ end
27
+
28
+ describe '#initialize' do
29
+ context "when no argument given" do
30
+ subject { Slosilo::Key.new }
31
+ let (:rsa) { double "key" }
32
+ it "generates a new key pair" do
33
+ OpenSSL::PKey::RSA.should_receive(:new).with(2048).and_return(rsa)
34
+ subject.key.should == rsa
35
+ end
36
+ end
37
+ context "when given an armored key" do
38
+ subject { Slosilo::Key.new rsa.to_der }
39
+ its(:to_der) { should == rsa.to_der }
40
+ end
41
+ context "when given a key instance" do
42
+ subject { Slosilo::Key.new rsa }
43
+ its(:to_der) { should == rsa.to_der }
44
+ end
45
+ context "when given something else" do
46
+ subject { Slosilo::Key.new "foo" }
47
+ it "fails early" do
48
+ expect { subject }.to raise_error
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#sign" do
54
+ context "when given a hash" do
55
+ it "converts to a sorted array and signs that" do
56
+ key.should_receive(:sign_string).with '[["a",3],["b",42]]'
57
+ key.sign b: 42, a: 3
58
+ end
59
+ end
60
+ context "when given an array" do
61
+ it "signs a JSON representation instead" do
62
+ key.should_receive(:sign_string).with '[2,[42,2]]'
63
+ key.sign [2, [42, 2]]
64
+ end
65
+ end
66
+ context "when given a string" do
67
+ let(:expected_signature) { "d[\xA4\x00\x02\xC5\x17\xF5P\x1AD\x91\xF9\xC1\x00P\x0EG\x14,IN\xDE\x17\xE1\xA2a\xCC\xABR\x99'\xB0A\xF5~\x93M/\x95-B\xB1\xB6\x92!\x1E\xEA\x9C\v\xC2O\xA8\x91\x1C\xF9\x11\x92a\xBFxm-\x93\x9C\xBBoM\x92%\xA9\xD06$\xC1\xBC.`\xF8\x03J\x16\xE1\xB0c\xDD\xBF\xB0\xAA\xD7\xD4\xF4\xFC\e*\xAB\x13A%-\xD3\t\xA5R\x18\x01let6\xC8\xE9\"\x7F6O\xC7p\x82\xAB\x04J(IY\xAA]b\xA4'\xD6\x873`\xAB\x13\x95g\x9C\x17\xCAB\xF8\xB9\x85B:^\xC5XY^\x03\xEA\xB6V\x17b2\xCA\xF5\xD6\xD4\xD2\xE3u\x11\xECQ\x0Fb\x14\xE2\x04\xE1<a\xC5\x01eW-\x15\x01X\x81K\x1A\xE5A\vVj\xBF\xFC\xFE#\xD5\x93y\x16\xDC\xB4\x8C\xF0\x02Y\xA8\x87i\x01qC\xA7#\xE8\f\xA5\xF0c\xDEJ\xB0\xDB BJ\x87\xA4\xB0\x92\x80\x03\x95\xEE\xE9\xB8K\xC0\xE3JbE-\xD4\xCBP\\\x13S\"\eZ\xE1\x93\xFDa pinch of salt" }
68
+ it "signs it" do
69
+ key.stub salt: 'a pinch of salt'
70
+ key.sign("this sentence is not this sentence").should == expected_signature
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#signed_token" do
76
+ let(:time) { Time.new(2012,1,1,1,1,1,0) }
77
+ let(:data) { { "foo" => :bar } }
78
+ let(:token_to_sign) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC" } }
79
+ let(:signature) { "signature" }
80
+ let(:salt) { 'a pinch of salt' }
81
+ let(:expected_signature) { Base64::urlsafe_encode64 "\xB0\xCE{\x9FP\xEDV\x9C\xE7b\x8B[\xFAil\x87^\x96\x17Z\x97\x1D\xC2?B\x96\x9C\x8Ep-\xDF_\x8F\xC21\xD9^\xBC\n\x16\x04\x8DJ\xF6\xAF-\xEC\xAD\x03\xF9\xEE:\xDF\xB5\x8F\xF9\xF6\x81m\xAB\x9C\xAB1\x1E\x837\x8C\xFB\xA8P\xA8<\xEA\x1Dx\xCEd\xED\x84f\xA7\xB5t`\x96\xCC\x0F\xA9t\x8B\x9Fo\xBF\x92K\xFA\xFD\xC5?\x8F\xC68t\xBC\x9F\xDE\n$\xCA\xD2\x8F\x96\x0EtX2\x8Cl\x1E\x8Aa\r\x8D\xCAi\x86\x1A\xBD\x1D\xF7\xBC\x8561j\x91YlO\xFA(\x98\x10iq\xCC\xAF\x9BV\xC6\v\xBC\x10Xm\xCD\xFE\xAD=\xAA\x95,\xB4\xF7\xE8W\xB8\x83;\x81\x88\xE6\x01\xBA\xA5F\x91\x17\f\xCE\x80\x8E\v\x83\x9D<\x0E\x83\xF6\x8D\x03\xC0\xE8A\xD7\x90i\x1D\x030VA\x906D\x10\xA0\xDE\x12\xEF\x06M\xD8\x8B\xA9W\xC8\x9DTc\x8AJ\xA4\xC0\xD3!\xFA\x14\x89\xD1p\xB4J7\xA5\x04\xC2l\xDC8<\x04Y\xD8\xA4\xFB[\x89\xB1\xEC\xDA\xB8\xD7\xEA\x03Ja pinch of salt" }
82
+ let(:expected_token) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC", "signature" => expected_signature } }
83
+ before do
84
+ key.stub salt: salt
85
+ Time.stub new: time
86
+ end
87
+ subject { key.signed_token data }
88
+ it { should == expected_token }
89
+ end
90
+
91
+ describe "#token_valid?" do
92
+ let(:data) { { "foo" => :bar } }
93
+ let(:signature) { Base64::urlsafe_encode64 "\xB0\xCE{\x9FP\xEDV\x9C\xE7b\x8B[\xFAil\x87^\x96\x17Z\x97\x1D\xC2?B\x96\x9C\x8Ep-\xDF_\x8F\xC21\xD9^\xBC\n\x16\x04\x8DJ\xF6\xAF-\xEC\xAD\x03\xF9\xEE:\xDF\xB5\x8F\xF9\xF6\x81m\xAB\x9C\xAB1\x1E\x837\x8C\xFB\xA8P\xA8<\xEA\x1Dx\xCEd\xED\x84f\xA7\xB5t`\x96\xCC\x0F\xA9t\x8B\x9Fo\xBF\x92K\xFA\xFD\xC5?\x8F\xC68t\xBC\x9F\xDE\n$\xCA\xD2\x8F\x96\x0EtX2\x8Cl\x1E\x8Aa\r\x8D\xCAi\x86\x1A\xBD\x1D\xF7\xBC\x8561j\x91YlO\xFA(\x98\x10iq\xCC\xAF\x9BV\xC6\v\xBC\x10Xm\xCD\xFE\xAD=\xAA\x95,\xB4\xF7\xE8W\xB8\x83;\x81\x88\xE6\x01\xBA\xA5F\x91\x17\f\xCE\x80\x8E\v\x83\x9D<\x0E\x83\xF6\x8D\x03\xC0\xE8A\xD7\x90i\x1D\x030VA\x906D\x10\xA0\xDE\x12\xEF\x06M\xD8\x8B\xA9W\xC8\x9DTc\x8AJ\xA4\xC0\xD3!\xFA\x14\x89\xD1p\xB4J7\xA5\x04\xC2l\xDC8<\x04Y\xD8\xA4\xFB[\x89\xB1\xEC\xDA\xB8\xD7\xEA\x03Ja pinch of salt" }
94
+ let(:token) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC", "signature" => signature } }
95
+ before { Time.stub now: Time.new(2012,1,1,1,2,1,0) }
96
+ subject { key.token_valid? token }
97
+ it { should be_true }
98
+ context "when token is 1 hour old" do
99
+ before { Time.stub now: Time.new(2012,1,1,2,1,1,0) }
100
+ it { should be_false }
101
+ context "when timestamp in the token is changed accordingly" do
102
+ let(:token) { { "data" => data, "timestamp" => "2012-01-01 02:00:01 UTC", "signature" => signature } }
103
+ it { should be_false }
104
+ end
105
+ end
106
+ context "when the data is changed" do
107
+ let(:data) { { "foo" => :baz } }
108
+ it { should be_false }
109
+ end
110
+ context "when RSA decrypt raises an error" do
111
+ before { OpenSSL::PKey::RSA.any_instance.should_receive(:public_decrypt).and_raise(OpenSSL::PKey::RSAError) }
112
+ it { should be_false }
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slosilo::Keystore do
4
+ include_context "with example key"
5
+ include_context "with mock adapter"
6
+
7
+ describe '#put' do
8
+ it "handles Slosilo::Keys too" do
9
+ subject.put(:test, key)
10
+ adapter['test'].should == rsa.to_der
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,109 @@
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
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slosilo::Random do
4
+ subject { Slosilo::Random }
5
+ let(:other_salt) { Slosilo::Random::salt }
6
+
7
+ its('salt.length') { should == 32 }
8
+ its(:salt) { should_not == other_salt }
9
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ require 'slosilo/adapters/sequel_adapter'
4
+
5
+ describe Slosilo::Adapters::SequelAdapter do
6
+ let(:model) { double "model" }
7
+ before { subject.stub create_model: model }
8
+
9
+ describe "#get_key" do
10
+ context "when given key does not exist" do
11
+ before { model.stub :[] => nil }
12
+ it "returns nil" do
13
+ subject.get_key(:whatever).should_not be
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "#put_key" do
19
+ let(:id) { "id" }
20
+ let(:key) { "key" }
21
+ it "creates the key" do
22
+ model.should_receive(:create).with id: id, key: key
23
+ subject.put_key id, key
24
+ end
25
+ end
26
+
27
+ let(:adapter) { subject }
28
+ describe "#each" do
29
+ let(:one) { double("one", id: :one, key: :onek) }
30
+ let(:two) { double("two", id: :two, key: :twok) }
31
+ before { model.stub(:each).and_yield(one).and_yield(two) }
32
+
33
+ it "iterates over each key" do
34
+ results = []
35
+ adapter.each { |id,k| results << { id => k } }
36
+ results.should == [ { one: :onek}, {two: :twok } ]
37
+ end
38
+ end
39
+
40
+ describe '#model' do
41
+ let(:db) { Sequel.sqlite }
42
+ before do
43
+ Slosilo::encryption_key = Slosilo::Symmetric.new.random_key
44
+ subject.unstub :create_model
45
+ require 'sequel'
46
+ Sequel::Model.db = db
47
+ Sequel.extension :migration
48
+ require 'slosilo/adapters/sequel_adapter/migration'
49
+ Sequel::Migration::descendants.first.apply db, :up
50
+ end
51
+
52
+ let(:key) { 'fake key' }
53
+ let(:id) { 'some id' }
54
+ it "transforms (encrypts) the key" do
55
+ subject.model.create id: id, key: key
56
+ db[:slosilo_keystore][id: id][:key].should_not == key
57
+ subject.model[id].key.should == key
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slosilo do
4
+ include_context "with mock adapter"
5
+ let(:key) { OpenSSL::PKey::RSA.new 512 }
6
+ before { adapter['test'] = key.to_der }
7
+
8
+ describe '[]' do
9
+ it "returns a Slosilo::Key" do
10
+ Slosilo[:test].should be_instance_of Slosilo::Key
11
+ end
12
+
13
+ context "when the requested key does not exist" do
14
+ it "returns nil instead of creating a new key" do
15
+ Slosilo[:aether].should_not be
16
+ end
17
+ end
18
+ end
19
+
20
+ describe '.sign' do
21
+ let(:own_key) { double "own key" }
22
+ before { Slosilo.stub(:[]).with(:own).and_return own_key }
23
+ let (:argument) { double "thing to sign" }
24
+ it "fetches the own key and signs using that" do
25
+ own_key.should_receive(:sign).with(argument)
26
+ Slosilo.sign argument
27
+ end
28
+ end
29
+
30
+ describe '.token_valid?' do
31
+ before { adapter['test'].stub token_valid?: false }
32
+ let(:key2) { double "key 2", token_valid?: false }
33
+ let(:key3) { double "key 3", token_valid?: false }
34
+ before do
35
+ adapter[:key2] = key2
36
+ adapter[:key3] = key3
37
+ end
38
+
39
+ let(:token) { double "token" }
40
+ subject { Slosilo.token_valid? token }
41
+
42
+ context "when no key validates the token" do
43
+ before { Slosilo::Key.stub new: (double "key", token_valid?: false) }
44
+ it { should be_false }
45
+ end
46
+
47
+ context "when a key validates the token" do
48
+ let(:valid_key) { double token_valid?: true }
49
+ let(:invalid_key) { double token_valid?: true }
50
+ before do
51
+ Slosilo::Key.stub new: invalid_key
52
+ Slosilo::Key.stub(:new).with(key2).and_return(valid_key)
53
+ end
54
+
55
+ it { should be_true }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ require "simplecov"
2
+ SimpleCov.start
3
+
4
+ require 'slosilo'
5
+
6
+ shared_context "with mock adapter" do
7
+ require 'slosilo/adapters/mock_adapter'
8
+
9
+ let(:adapter) { Slosilo::Adapters::MockAdapter.new }
10
+ before { Slosilo::adapter = adapter }
11
+ end
12
+
13
+ shared_context "with example key" do
14
+ let(:rsa) { OpenSSL::PKey::RSA.new """
15
+ -----BEGIN RSA PRIVATE KEY-----
16
+ MIIEpQIBAAKCAQEAtTG/SQhW9QawP+GL6EZ5Al9gscCr7HiRO7MuQqFkaXIJD6+3
17
+ prdHRrb0qqjNlGFgDBGAuswZ2AYqhBt7eekup+/vIpI5n04b0w+is3WwZAFco4uP
18
+ ojDeM0aY65Ar3Zgra2vWUJXRwBumroZjVBVoLJSgVfwIhwU6ORbS2oJflbtqxpuS
19
+ zkPDqS6RwEzI/DHuHTOI26fe+vfuDqGOuSR6iVI16lfvTbWwccpDwU0W9vSlyjjD
20
+ LIw0MnoKL3DHyzO66s+oNNRleMvjghQtJk/xg1kRuHReJ5/ygt2zyzdKSLeqU+T+
21
+ TCWw/F65jrFElftexiS+g+lZC467VLCaMe1fJQIDAQABAoIBAQCiNWzXRr4CEQDL
22
+ z3Deeehu9U+tEZ1Xzv/FgD0TrUQlGc9+2YIBn+YRKkySUxfnk9zWMP0bPQiN2cdK
23
+ CQhbNSNteGCOhHVNZjGGm2K+YceNX6K9Tn1BZ5okMTlI+QIsGMQWIK316omh/58S
24
+ coCNj7R45H09PKmtpkJfRU1yDHDhqypjPDpb9/7U5mt3g2BdXYi+1hilfonHoDrC
25
+ yy3eRdf7Tlij9O3UeM+Z7pZrKATcvpDkYbNWizDITvKMYy6Ss+ajM5v7lt6QN5LP
26
+ MHjwX8Ilrxkxl0jeopr4f94tR7rNDZbLC457j8gns7cUeODtF7pPZqlrlk4KOq8Q
27
+ DvEMt2ZpAoGBAOLNUiO1SwRo75Y8ukuMVQev8O8WuzEEGINoM1lQiYlbUw3HmVp3
28
+ iUvv58ANmKSzTXpOEZ1L8pZHSp435FrzD2WZmCAoXhNdfAXtmZA7Y46iE6BF4qrr
29
+ UegtLPhVgwpO74Y+4w2YwfDknzCOhWE4sxCbukuSvxz2pz1Vm31eFB6jAoGBAMyF
30
+ VxfYq9WhmLNsHqR+qfhb1EC5FfpSq23z/o4ryiKqCaWHimVtOO7DL7x2SK3mVNNZ
31
+ X8b4+vnJpAQ3nOxeg8fpmBaLAWYRna2AN/CYVIMKYusawhsGAlZZTu2mtJKLiOPS
32
+ 8/z5dK55xJWlG5JalUB+n/4vd3WmXiT/XJj3qU+XAoGBALyHzLXeKCPcTvzmMj5G
33
+ wxAG0xMMJEMUkoP5hGXEKvBBOAMGXpXzM/Ap1s2w/6g5XDhE2SOWVGtTi9WFxI9N
34
+ 6Qid6vUgWUNjvIr4/WQF2jZgyEu8jDVkM8v6cZ1lB+7zuuwvLnLI/r6ObT3h20H7
35
+ 7e3qZawYqkEbT94OYZiPMc5dAoGAHmIQtjIyFOKU1NLTGozWo1bBCXx1j2KIpSUC
36
+ RAytUsj/9d9U6Ax50L6ecNkBoxP8tgko+V4zqrgR7a51WYgQ+7nwJikwZAFp80SB
37
+ CvUWWQFKALNQ8sLJxhouZ4/Ec6DXDUFhjcthUio00iZdGjjqw1IMYq6aiJfWlJh7
38
+ IR5pwLECgYEAyjlguks/3cjrHRF+yjonxT4tLuBI/n3TAQUPqmtkJtcwZSAJas/1
39
+ c/twlAJ7F6n3ZroF3lgPxMJRRHZl4Z4dJsDatIxVShf3nSGg9Mi5C25obxahbv5/
40
+ Dg1ikwi8GUF4HPZe9DyhXgDhg19wM/qcpjX8bSypsUWHWP+FanhjdWU=
41
+ -----END RSA PRIVATE KEY-----
42
+ """ }
43
+ let (:key) { Slosilo::Key.new rsa.to_der }
44
+
45
+ def self.mock_own_key
46
+ before { Slosilo.stub(:[]).with(:own).and_return key }
47
+ end
48
+ end
49
+
50
+ class RackEnvironmentInputMatcher
51
+ def initialize expected
52
+ @expected = expected
53
+ end
54
+
55
+ def == env
56
+ env['rack.input'].read.should == @expected
57
+ end
58
+ end
59
+
60
+ def rack_environment_with_input expected
61
+ RackEnvironmentInputMatcher.new expected
62
+ end