slosilo 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +10 -0
  3. data/.gitignore +21 -0
  4. data/.gitleaks.toml +221 -0
  5. data/.kateproject +4 -0
  6. data/CHANGELOG.md +50 -0
  7. data/CONTRIBUTING.md +16 -0
  8. data/Gemfile +4 -0
  9. data/Jenkinsfile +132 -0
  10. data/LICENSE +22 -0
  11. data/README.md +152 -0
  12. data/Rakefile +17 -0
  13. data/SECURITY.md +42 -0
  14. data/dev/Dockerfile.dev +7 -0
  15. data/dev/docker-compose.yml +8 -0
  16. data/lib/slosilo/adapters/abstract_adapter.rb +23 -0
  17. data/lib/slosilo/adapters/file_adapter.rb +42 -0
  18. data/lib/slosilo/adapters/memory_adapter.rb +31 -0
  19. data/lib/slosilo/adapters/mock_adapter.rb +21 -0
  20. data/lib/slosilo/adapters/sequel_adapter/migration.rb +52 -0
  21. data/lib/slosilo/adapters/sequel_adapter.rb +96 -0
  22. data/lib/slosilo/attr_encrypted.rb +85 -0
  23. data/lib/slosilo/errors.rb +15 -0
  24. data/lib/slosilo/jwt.rb +122 -0
  25. data/lib/slosilo/key.rb +218 -0
  26. data/lib/slosilo/keystore.rb +89 -0
  27. data/lib/slosilo/random.rb +11 -0
  28. data/lib/slosilo/symmetric.rb +63 -0
  29. data/lib/slosilo/version.rb +22 -0
  30. data/lib/slosilo.rb +13 -0
  31. data/lib/tasks/slosilo.rake +32 -0
  32. data/publish.sh +5 -0
  33. data/secrets.yml +1 -0
  34. data/slosilo.gemspec +38 -0
  35. data/spec/encrypted_attributes_spec.rb +114 -0
  36. data/spec/file_adapter_spec.rb +81 -0
  37. data/spec/jwt_spec.rb +102 -0
  38. data/spec/key_spec.rb +258 -0
  39. data/spec/keystore_spec.rb +26 -0
  40. data/spec/random_spec.rb +19 -0
  41. data/spec/sequel_adapter_spec.rb +171 -0
  42. data/spec/slosilo_spec.rb +124 -0
  43. data/spec/spec_helper.rb +84 -0
  44. data/spec/symmetric_spec.rb +94 -0
  45. data/test.sh +8 -0
  46. metadata +238 -0
