slosilo 0.0.0 → 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.
- data/.gitignore +0 -2
- data/LICENSE +2 -2
- data/README.md +8 -128
- data/lib/slosilo/adapters/abstract_adapter.rb +0 -4
- data/lib/slosilo/adapters/mock_adapter.rb +1 -14
- data/lib/slosilo/adapters/sequel_adapter/migration.rb +2 -5
- data/lib/slosilo/adapters/sequel_adapter.rb +5 -67
- data/lib/slosilo/attr_encrypted.rb +7 -33
- data/lib/slosilo/http_request.rb +59 -0
- data/lib/slosilo/key.rb +6 -129
- data/lib/slosilo/keystore.rb +12 -40
- data/lib/slosilo/rack/middleware.rb +123 -0
- data/lib/slosilo/symmetric.rb +17 -47
- data/lib/slosilo/version.rb +2 -21
- data/lib/slosilo.rb +2 -2
- data/lib/tasks/slosilo.rake +0 -10
- data/slosilo.gemspec +6 -19
- data/spec/http_request_spec.rb +107 -0
- data/spec/http_stack_spec.rb +44 -0
- data/spec/key_spec.rb +32 -175
- data/spec/keystore_spec.rb +2 -15
- data/spec/rack_middleware_spec.rb +109 -0
- data/spec/random_spec.rb +2 -12
- data/spec/sequel_adapter_spec.rb +22 -133
- data/spec/slosilo_spec.rb +12 -78
- data/spec/spec_helper.rb +15 -37
- data/spec/symmetric_spec.rb +26 -69
- metadata +51 -104
- checksums.yaml +0 -7
- data/.github/CODEOWNERS +0 -10
- data/.gitleaks.toml +0 -221
- data/.kateproject +0 -4
- data/CHANGELOG.md +0 -50
- data/CONTRIBUTING.md +0 -16
- data/Jenkinsfile +0 -132
- data/SECURITY.md +0 -42
- data/dev/Dockerfile.dev +0 -7
- data/dev/docker-compose.yml +0 -8
- data/lib/slosilo/adapters/file_adapter.rb +0 -42
- data/lib/slosilo/adapters/memory_adapter.rb +0 -31
- data/lib/slosilo/errors.rb +0 -15
- data/lib/slosilo/jwt.rb +0 -122
- data/publish.sh +0 -5
- data/secrets.yml +0 -1
- data/spec/encrypted_attributes_spec.rb +0 -114
- data/spec/file_adapter_spec.rb +0 -81
- data/spec/jwt_spec.rb +0 -102
- data/test.sh +0 -8
data/spec/key_spec.rb
CHANGED
@@ -1,129 +1,51 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
require 'active_support'
|
4
|
-
require 'active_support/core_ext/numeric/time'
|
5
|
-
|
6
3
|
describe Slosilo::Key do
|
7
4
|
include_context "with example key"
|
8
5
|
|
9
6
|
subject { key }
|
10
|
-
|
11
|
-
|
12
|
-
subject { super().to_der }
|
13
|
-
it { is_expected.to eq(rsa.to_der) }
|
14
|
-
end
|
15
|
-
|
16
|
-
describe '#to_s' do
|
17
|
-
subject { super().to_s }
|
18
|
-
it { is_expected.to eq(rsa.public_key.to_pem) }
|
19
|
-
end
|
20
|
-
|
21
|
-
describe '#fingerprint' do
|
22
|
-
subject { super().fingerprint }
|
23
|
-
it { is_expected.to eq(key_fingerprint) }
|
24
|
-
end
|
25
|
-
it { is_expected.to be_private }
|
26
|
-
|
27
|
-
context "with identical key" do
|
28
|
-
let(:other) { Slosilo::Key.new rsa.to_der }
|
29
|
-
it "is equal" do
|
30
|
-
expect(subject).to eq(other)
|
31
|
-
end
|
32
|
-
|
33
|
-
it "is eql?" do
|
34
|
-
expect(subject.eql?(other)).to be_truthy
|
35
|
-
end
|
36
|
-
|
37
|
-
it "has equal hash" do
|
38
|
-
expect(subject.hash).to eq(other.hash)
|
39
|
-
end
|
40
|
-
end
|
7
|
+
its(:to_der) { should == rsa.to_der }
|
8
|
+
its(:to_s) { should == rsa.public_key.to_pem }
|
41
9
|
|
42
|
-
context "with a different key" do
|
43
|
-
let(:other) { Slosilo::Key.new another_rsa }
|
44
|
-
it "is not equal" do
|
45
|
-
expect(subject).not_to eq(other)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "is not eql?" do
|
49
|
-
expect(subject.eql?(other)).not_to be_truthy
|
50
|
-
end
|
51
|
-
|
52
|
-
it "has different hash" do
|
53
|
-
expect(subject.hash).not_to eq(other.hash)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe '#public' do
|
58
|
-
it "returns a key with just the public half" do
|
59
|
-
pkey = subject.public
|
60
|
-
expect(pkey).to be_a(Slosilo::Key)
|
61
|
-
expect(pkey).to_not be_private
|
62
|
-
expect(pkey.key).to_not be_private
|
63
|
-
expect(pkey.to_der).to eq(rsa.public_key.to_der)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
10
|
let(:plaintext) { 'quick brown fox jumped over the lazy dog' }
|
68
11
|
describe '#encrypt' do
|
69
12
|
it "generates a symmetric encryption key and encrypts the plaintext with the public key" do
|
70
13
|
ctxt, skey = subject.encrypt plaintext
|
71
14
|
pskey = rsa.private_decrypt skey
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
describe '#encrypt_message' do
|
77
|
-
it "#encrypts a message and then returns the result as a single string" do
|
78
|
-
expect(subject).to receive(:encrypt).with(plaintext).and_return ['fake ciphertext', 'fake key']
|
79
|
-
expect(subject.encrypt_message(plaintext)).to eq('fake keyfake ciphertext')
|
15
|
+
Slosilo::Symmetric.new.decrypt(ctxt, key: pskey).should == plaintext
|
80
16
|
end
|
81
17
|
end
|
82
18
|
|
83
|
-
let(:ciphertext){ "G\xAD^\x17\x11\xBBQ9-b\x14\xF6\x92#Q0x\xF4\xAD\x1A\x92\xC3VZW\x89\x8E\x8Fg\x93\x05B\xF8\xD6O\xCFGCTp\b~\x916\xA3\x9AN\x8D\x961\x1F\xA3mSf&\xAD\xA77/]z\xA89\x01\xA7\xA9\x92\f".force_encoding('ASCII-8BIT') }
|
84
|
-
let(:skey){ "\x82\x93\xFAA\xA6wQA\xE1\xB5\xA6b\x8C.\xCF#I\x86I\x83u\x99\rTA\xEF\xC4\x91\xC5)-\xEBQ\xB1\xC0\xC6\xFF\x90L\xFE\x1E\x15\x81\x12\x16\xDD:A\xC5d\xE1B\xD2f@\xB8o\xB7+N\xB7\n\x92\xDC\x9E\xE3\x83\xB8>h\a\xC7\xCC\xCF\xD0t\x06\x8B\xA8\xBF\xEFe\xA4{\x88\f\xDD\roF\xEB.\xDA\xBF\x9D_0>\xF03c'\x1F!)*-\x19\x97\xAC\xD2\x1F(,6h\a\x93\xDB\x8E\x97\xF9\x1A\x11\x84\x11t\xD9\xB2\x85\xB0\x12\x7F\x03\x00O\x8F\xBE#\xFFb\xA5w\xF3g\xCF\xB4\xF2\xB7\xDBiA=\xA8\xFD1\xEC\xBF\xD7\x8E\xB6W>\x03\xACNBa\xBF\xFD\xC6\xB32\x8C\xE2\xF1\x87\x9C\xAE6\xD1\x12\vkl\xBB\xA0\xED\x9A\xEE6\xF2\xD9\xB4LL\xE2h/u_\xA1i=\x11x\x8DGha\x8EG\b+\x84[\x87\x8E\x01\x0E\xA5\xB0\x9F\xE9vSl\x18\xF3\xEA\xF4NH\xA8\xF1\x81\xBB\x98\x01\xE8p]\x18\x11f\xA3K\xA87c\xBB\x13X~K\xA2".force_encoding('ASCII-8BIT') }
|
85
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
|
+
|
86
23
|
it "decrypts the symmetric key and then uses it to decrypt the ciphertext" do
|
87
|
-
|
24
|
+
subject.decrypt(ciphertext, skey).should == plaintext
|
88
25
|
end
|
89
26
|
end
|
90
27
|
|
91
|
-
describe '#decrypt_message' do
|
92
|
-
it "splits the message into key and rest, then #decrypts it" do
|
93
|
-
expect(subject).to receive(:decrypt).with(ciphertext, skey).and_return plaintext
|
94
|
-
expect(subject.decrypt_message(skey + ciphertext)).to eq(plaintext)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
28
|
describe '#initialize' do
|
99
29
|
context "when no argument given" do
|
100
30
|
subject { Slosilo::Key.new }
|
101
31
|
let (:rsa) { double "key" }
|
102
32
|
it "generates a new key pair" do
|
103
|
-
|
104
|
-
|
33
|
+
OpenSSL::PKey::RSA.should_receive(:new).with(2048).and_return(rsa)
|
34
|
+
subject.key.should == rsa
|
105
35
|
end
|
106
36
|
end
|
107
37
|
context "when given an armored key" do
|
108
38
|
subject { Slosilo::Key.new rsa.to_der }
|
109
|
-
|
110
|
-
describe '#to_der' do
|
111
|
-
subject { super().to_der }
|
112
|
-
it { is_expected.to eq(rsa.to_der) }
|
113
|
-
end
|
39
|
+
its(:to_der) { should == rsa.to_der }
|
114
40
|
end
|
115
41
|
context "when given a key instance" do
|
116
42
|
subject { Slosilo::Key.new rsa }
|
117
|
-
|
118
|
-
describe '#to_der' do
|
119
|
-
subject { super().to_der }
|
120
|
-
it { is_expected.to eq(rsa.to_der) }
|
121
|
-
end
|
43
|
+
its(:to_der) { should == rsa.to_der }
|
122
44
|
end
|
123
45
|
context "when given something else" do
|
124
46
|
subject { Slosilo::Key.new "foo" }
|
125
47
|
it "fails early" do
|
126
|
-
expect { subject }.to raise_error
|
48
|
+
expect { subject }.to raise_error
|
127
49
|
end
|
128
50
|
end
|
129
51
|
end
|
@@ -131,34 +53,21 @@ describe Slosilo::Key do
|
|
131
53
|
describe "#sign" do
|
132
54
|
context "when given a hash" do
|
133
55
|
it "converts to a sorted array and signs that" do
|
134
|
-
|
56
|
+
key.should_receive(:sign_string).with '[["a",3],["b",42]]'
|
135
57
|
key.sign b: 42, a: 3
|
136
58
|
end
|
137
59
|
end
|
138
60
|
context "when given an array" do
|
139
61
|
it "signs a JSON representation instead" do
|
140
|
-
|
62
|
+
key.should_receive(:sign_string).with '[2,[42,2]]'
|
141
63
|
key.sign [2, [42, 2]]
|
142
64
|
end
|
143
65
|
end
|
144
66
|
context "when given a string" do
|
145
|
-
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"
|
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" }
|
146
68
|
it "signs it" do
|
147
|
-
|
148
|
-
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
context "when given a Hash containing non-ascii characters" do
|
153
|
-
let(:unicode){ "adèle.dupuis" }
|
154
|
-
let(:encoded){
|
155
|
-
unicode.dup.tap{|s| s.force_encoding Encoding::ASCII_8BIT}
|
156
|
-
}
|
157
|
-
let(:hash){ {"data" => unicode} }
|
158
|
-
|
159
|
-
it "converts the value to raw bytes before signing it" do
|
160
|
-
expect(key).to receive(:sign_string).with("[[\"data\",\"#{encoded}\"]]").and_call_original
|
161
|
-
key.sign hash
|
69
|
+
key.stub salt: 'a pinch of salt'
|
70
|
+
key.sign("this sentence is not this sentence").should == expected_signature
|
162
71
|
end
|
163
72
|
end
|
164
73
|
end
|
@@ -169,90 +78,38 @@ describe Slosilo::Key do
|
|
169
78
|
let(:token_to_sign) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC" } }
|
170
79
|
let(:signature) { "signature" }
|
171
80
|
let(:salt) { 'a pinch of salt' }
|
172
|
-
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"
|
173
|
-
let(:expected_token) {
|
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 } }
|
174
83
|
before do
|
175
|
-
|
176
|
-
|
84
|
+
key.stub salt: salt
|
85
|
+
Time.stub new: time
|
177
86
|
end
|
178
87
|
subject { key.signed_token data }
|
179
|
-
it {
|
180
|
-
end
|
181
|
-
|
182
|
-
describe "#validate_jwt" do
|
183
|
-
let(:token) do
|
184
|
-
instance_double Slosilo::JWT,
|
185
|
-
header: { 'alg' => 'conjur.org/slosilo/v2' },
|
186
|
-
claims: { 'iat' => Time.now.to_i },
|
187
|
-
string_to_sign: double("string to sign"),
|
188
|
-
signature: double("signature")
|
189
|
-
end
|
190
|
-
|
191
|
-
before do
|
192
|
-
allow(key).to receive(:verify_signature).with(token.string_to_sign, token.signature) { true }
|
193
|
-
end
|
194
|
-
|
195
|
-
it "verifies the signature" do
|
196
|
-
expect { key.validate_jwt token }.not_to raise_error
|
197
|
-
end
|
198
|
-
|
199
|
-
it "rejects unknown algorithm" do
|
200
|
-
token.header['alg'] = 'HS256' # we're not supporting standard algorithms
|
201
|
-
expect { key.validate_jwt token }.to raise_error /algorithm/
|
202
|
-
end
|
203
|
-
|
204
|
-
it "rejects bad signature" do
|
205
|
-
allow(key).to receive(:verify_signature).with(token.string_to_sign, token.signature) { false }
|
206
|
-
expect { key.validate_jwt token }.to raise_error /signature/
|
207
|
-
end
|
208
|
-
|
209
|
-
it "rejects expired token" do
|
210
|
-
token.claims['exp'] = 1.hour.ago.to_i
|
211
|
-
expect { key.validate_jwt token }.to raise_error /expired/
|
212
|
-
end
|
213
|
-
|
214
|
-
it "accepts unexpired token with implicit expiration" do
|
215
|
-
token.claims['iat'] = 5.minutes.ago
|
216
|
-
expect { key.validate_jwt token }.to_not raise_error
|
217
|
-
end
|
218
|
-
|
219
|
-
it "rejects token expired with implicit expiration" do
|
220
|
-
token.claims['iat'] = 10.minutes.ago.to_i
|
221
|
-
expect { key.validate_jwt token }.to raise_error /expired/
|
222
|
-
end
|
88
|
+
it { should == expected_token }
|
223
89
|
end
|
224
|
-
|
90
|
+
|
225
91
|
describe "#token_valid?" do
|
226
92
|
let(:data) { { "foo" => :bar } }
|
227
|
-
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"
|
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" }
|
228
94
|
let(:token) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC", "signature" => signature } }
|
229
|
-
before {
|
95
|
+
before { Time.stub now: Time.new(2012,1,1,1,2,1,0) }
|
230
96
|
subject { key.token_valid? token }
|
231
|
-
it {
|
232
|
-
|
233
|
-
it "doesn't check signature on the advisory key field" do
|
234
|
-
expect(key.token_valid?(token.merge "key" => key_fingerprint)).to be_truthy
|
235
|
-
end
|
236
|
-
|
237
|
-
it "rejects the token if the key field is present and doesn't match" do
|
238
|
-
expect(key.token_valid?(token.merge "key" => "this is not the key you are looking for")).not_to be_truthy
|
239
|
-
end
|
240
|
-
|
97
|
+
it { should be_true }
|
241
98
|
context "when token is 1 hour old" do
|
242
|
-
before {
|
243
|
-
it {
|
99
|
+
before { Time.stub now: Time.new(2012,1,1,2,1,1,0) }
|
100
|
+
it { should be_false }
|
244
101
|
context "when timestamp in the token is changed accordingly" do
|
245
102
|
let(:token) { { "data" => data, "timestamp" => "2012-01-01 02:00:01 UTC", "signature" => signature } }
|
246
|
-
it {
|
103
|
+
it { should be_false }
|
247
104
|
end
|
248
105
|
end
|
249
106
|
context "when the data is changed" do
|
250
107
|
let(:data) { { "foo" => :baz } }
|
251
|
-
it {
|
108
|
+
it { should be_false }
|
252
109
|
end
|
253
110
|
context "when RSA decrypt raises an error" do
|
254
|
-
before {
|
255
|
-
it {
|
111
|
+
before { OpenSSL::PKey::RSA.any_instance.should_receive(:public_decrypt).and_raise(OpenSSL::PKey::RSAError) }
|
112
|
+
it { should be_false }
|
256
113
|
end
|
257
114
|
end
|
258
115
|
end
|
data/spec/keystore_spec.rb
CHANGED
@@ -5,22 +5,9 @@ describe Slosilo::Keystore do
|
|
5
5
|
include_context "with mock adapter"
|
6
6
|
|
7
7
|
describe '#put' do
|
8
|
-
it "handles Slosilo::Keys" do
|
8
|
+
it "handles Slosilo::Keys too" do
|
9
9
|
subject.put(:test, key)
|
10
|
-
|
11
|
-
end
|
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
|
-
|
21
|
-
it "passes the Slosilo key to the adapter" do
|
22
|
-
expect(adapter).to receive(:put_key).with "test", key
|
23
|
-
subject.put :test, key
|
10
|
+
adapter['test'].should == rsa.to_der
|
24
11
|
end
|
25
12
|
end
|
26
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
|
data/spec/random_spec.rb
CHANGED
@@ -4,16 +4,6 @@ describe Slosilo::Random do
|
|
4
4
|
subject { Slosilo::Random }
|
5
5
|
let(:other_salt) { Slosilo::Random::salt }
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
describe '#length' do
|
10
|
-
subject { super().length }
|
11
|
-
it { is_expected.to eq(32) }
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
describe '#salt' do
|
16
|
-
subject { super().salt }
|
17
|
-
it { is_expected.not_to eq(other_salt) }
|
18
|
-
end
|
7
|
+
its('salt.length') { should == 32 }
|
8
|
+
its(:salt) { should_not == other_salt }
|
19
9
|
end
|
data/spec/sequel_adapter_spec.rb
CHANGED
@@ -1,43 +1,25 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'sequel'
|
3
|
-
require 'io/grab'
|
4
2
|
|
5
3
|
require 'slosilo/adapters/sequel_adapter'
|
6
4
|
|
7
5
|
describe Slosilo::Adapters::SequelAdapter do
|
8
|
-
include_context "with example key"
|
9
|
-
|
10
6
|
let(:model) { double "model" }
|
11
|
-
before {
|
7
|
+
before { subject.stub create_model: model }
|
12
8
|
|
13
9
|
describe "#get_key" do
|
14
10
|
context "when given key does not exist" do
|
15
|
-
before {
|
11
|
+
before { model.stub :[] => nil }
|
16
12
|
it "returns nil" do
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
context "when it exists" do
|
22
|
-
let(:id) { "id" }
|
23
|
-
before { allow(model).to receive(:[]).with(id).and_return (double "key entry", id: id, key: rsa.to_der) }
|
24
|
-
it "returns it" do
|
25
|
-
expect(subject.get_key(id)).to eq(key)
|
13
|
+
subject.get_key(:whatever).should_not be
|
26
14
|
end
|
27
15
|
end
|
28
16
|
end
|
29
17
|
|
30
18
|
describe "#put_key" do
|
31
19
|
let(:id) { "id" }
|
20
|
+
let(:key) { "key" }
|
32
21
|
it "creates the key" do
|
33
|
-
|
34
|
-
allow(model).to receive_messages columns: [:id, :key]
|
35
|
-
subject.put_key id, key
|
36
|
-
end
|
37
|
-
|
38
|
-
it "adds the fingerprint if feasible" do
|
39
|
-
expect(model).to receive(:create).with(hash_including(:id => id, :key => key.to_der, :fingerprint => key.fingerprint))
|
40
|
-
allow(model).to receive_messages columns: [:id, :key, :fingerprint]
|
22
|
+
model.should_receive(:create).with id: id, key: key
|
41
23
|
subject.put_key id, key
|
42
24
|
end
|
43
25
|
end
|
@@ -46,126 +28,33 @@ describe Slosilo::Adapters::SequelAdapter do
|
|
46
28
|
describe "#each" do
|
47
29
|
let(:one) { double("one", id: :one, key: :onek) }
|
48
30
|
let(:two) { double("two", id: :two, key: :twok) }
|
49
|
-
before {
|
31
|
+
before { model.stub(:each).and_yield(one).and_yield(two) }
|
50
32
|
|
51
33
|
it "iterates over each key" do
|
52
34
|
results = []
|
53
|
-
allow(Slosilo::Key).to receive(:new) {|x|x}
|
54
35
|
adapter.each { |id,k| results << { id => k } }
|
55
|
-
|
36
|
+
results.should == [ { one: :onek}, {two: :twok } ]
|
56
37
|
end
|
57
38
|
end
|
58
|
-
|
59
|
-
|
39
|
+
|
40
|
+
describe '#model' do
|
60
41
|
let(:db) { Sequel.sqlite }
|
61
42
|
before do
|
62
|
-
|
63
|
-
|
43
|
+
Slosilo::encryption_key = Slosilo::Symmetric.new.random_key
|
44
|
+
subject.unstub :create_model
|
45
|
+
require 'sequel'
|
64
46
|
Sequel::Model.db = db
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
shared_context "encryption key" do
|
69
|
-
before do
|
70
|
-
Slosilo.encryption_key = Slosilo::Symmetric.new.random_key
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
context "with old schema" do
|
75
|
-
include_context "encryption key"
|
76
|
-
include_context "database"
|
77
|
-
|
78
|
-
before do
|
79
|
-
db.create_table :slosilo_keystore do
|
80
|
-
String :id, primary_key: true
|
81
|
-
bytea :key, null: false
|
82
|
-
end
|
83
|
-
subject.put_key 'test', key
|
84
|
-
end
|
85
|
-
|
86
|
-
context "after migration" do
|
87
|
-
before { subject.migrate! }
|
88
|
-
|
89
|
-
it "supports look up by id" do
|
90
|
-
expect(subject.get_key("test")).to eq(key)
|
91
|
-
end
|
92
|
-
|
93
|
-
it "supports look up by fingerprint, without a warning" do
|
94
|
-
expect($stderr.grab do
|
95
|
-
expect(subject.get_by_fingerprint(key.fingerprint)).to eq([key, 'test'])
|
96
|
-
end).to be_empty
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
it "supports look up by id" do
|
101
|
-
expect(subject.get_key("test")).to eq(key)
|
102
|
-
end
|
103
|
-
|
104
|
-
it "supports look up by fingerprint, but issues a warning" do
|
105
|
-
expect($stderr.grab do
|
106
|
-
expect(subject.get_by_fingerprint(key.fingerprint)).to eq([key, 'test'])
|
107
|
-
end).not_to be_empty
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
shared_context "current schema" do
|
112
|
-
include_context "database"
|
113
|
-
before do
|
114
47
|
Sequel.extension :migration
|
115
|
-
require 'slosilo/adapters/sequel_adapter/migration
|
116
|
-
Sequel::Migration
|
117
|
-
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
subject.
|
125
|
-
end
|
126
|
-
|
127
|
-
it "supports look up by id" do
|
128
|
-
expect(subject.get_key("test")).to eq(key)
|
129
|
-
end
|
130
|
-
|
131
|
-
it "supports look up by fingerprint" do
|
132
|
-
expect(subject.get_by_fingerprint(key.fingerprint)).to eq([key, 'test'])
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
context "with an encryption key", :wip do
|
137
|
-
include_context "encryption key"
|
138
|
-
include_context "current schema"
|
139
|
-
|
140
|
-
it { is_expected.to be_secure }
|
141
|
-
|
142
|
-
it "saves the keys in encrypted form" do
|
143
|
-
subject.put_key 'test', key
|
144
|
-
|
145
|
-
expect(db[:slosilo_keystore][id: 'test'][:key]).to_not eq(key.to_der)
|
146
|
-
expect(subject.get_key 'test').to eq(key)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
context "without an encryption key", :wip do
|
151
|
-
before do
|
152
|
-
Slosilo.encryption_key = nil
|
153
|
-
end
|
154
|
-
|
155
|
-
include_context "current schema"
|
156
|
-
|
157
|
-
it { is_expected.not_to be_secure }
|
158
|
-
|
159
|
-
it "refuses to store a private key" do
|
160
|
-
expect { subject.put_key 'test', key }.to raise_error(Slosilo::Error::InsecureKeyStorage)
|
161
|
-
end
|
162
|
-
|
163
|
-
it "saves the keys in plaintext form" do
|
164
|
-
pkey = key.public
|
165
|
-
subject.put_key 'test', pkey
|
166
|
-
|
167
|
-
expect(db[:slosilo_keystore][id: 'test'][:key]).to eq(pkey.to_der)
|
168
|
-
expect(subject.get_key 'test').to eq(pkey)
|
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
|
169
58
|
end
|
170
59
|
end
|
171
60
|
end
|