slosilo 0.0.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +0 -2
  2. data/LICENSE +2 -2
  3. data/README.md +8 -128
  4. data/lib/slosilo/adapters/abstract_adapter.rb +0 -4
  5. data/lib/slosilo/adapters/mock_adapter.rb +1 -14
  6. data/lib/slosilo/adapters/sequel_adapter/migration.rb +2 -5
  7. data/lib/slosilo/adapters/sequel_adapter.rb +5 -67
  8. data/lib/slosilo/attr_encrypted.rb +7 -33
  9. data/lib/slosilo/http_request.rb +59 -0
  10. data/lib/slosilo/key.rb +6 -129
  11. data/lib/slosilo/keystore.rb +12 -40
  12. data/lib/slosilo/rack/middleware.rb +123 -0
  13. data/lib/slosilo/symmetric.rb +17 -47
  14. data/lib/slosilo/version.rb +2 -21
  15. data/lib/slosilo.rb +2 -2
  16. data/lib/tasks/slosilo.rake +0 -10
  17. data/slosilo.gemspec +6 -19
  18. data/spec/http_request_spec.rb +107 -0
  19. data/spec/http_stack_spec.rb +44 -0
  20. data/spec/key_spec.rb +32 -175
  21. data/spec/keystore_spec.rb +2 -15
  22. data/spec/rack_middleware_spec.rb +109 -0
  23. data/spec/random_spec.rb +2 -12
  24. data/spec/sequel_adapter_spec.rb +22 -133
  25. data/spec/slosilo_spec.rb +12 -78
  26. data/spec/spec_helper.rb +15 -37
  27. data/spec/symmetric_spec.rb +26 -69
  28. metadata +51 -104
  29. checksums.yaml +0 -7
  30. data/.github/CODEOWNERS +0 -10
  31. data/.gitleaks.toml +0 -221
  32. data/.kateproject +0 -4
  33. data/CHANGELOG.md +0 -50
  34. data/CONTRIBUTING.md +0 -16
  35. data/Jenkinsfile +0 -132
  36. data/SECURITY.md +0 -42
  37. data/dev/Dockerfile.dev +0 -7
  38. data/dev/docker-compose.yml +0 -8
  39. data/lib/slosilo/adapters/file_adapter.rb +0 -42
  40. data/lib/slosilo/adapters/memory_adapter.rb +0 -31
  41. data/lib/slosilo/errors.rb +0 -15
  42. data/lib/slosilo/jwt.rb +0 -122
  43. data/publish.sh +0 -5
  44. data/secrets.yml +0 -1
  45. data/spec/encrypted_attributes_spec.rb +0 -114
  46. data/spec/file_adapter_spec.rb +0 -81
  47. data/spec/jwt_spec.rb +0 -102
  48. 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
- 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
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
- 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')
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
- expect(subject.decrypt(ciphertext, skey)).to eq(plaintext)
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
- expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_return(rsa)
104
- expect(subject.key).to eq(rsa)
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 ArgumentError
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
- expect(key).to receive(:sign_string).with '[["a",3],["b",42]]'
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
- expect(key).to receive(:sign_string).with '[2,[42,2]]'
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".force_encoding("ASCII-8BIT") }
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
- 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
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".force_encoding("ASCII-8BIT") }
173
- let(:expected_token) { token_to_sign.merge "signature" => expected_signature, "key" => key_fingerprint }
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
- allow(key).to receive_messages shake_salt: salt
176
- allow(Time).to receive_messages new: time
84
+ key.stub salt: salt
85
+ Time.stub new: time
177
86
  end
178
87
  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
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".force_encoding("ASCII-8BIT") }
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 { allow(Time).to receive_messages now: Time.new(2012,1,1,1,2,1,0) }
95
+ before { Time.stub now: Time.new(2012,1,1,1,2,1,0) }
230
96
  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
-
97
+ it { should be_true }
241
98
  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 }
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 { is_expected.to be_falsey }
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 { is_expected.to be_falsey }
108
+ it { should be_false }
252
109
  end
253
110
  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 }
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
@@ -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
- 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
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
- 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
7
+ its('salt.length') { should == 32 }
8
+ its(:salt) { should_not == other_salt }
19
9
  end
@@ -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 { allow(subject).to receive_messages create_model: model }
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 { allow(model).to receive_messages :[] => nil }
11
+ before { model.stub :[] => nil }
16
12
  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)
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
- 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]
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 { allow(model).to receive(:each).and_yield(one).and_yield(two) }
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
- expect(results).to eq([ { one: :onek}, {two: :twok } ])
36
+ results.should == [ { one: :onek}, {two: :twok } ]
56
37
  end
57
38
  end
58
-
59
- shared_context "database" do
39
+
40
+ describe '#model' do
60
41
  let(:db) { Sequel.sqlite }
61
42
  before do
62
- allow(subject).to receive(:create_model).and_call_original
63
- Sequel::Model.cache_anonymous_models = false
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.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)
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