data/spec/jwt_spec.rb ADDED
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ # (Mostly) integration tests for JWT token format
4
+ describe Slosilo::Key do
5
+ include_context "with example key"
6
+
7
+ describe '#issue_jwt' do
8
+ it 'issues an JWT token with given claims' do
9
+ allow(Time).to receive(:now) { DateTime.parse('2014-06-04 23:22:32 -0400').to_time }
10
+
11
+ tok = key.issue_jwt sub: 'host/example', cidr: %w(fec0::/64)
12
+
13
+ expect(tok).to be_frozen
14
+
15
+ expect(tok.header).to eq \
16
+ alg: 'conjur.org/slosilo/v2',
17
+ kid: key_fingerprint
18
+ expect(tok.claims).to eq \
19
+ iat: 1401938552,
20
+ sub: 'host/example',
21
+ cidr: ['fec0::/64']
22
+
23
+ expect(key.verify_signature tok.string_to_sign, tok.signature).to be_truthy
24
+ end
25
+ end
26
+ end
27
+
28
+ describe Slosilo::JWT do
29
+ context "with a signed token" do
30
+ let(:signature) { 'very signed, such alg' }
31
+ subject(:token) { Slosilo::JWT.new test: "token" }
32
+ before do
33
+ allow(Time).to receive(:now) { DateTime.parse('2014-06-04 23:22:32 -0400').to_time }
34
+ token.add_signature(alg: 'test-sig') { signature }
35
+ end
36
+
37
+ it 'allows conversion to JSON representation with #to_json' do
38
+ json = JSON.load token.to_json
39
+ expect(JSON.load Base64.urlsafe_decode64 json['protected']).to eq \
40
+ 'alg' => 'test-sig'
41
+ expect(JSON.load Base64.urlsafe_decode64 json['payload']).to eq \
42
+ 'iat' => 1401938552, 'test' => 'token'
43
+ expect(Base64.urlsafe_decode64 json['signature']).to eq signature
44
+ end
45
+
46
+ it 'allows conversion to compact representation with #to_s' do
47
+ h, c, s = token.to_s.split '.'
48
+ expect(JSON.load Base64.urlsafe_decode64 h).to eq \
49
+ 'alg' => 'test-sig'
50
+ expect(JSON.load Base64.urlsafe_decode64 c).to eq \
51
+ 'iat' => 1401938552, 'test' => 'token'
52
+ expect(Base64.urlsafe_decode64 s).to eq signature
53
+ end
54
+ end
55
+
56
+ describe '#to_json' do
57
+ it "passes any parameters" do
58
+ token = Slosilo::JWT.new
59
+ allow(token).to receive_messages \
60
+ header: :header,
61
+ claims: :claims,
62
+ signature: :signature
63
+ expect_any_instance_of(Hash).to receive(:to_json).with :testing
64
+ expect(token.to_json :testing)
65
+ end
66
+ end
67
+
68
+ describe '()' do
69
+ include_context "with example key"
70
+
71
+ it 'understands both serializations' do
72
+ [COMPACT_TOKEN, JSON_TOKEN].each do |token|
73
+ token = Slosilo::JWT token
74
+ expect(token.header).to eq \
75
+ 'typ' => 'JWT',
76
+ 'alg' => 'conjur.org/slosilo/v2',
77
+ 'kid' => key_fingerprint
78
+ expect(token.claims).to eq \
79
+ 'sub' => 'host/example',
80
+ 'iat' => 1401938552,
81
+ 'exp' => 1401938552 + 60*60,
82
+ 'cidr' => ['fec0::/64']
83
+ expect(key.verify_signature token.string_to_sign, token.signature).to be_truthy
84
+ end
85
+ end
86
+
87
+ it 'is a noop if already parsed' do
88
+ token = Slosilo::JWT COMPACT_TOKEN
89
+ expect(Slosilo::JWT token).to eq token
90
+ end
91
+
92
+ it 'raises ArgumentError on failure to convert' do
93
+ expect { Slosilo::JWT "foo bar" }.to raise_error ArgumentError
94
+ expect { Slosilo::JWT elite: 31337 }.to raise_error ArgumentError
95
+ expect { Slosilo::JWT "foo.bar.xyzzy" }.to raise_error ArgumentError
96
+ end
97
+ end
98
+
99
+ COMPACT_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiIxMDdiZGI4NTAxYzQxOWZhZDJmZGIyMGI0NjdkNGQwYTYyYTE2YTk4YzM1ZjJkYTBlYjNiMWZmOTI5Nzk1YWQ5In0=.eyJzdWIiOiJob3N0L2V4YW1wbGUiLCJjaWRyIjpbImZlYzA6Oi82NCJdLCJleHAiOjE0MDE5NDIxNTIsImlhdCI6MTQwMTkzODU1Mn0=.qSxy6gx0DbiIc-Wz_vZhBsYi1SCkHhzxfMGPnnG6MTqjlzy7ntmlU2H92GKGoqCRo6AaNLA_C3hA42PeEarV5nMoTj8XJO_kwhrt2Db2OX4u83VS0_enoztWEZG5s45V0Lv71lVR530j4LD-hpqhm_f4VuISkeH84u0zX7s1zKOlniuZP-abCAHh0htTnrVz9wKG0VywkCUmWYyNNqC2h8PRf64SvCWcQ6VleHpjO-ms8OeTw4ZzRbzKMi0mL6eTmQlbT3PeBArUaS0pNJPg9zdDQaL2XDOofvQmj6Yy_8RA4eCt9HEfTYEdriVqK-_9QCspbGzFVn9GTWf51MRi5dngV9ItsDoG9ktDtqFuMttv7TcqjftsIHZXZsAZ175E".freeze
100
+
101
+ JSON_TOKEN = "{\"protected\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiIxMDdiZGI4NTAxYzQxOWZhZDJmZGIyMGI0NjdkNGQwYTYyYTE2YTk4YzM1ZjJkYTBlYjNiMWZmOTI5Nzk1YWQ5In0=\",\"payload\":\"eyJzdWIiOiJob3N0L2V4YW1wbGUiLCJjaWRyIjpbImZlYzA6Oi82NCJdLCJleHAiOjE0MDE5NDIxNTIsImlhdCI6MTQwMTkzODU1Mn0=\",\"signature\":\"qSxy6gx0DbiIc-Wz_vZhBsYi1SCkHhzxfMGPnnG6MTqjlzy7ntmlU2H92GKGoqCRo6AaNLA_C3hA42PeEarV5nMoTj8XJO_kwhrt2Db2OX4u83VS0_enoztWEZG5s45V0Lv71lVR530j4LD-hpqhm_f4VuISkeH84u0zX7s1zKOlniuZP-abCAHh0htTnrVz9wKG0VywkCUmWYyNNqC2h8PRf64SvCWcQ6VleHpjO-ms8OeTw4ZzRbzKMi0mL6eTmQlbT3PeBArUaS0pNJPg9zdDQaL2XDOofvQmj6Yy_8RA4eCt9HEfTYEdriVqK-_9QCspbGzFVn9GTWf51MRi5dngV9ItsDoG9ktDtqFuMttv7TcqjftsIHZXZsAZ175E\"}".freeze
102
+ end
data/spec/key_spec.rb ADDED
@@ -0,0 +1,258 @@
1
+ require 'spec_helper'
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/numeric/time'
5
+
6
+ describe Slosilo::Key do
7
+ include_context "with example key"
8
+
9
+ subject { key }
10
+
11
+ describe '#to_der' do
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
41
+
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
+ let(:plaintext) { 'quick brown fox jumped over the lazy dog' }
68
+ describe '#encrypt' do
69
+ it "generates a symmetric encryption key and encrypts the plaintext with the public key" do
70
+ ctxt, skey = subject.encrypt plaintext
71
+ pskey = rsa.private_decrypt skey
72
+ expect(Slosilo::Symmetric.new.decrypt(ctxt, key: pskey)).to eq(plaintext)
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')
80
+ end
81
+ end
82
+
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
+ describe '#decrypt' do
86
+ it "decrypts the symmetric key and then uses it to decrypt the ciphertext" do
87
+ expect(subject.decrypt(ciphertext, skey)).to eq(plaintext)
88
+ end
89
+ end
90
+
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
+ describe '#initialize' do
99
+ context "when no argument given" do
100
+ subject { Slosilo::Key.new }
101
+ let (:rsa) { double "key" }
102
+ it "generates a new key pair" do
103
+ expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_return(rsa)
104
+ expect(subject.key).to eq(rsa)
105
+ end
106
+ end
107
+ context "when given an armored key" do
108
+ 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
114
+ end
115
+ context "when given a key instance" do
116
+ 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
122
+ end
123
+ context "when given something else" do
124
+ subject { Slosilo::Key.new "foo" }
125
+ it "fails early" do
126
+ expect { subject }.to raise_error ArgumentError
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#sign" do
132
+ context "when given a hash" do
133
+ it "converts to a sorted array and signs that" do
134
+ expect(key).to receive(:sign_string).with '[["a",3],["b",42]]'
135
+ key.sign b: 42, a: 3
136
+ end
137
+ end
138
+ context "when given an array" do
139
+ it "signs a JSON representation instead" do
140
+ expect(key).to receive(:sign_string).with '[2,[42,2]]'
141
+ key.sign [2, [42, 2]]
142
+ end
143
+ end
144
+ 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".force_encoding("ASCII-8BIT") }
146
+ it "signs it" do
147
+ allow(key).to receive_messages shake_salt: 'a pinch of salt'
148
+ expect(key.sign("this sentence is not this sentence")).to eq(expected_signature)
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
162
+ end
163
+ end
164
+ end
165
+
166
+ describe "#signed_token" do
167
+ let(:time) { Time.new(2012,1,1,1,1,1,0) }
168
+ let(:data) { { "foo" => :bar } }
169
+ let(:token_to_sign) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC" } }
170
+ let(:signature) { "signature" }
171
+ 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".force_encoding("ASCII-8BIT") }
173
+ let(:expected_token) { token_to_sign.merge "signature" => expected_signature, "key" => key_fingerprint }
174
+ before do
175
+ allow(key).to receive_messages shake_salt: salt
176
+ allow(Time).to receive_messages new: time
177
+ end
178
+ subject { key.signed_token data }
179
+ it { is_expected.to eq(expected_token) }
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
223
+ end
224
+
225
+ describe "#token_valid?" do
226
+ 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".force_encoding("ASCII-8BIT") }
228
+ let(:token) { { "data" => data, "timestamp" => "2012-01-01 01:01:01 UTC", "signature" => signature } }
229
+ before { allow(Time).to receive_messages now: Time.new(2012,1,1,1,2,1,0) }
230
+ subject { key.token_valid? token }
231
+ it { is_expected.to be_truthy }
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
+
241
+ context "when token is 1 hour old" do
242
+ before { allow(Time).to receive_messages now: Time.new(2012,1,1,2,1,1,0) }
243
+ it { is_expected.to be_falsey }
244
+ context "when timestamp in the token is changed accordingly" do
245
+ let(:token) { { "data" => data, "timestamp" => "2012-01-01 02:00:01 UTC", "signature" => signature } }
246
+ it { is_expected.to be_falsey }
247
+ end
248
+ end
249
+ context "when the data is changed" do
250
+ let(:data) { { "foo" => :baz } }
251
+ it { is_expected.to be_falsey }
252
+ end
253
+ context "when RSA decrypt raises an error" do
254
+ before { expect_any_instance_of(OpenSSL::PKey::RSA).to receive(:public_decrypt).and_raise(OpenSSL::PKey::RSAError) }
255
+ it { is_expected.to be_falsey }
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,26 @@
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" do
9
+ subject.put(:test, key)
10
+ expect(adapter['test'].to_der).to eq(rsa.to_der)
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
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slosilo::Random do
4
+ subject { Slosilo::Random }
5
+ let(:other_salt) { Slosilo::Random::salt }
6
+
7
+ describe '#salt' do
8
+ subject { super().salt }
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
19
+ end
@@ -0,0 +1,171 @@
1
+ require 'spec_helper'
2
+ require 'sequel'
3
+ require 'io/grab'
4
+
5
+ require 'slosilo/adapters/sequel_adapter'
6
+
7
+ describe Slosilo::Adapters::SequelAdapter do
8
+ include_context "with example key"
9
+
10
+ let(:model) { double "model" }
11
+ before { allow(subject).to receive_messages create_model: model }
12
+
13
+ describe "#get_key" do
14
+ context "when given key does not exist" do
15
+ before { allow(model).to receive_messages :[] => nil }
16
+ it "returns nil" do
17
+ expect(subject.get_key(:whatever)).not_to be
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)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#put_key" do
31
+ let(:id) { "id" }
32
+ it "creates the key" do
33
+ expect(model).to receive(:create).with(hash_including(:id => id, :key => key.to_der))
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]
41
+ subject.put_key id, key
42
+ end
43
+ end
44
+
45
+ let(:adapter) { subject }
46
+ describe "#each" do
47
+ let(:one) { double("one", id: :one, key: :onek) }
48
+ let(:two) { double("two", id: :two, key: :twok) }
49
+ before { allow(model).to receive(:each).and_yield(one).and_yield(two) }
50
+
51
+ it "iterates over each key" do
52
+ results = []
53
+ allow(Slosilo::Key).to receive(:new) {|x|x}
54
+ adapter.each { |id,k| results << { id => k } }
55
+ expect(results).to eq([ { one: :onek}, {two: :twok } ])
56
+ end
57
+ end
58
+
59
+ shared_context "database" do
60
+ let(:db) { Sequel.sqlite }
61
+ before do
62
+ allow(subject).to receive(:create_model).and_call_original
63
+ Sequel::Model.cache_anonymous_models = false
64
+ 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
+ Sequel.extension :migration
115
+ require 'slosilo/adapters/sequel_adapter/migration.rb'
116
+ Sequel::Migration.descendants.first.apply db, :up
117
+ end
118
+ end
119
+
120
+ context "with current schema" do
121
+ include_context "encryption key"
122
+ include_context "current schema"
123
+ before do
124
+ subject.put_key 'test', key
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)
169
+ end
170
+ end
171
+ end