smaak 0.0.10 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +112 -53
- data/lib/smaak/adaptors/net_http_adaptor.rb +43 -0
- data/lib/smaak/adaptors/rack_adaptor.rb +32 -0
- data/lib/smaak/associate.rb +12 -6
- data/lib/smaak/auth_message.rb +55 -39
- data/lib/smaak/cavage_04.rb +84 -0
- data/lib/smaak/client.rb +38 -21
- data/lib/smaak/crypto.rb +45 -0
- data/lib/smaak/server.rb +33 -16
- data/lib/smaak/smaak_service.rb +29 -0
- data/lib/smaak/utils.rb +10 -0
- data/lib/smaak/version.rb +1 -1
- data/lib/smaak.rb +58 -18
- data/smaak.gemspec +1 -0
- data/spec/lib/smaak/adaptors/net_http_adaptor_spec.rb +99 -0
- data/spec/lib/smaak/adaptors/rack_adaptor_spec.rb +83 -0
- data/spec/lib/smaak/auth_message_spec.rb +78 -147
- data/spec/lib/smaak/cavage_04_spec.rb +231 -0
- data/spec/lib/smaak/client_spec.rb +147 -34
- data/spec/lib/smaak/crypto_spec.rb +94 -0
- data/spec/lib/smaak/server_spec.rb +172 -22
- data/spec/lib/smaak/smaak_service_spec.rb +52 -0
- data/spec/lib/smaak_spec.rb +130 -52
- data/spec/spec_helper.rb +3 -0
- metadata +32 -3
- data/lib/smaak/request_signing_validator.rb +0 -20
@@ -3,70 +3,183 @@ require 'smaak'
|
|
3
3
|
|
4
4
|
describe Smaak::Client do
|
5
5
|
before :all do
|
6
|
-
@
|
6
|
+
@test_uri = "http://rubygems.org:80/gems/smaak"
|
7
|
+
@test_encrypt = false
|
8
|
+
@test_service_identifier = 'service-to-talk-to'
|
7
9
|
@test_service_psk = 'testsharedsecret'
|
8
10
|
@test_client_private_key = OpenSSL::PKey::RSA.new(4096)
|
9
11
|
@test_service_private_key = OpenSSL::PKey::RSA.new(4096)
|
10
12
|
@test_service_public_key = @test_service_private_key.public_key
|
11
13
|
@test_data = {}
|
12
14
|
@iut = Smaak::Client.new
|
13
|
-
@
|
15
|
+
@test_identifier = 'test-client-1.cpt1.host-h.net'
|
14
16
|
@test_token_life = 5
|
15
|
-
@iut.
|
17
|
+
@iut.set_identifier(@test_identifier)
|
16
18
|
@iut.set_private_key(@test_client_private_key)
|
17
19
|
@iut.set_token_life(@test_token_life)
|
18
|
-
@iut.add_association(@
|
20
|
+
@iut.add_association(@test_service_identifier, @test_service_public_key, @test_service_psk, @test_encrypt)
|
21
|
+
|
22
|
+
@request = Net::HTTP::Post.new(@test_uri)
|
23
|
+
@request.body = "body-data"
|
24
|
+
@test_adaptor = Smaak::NetHttpAdaptor.new(@request)
|
19
25
|
end
|
20
26
|
|
21
|
-
context "when given an
|
22
|
-
it "should remember an
|
27
|
+
context "when given an identifier" do
|
28
|
+
it "should remember an identifier provided" do
|
23
29
|
iut = Smaak::Client.new
|
24
|
-
expect(iut.
|
25
|
-
iut.
|
26
|
-
expect(iut.
|
30
|
+
expect(iut.identifier).to eq(nil)
|
31
|
+
iut.set_identifier(@test_identifier)
|
32
|
+
expect(iut.identifier).to eq(@test_identifier)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should raise an ArgumentError if an identifier is not provided" do
|
36
|
+
expect {
|
37
|
+
@iut.set_identifier(nil)
|
38
|
+
}.to raise_error ArgumentError, "Invalid identifier"
|
39
|
+
expect {
|
40
|
+
@iut.set_identifier("")
|
41
|
+
}.to raise_error ArgumentError, "Invalid identifier"
|
42
|
+
expect {
|
43
|
+
@iut.set_identifier(" ")
|
44
|
+
}.to raise_error ArgumentError, "Invalid identifier"
|
27
45
|
end
|
28
46
|
end
|
29
47
|
|
30
|
-
context "when asked to
|
48
|
+
context "when asked to sign a request destined for an associate" do
|
31
49
|
it "should raise an ArgumentError if the associate is unknown" do
|
32
50
|
expect{
|
33
|
-
@iut.
|
51
|
+
@iut.sign_request("unknown", nil)
|
34
52
|
}.to raise_error ArgumentError, "Associate invalid"
|
35
53
|
expect{
|
36
|
-
@iut.
|
54
|
+
@iut.sign_request(nil, nil)
|
37
55
|
}.to raise_error ArgumentError, "Associate invalid"
|
38
56
|
end
|
39
57
|
|
40
|
-
it "should
|
41
|
-
expect
|
42
|
-
|
58
|
+
it "should raise an ArgumentError if an adaptor was not provided" do
|
59
|
+
expect {
|
60
|
+
@iut.sign_request(@test_service_identifier, nil)
|
61
|
+
}.to raise_error ArgumentError, "Invalid adaptor"
|
43
62
|
end
|
44
63
|
|
45
|
-
it "should
|
46
|
-
expect(Smaak).to receive(:
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
64
|
+
it "should create a new auth message using the associate details" do
|
65
|
+
expect(Smaak::AuthMessage).to receive(:create).with(@test_service_public_key.export, @test_service_psk, @test_token_life, @test_identifier, @test_encrypt)
|
66
|
+
expect {
|
67
|
+
@iut.sign_request(@test_service_identifier, @test_adaptor)
|
68
|
+
}.to raise_error NoMethodError
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should encrypt the request body if the associate is configured for encryption" do
|
72
|
+
@iut.add_association('encrypted', @test_service_public_key, @test_service_psk, true)
|
73
|
+
expect(Smaak::Crypto).to receive(:encrypt).with(@test_adaptor.body, @test_service_public_key).and_return(@test_adaptor.body)
|
74
|
+
@iut.sign_request('encrypted', @test_adaptor)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should not encrypt the request body if the associate is not configure for encryption" do
|
78
|
+
expect(Smaak::Crypto).not_to receive(:encrypt)
|
79
|
+
@iut.sign_request(@test_service_identifier, @test_adaptor)
|
52
80
|
end
|
53
81
|
|
54
82
|
it "should sign the message" do
|
55
|
-
expect(Smaak).to receive(:
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
83
|
+
expect(Smaak).to receive(:sign_authorization_headers)
|
84
|
+
@iut.sign_request(@test_service_identifier, @test_adaptor)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return the adaptor with the signed request" do
|
88
|
+
expect(@iut.sign_request(@test_service_identifier, @test_adaptor)).to eq(@test_adaptor)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when asked to help the developer by providing GET and POST requests given an associate, URI, body, ssl and ssl verify option" do
|
93
|
+
it "should compile a Net::HTTP request" do
|
94
|
+
url = URI.parse(@test_uri)
|
95
|
+
expect(Net::HTTP).to receive(:new).with(url.host, url.port)
|
96
|
+
expect {
|
97
|
+
@iut.get(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
98
|
+
}.to raise_error
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should set the method to GET if requested" do
|
102
|
+
url = URI.parse(@test_uri)
|
103
|
+
expect(Net::HTTP::Get).to receive(:new).with(url.to_s)
|
104
|
+
expect {
|
105
|
+
@iut.get(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
60
106
|
}.to raise_error
|
61
107
|
end
|
62
108
|
|
63
|
-
it "should
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
109
|
+
it "should set the method to POST if requested" do
|
110
|
+
url = URI.parse(@test_uri)
|
111
|
+
expect(Net::HTTP::Post).to receive(:new).with(url.to_s)
|
112
|
+
expect {
|
113
|
+
@iut.post(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
114
|
+
}.to raise_error
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should set use_ssl appropriately" do
|
118
|
+
url = URI.parse(@test_uri)
|
119
|
+
mock_http = double(Net::HTTP)
|
120
|
+
expect(Net::HTTP).to receive(:new).with(url.host, url.port).and_return(mock_http)
|
121
|
+
expect(mock_http).to receive(:use_ssl=).with(false)
|
122
|
+
expect {
|
123
|
+
@iut.get(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
124
|
+
}.to raise_error
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should configure ssl verification appropriately" do
|
128
|
+
url = URI.parse(@test_uri)
|
129
|
+
mock_http = double(Net::HTTP)
|
130
|
+
expect(Net::HTTP).to receive(:new).with(url.host, url.port).and_return(mock_http)
|
131
|
+
expect(mock_http).to receive(:use_ssl=).with(false)
|
132
|
+
expect(mock_http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
133
|
+
expect {
|
134
|
+
@iut.get(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
135
|
+
}.to raise_error
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should set the body appropriately" do
|
139
|
+
url = URI.parse(@test_uri)
|
140
|
+
mock_req = double(Net::HTTP::Post)
|
141
|
+
expect(Net::HTTP::Post).to receive(:new).with(url.to_s).and_return(mock_req)
|
142
|
+
expect(mock_req).to receive(:body=).with(@test_body)
|
143
|
+
expect {
|
144
|
+
@iut.post(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
145
|
+
}.to raise_error
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should connect using the URI provided" do
|
149
|
+
url = URI.parse(@test_uri)
|
150
|
+
mock_http = Net::HTTP.new(url.host, url.port)
|
151
|
+
mock_req = Net::HTTP::Post.new(url.to_s)
|
152
|
+
expect(Net::HTTP).to receive(:new).with(url.host, url.port).and_return(mock_http)
|
153
|
+
expect(Net::HTTP::Post).to receive(:new).with(url.to_s).and_return(mock_req)
|
154
|
+
expect(mock_http).to receive(:request).with(mock_req)
|
155
|
+
@iut.post(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should decrypt the response body if the request was encrypted" do
|
159
|
+
@iut.add_association('encrypted', @test_service_public_key, @test_service_psk, true)
|
160
|
+
url = URI.parse(@test_uri)
|
161
|
+
mock_http = Net::HTTP.new(url.host, url.port)
|
162
|
+
mock_req = Net::HTTP::Post.new(url.to_s)
|
163
|
+
expect(Net::HTTP).to receive(:new).with(url.host, url.port).and_return(mock_http)
|
164
|
+
expect(Net::HTTP::Post).to receive(:new).with(url.to_s).and_return(mock_req)
|
165
|
+
mock_response = ""
|
166
|
+
expect(mock_response).to receive(:body).and_return ""
|
167
|
+
expect(mock_response).to receive(:body=)
|
168
|
+
expect(mock_http).to receive(:request).with(mock_req).and_return(mock_response)
|
169
|
+
expect(Smaak::Crypto).to receive(:encrypt).and_return ""
|
170
|
+
expect(Smaak::Crypto).to receive(:decrypt).with("", @iut.key)
|
171
|
+
|
172
|
+
@iut.post('encrypted', @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should return the response" do
|
176
|
+
url = URI.parse(@test_uri)
|
177
|
+
mock_http = Net::HTTP.new(url.host, url.port)
|
178
|
+
mock_req = Net::HTTP::Post.new(url.to_s)
|
179
|
+
expect(Net::HTTP).to receive(:new).with(url.host, url.port).and_return(mock_http)
|
180
|
+
expect(Net::HTTP::Post).to receive(:new).with(url.to_s).and_return(mock_req)
|
181
|
+
expect(mock_http).to receive(:request).with(mock_req).and_return "response"
|
182
|
+
expect(@iut.post(@test_service_identifier, @test_uri, @test_body, false, OpenSSL::SSL::VERIFY_NONE)).to eql("response")
|
70
183
|
end
|
71
184
|
end
|
72
185
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Smaak::Crypto do
|
4
|
+
before :all do
|
5
|
+
@private_key = OpenSSL::PKey::RSA.new(4096)
|
6
|
+
@data = {'a' => 'B'}.to_json
|
7
|
+
@bdata = Base64::strict_encode64(@data)
|
8
|
+
@digest = OpenSSL::Digest::SHA256.new
|
9
|
+
@signature = @private_key.sign(@digest, @bdata)
|
10
|
+
@public_key = @private_key.public_key
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when asked to obfuscate a clear-text psk" do
|
14
|
+
it "should reverse the psk and apply an MD5 hexadecimal digest to the result" do
|
15
|
+
expect(Smaak::Crypto::obfuscate_psk('sharedsecret')).to eq(Digest::MD5.hexdigest('sharedsecret'.reverse))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when asked to generate a nonce" do
|
20
|
+
it "should generate a random nonce with at most 1 in a ten billion probability of a consecutive clash" do
|
21
|
+
expect(SecureRandom).to receive(:random_number).with(10000000000)
|
22
|
+
Smaak::Crypto::generate_nonce
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should generate a different nonce every time with high probability (less than 1 in 10000) given a history of 1000000 nonces" do
|
26
|
+
repeat = {}
|
27
|
+
failed = 0
|
28
|
+
threshold = 1000000
|
29
|
+
for i in 1..threshold do
|
30
|
+
value = Smaak::Crypto::generate_nonce
|
31
|
+
failed = failed + 1 if repeat[value] == 1
|
32
|
+
repeat[value] = 1
|
33
|
+
end
|
34
|
+
failed_p = (failed.to_f / threshold) * 100
|
35
|
+
puts "I've seen #{failed_p} % of nonces before in #{threshold} generations"
|
36
|
+
expect(failed_p < 0.01).to eq(true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when asked to encode data using base 64" do
|
41
|
+
it "should encode the data without newlines or line feeds using base 64 (strict)" do
|
42
|
+
data = "some data"
|
43
|
+
expect(Smaak::Crypto::encode64(data)).to eq(Base64.strict_encode64(data))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when asked to decode data using base 64" do
|
48
|
+
it "should decode the data using base 64 (strict)" do
|
49
|
+
expect(Smaak::Crypto::decode64(Base64.strict_encode64("some data"))).to eq("some data")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when asked to sign data given a private key" do
|
54
|
+
it "should sign a strict Base64 representation of the data with the key using a 256 bit SHA digest" do
|
55
|
+
expect(Smaak::Crypto::sign_data(@data, @private_key)).to eq(@signature)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when asked to verify a signature given data and a public key" do
|
60
|
+
it "should verify using a 256 bit SHA digest" do
|
61
|
+
expect(OpenSSL::Digest::SHA256).to receive(:new).and_return(@digest)
|
62
|
+
Smaak::Crypto::verify_signature(@signature, @bdata, @public_key)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should return true when the signature is verified by the public key" do
|
66
|
+
expect(Smaak::Crypto::verify_signature(@signature, @bdata, @public_key)).to eq(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return false when the signature cannot be verified by the public key" do
|
70
|
+
other_key = OpenSSL::PKey::RSA.new(4096).public_key
|
71
|
+
expect(Smaak::Crypto::verify_signature(@signature, @bdata, other_key)).to eq(false)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when asked to encrypt data given a public key" do
|
76
|
+
it "should return a base64 representation of the data encrypted with the public key" do
|
77
|
+
expect(@private_key.private_decrypt(Base64.strict_decode64(Smaak::Crypto::encrypt(@data, @public_key)))).to eq(@data)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when asked to decrypt encrypted data given a private key" do
|
82
|
+
it "should return a decryption using the private key of a base64 decode of the data" do
|
83
|
+
expect(Smaak::Crypto::decrypt(Base64.strict_encode64(@public_key.public_encrypt(@data)), @private_key)).to eq(@data)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when asked to sync data from an IO stream" do
|
88
|
+
it "should sink all data from the stream, collating when nil is read" do
|
89
|
+
mock_stream = double(Object)
|
90
|
+
allow(mock_stream).to receive(:gets).and_return("a", "b", nil)
|
91
|
+
expect(Smaak::Crypto::sink(mock_stream)).to eq("ab")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -2,26 +2,34 @@ require './spec/spec_helper.rb'
|
|
2
2
|
require 'smaak'
|
3
3
|
require 'mock/request.rb'
|
4
4
|
|
5
|
+
def mock_auth_message(env)
|
6
|
+
request = Rack::Request.new(env)
|
7
|
+
adaptor = Smaak::create_adaptor(request)
|
8
|
+
@iut.build_auth_message_from_request(adaptor)
|
9
|
+
end
|
10
|
+
|
5
11
|
describe Smaak::Server do
|
6
12
|
before :all do
|
7
13
|
@iut = Smaak::Server.new
|
8
14
|
@iut.set_token_life(2)
|
9
|
-
@test_nonce = 1234567890
|
15
|
+
@test_nonce = "1234567890"
|
10
16
|
@test_server_private_key = OpenSSL::PKey::RSA.new(4096)
|
11
17
|
@test_psk = "testpresharedkey"
|
12
18
|
@test_server_public_key = @test_server_private_key.public_key
|
13
|
-
@
|
19
|
+
@test_identifier = 'test-service-1.cpt1.host-h.net'
|
20
|
+
@message = Smaak::AuthMessage.new(@test_identifier, @test_nonce, Time.now.to_i, @test_psk, @test_server_public_key.export, false)
|
21
|
+
@iut.add_association(@test_identifier, @test_server_public_key, @test_psk, false)
|
22
|
+
@iut.set_public_key(@test_server_public_key)
|
23
|
+
@iut.set_private_key(@test_server_private_key)
|
14
24
|
end
|
15
25
|
|
16
26
|
before :each do
|
17
|
-
@test_message_data = { 'recipient' => @test_server_public_key.export,
|
18
|
-
'identity' => @test_identity,
|
19
|
-
'psk' => Smaak::obfuscate_psk(@test_psk),
|
20
|
-
'expires' => Time.now.to_i + 10,
|
21
|
-
'nonce' => @test_nonce }
|
22
|
-
@message = Smaak::AuthMessage.new(Smaak::build_message(@test_message_data))
|
23
27
|
@iut.nonce_store[@test_nonce] = nil
|
24
28
|
expect(@iut.nonce_store[@test_nonce]).to eq(nil)
|
29
|
+
|
30
|
+
@test_expires = "#{Time.now.to_i + 5}"
|
31
|
+
@env = {"CONTENT_LENGTH" => "25", "REQUEST_METHOD" => "POST", "PATH_INFO" => "/gems/smaak", "HTTP_X_SMAAK_ENCRYPT" => "false", "HTTP_X_SMAAK_RECIPIENT" => Base64.strict_encode64(@test_server_public_key.export), "HTTP_X_SMAAK_IDENTIFIER" => @test_identifier, "HTTP_X_SMAAK_NONCE" => @test_nonce, "HTTP_X_SMAAK_EXPIRES" => @test_expires, "HTTP_X_SMAAK_PSK" => Smaak::Crypto::obfuscate_psk(@test_psk) }
|
32
|
+
@auth_message = mock_auth_message(@env)
|
25
33
|
end
|
26
34
|
|
27
35
|
context "when initialized" do
|
@@ -31,7 +39,36 @@ describe Smaak::Server do
|
|
31
39
|
|
32
40
|
it "should not know its own public key" do
|
33
41
|
iut = Smaak::Server.new
|
34
|
-
expect(iut.
|
42
|
+
expect(iut.key).to eq(nil)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not know its own private key" do
|
46
|
+
iut = Smaak::Server.new
|
47
|
+
expect(iut.private_key).to eq(nil)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when given a private key" do
|
52
|
+
it "should remember its private key" do
|
53
|
+
@iut.set_private_key(@test_server_private_key)
|
54
|
+
expect(@iut.private_key).to eql(@test_server_private_key)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should validate and adapt the key before assignment" do
|
58
|
+
expect(@iut).to receive(:adapt_rsa_key).with(@test_server_private_key)
|
59
|
+
@iut.set_private_key(@test_server_private_key)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when given a public key" do
|
64
|
+
it "should remember its public key" do
|
65
|
+
@iut.set_key(@test_server_public_key)
|
66
|
+
expect(@iut.key).to eql(@test_server_public_key)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should validate and adapt the key before assignment" do
|
70
|
+
expect(@iut).to receive(:adapt_rsa_key).with(@test_server_public_key)
|
71
|
+
@iut.set_private_key(@test_server_public_key)
|
35
72
|
end
|
36
73
|
end
|
37
74
|
|
@@ -69,27 +106,140 @@ describe Smaak::Server do
|
|
69
106
|
end
|
70
107
|
end
|
71
108
|
|
72
|
-
context "when asked to
|
73
|
-
|
74
|
-
@
|
75
|
-
@iut.set_public_key(@test_server_public_key.export)
|
76
|
-
@iut.add_association(@test_identity, @test_server_public_key.export, @test_psk)
|
109
|
+
context "when asked to build an AuthMessage from a request received" do
|
110
|
+
it "should decode the x-smaak-recipient header using base64 to obtain the recipient publc key" do
|
111
|
+
expect(@auth_message.recipient).to eql(@test_server_public_key.export)
|
77
112
|
end
|
78
113
|
|
79
|
-
it "should
|
80
|
-
expect(@
|
114
|
+
it "should set the psk to the x-smaak-psk header value" do
|
115
|
+
expect(@auth_message.psk).to eql(Smaak::Crypto::obfuscate_psk(@test_psk))
|
81
116
|
end
|
82
117
|
|
83
|
-
it "should
|
84
|
-
expect(@
|
118
|
+
it "should set expires to the x-smaak-expires header value" do
|
119
|
+
expect(@auth_message.expires).to eql(@test_expires)
|
85
120
|
end
|
86
121
|
|
87
|
-
it "should
|
88
|
-
expect(@
|
89
|
-
|
122
|
+
it "should set the identifier to the x-smaak-identifier header value" do
|
123
|
+
expect(@auth_message.identifier).to eql(@test_identifier)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should set the nonce to the x-smaak-nonce header value" do
|
127
|
+
expect(@auth_message.nonce).to eql(@test_nonce)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should return the auth_message" do
|
131
|
+
expect(@auth_message.is_a?(Smaak::AuthMessage)).to eql(true)
|
90
132
|
end
|
91
133
|
end
|
92
134
|
|
93
|
-
context "when asked to
|
135
|
+
context "when asked to verify an auth message" do
|
136
|
+
it "should return false if the auth message is not unique" do
|
137
|
+
@iut.nonce_store[@test_nonce] = 1
|
138
|
+
expect(@iut.verify_auth_message(@auth_message)).to eql(false)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should return false if the auth_message is not intended for the recipient" do
|
142
|
+
env = @env
|
143
|
+
env["HTTP_X_SMAAK_RECIPIENT"] = Base64.strict_encode64("another-recipient")
|
144
|
+
auth_message = mock_auth_message(env)
|
145
|
+
expect(@iut.verify_auth_message(auth_message)).to eql(false)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should return false if the auth_message's pre-shared key does not match the association's, indexed by the auth message's identifier field" do
|
149
|
+
env = @env
|
150
|
+
env["HTTP_X_SMAAK_PSK"] = "doesnotmatch"
|
151
|
+
auth_message = mock_auth_message(env)
|
152
|
+
expect(@iut.verify_auth_message(auth_message)).to eql(false)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should return true if the message successfully verifies" do
|
156
|
+
expect(@iut.verify_auth_message(@auth_message)).to eql(true)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "when asked to verify a signed request" do
|
161
|
+
it "should create an adaptor for the request" do
|
162
|
+
expect(Smaak).to receive(:create_adaptor).with(@request)
|
163
|
+
expect {
|
164
|
+
@iut.verify_signed_request(@request)
|
165
|
+
}.to raise_error
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should build an auth message using the adaptor" do
|
169
|
+
mock_adaptor = double(Smaak::RackAdaptor)
|
170
|
+
expect(Smaak).to receive(:create_adaptor).with(@request).and_return(mock_adaptor)
|
171
|
+
expect(@iut).to receive(:build_auth_message_from_request).with(mock_adaptor)
|
172
|
+
expect {
|
173
|
+
@iut.verify_signed_request(@request)
|
174
|
+
}.to raise_error
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should return false if the constructed auth message cannot be verified" do
|
178
|
+
mock_adaptor = double(Smaak::RackAdaptor)
|
179
|
+
expect(Smaak).to receive(:create_adaptor).with(@request).and_return(mock_adaptor)
|
180
|
+
expect(@iut).to receive(:build_auth_message_from_request).with(mock_adaptor).and_return(@auth_message)
|
181
|
+
expect(@iut).to receive(:verify_auth_message).and_return false
|
182
|
+
expect(@iut.verify_signed_request(@request)).to eql(false)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should decrypt the request body if the auth_message indicates encryption" do
|
186
|
+
mock_adaptor = double(Smaak::RackAdaptor)
|
187
|
+
expect(Smaak).to receive(:create_adaptor).with(@request).and_return(mock_adaptor)
|
188
|
+
expect(@iut).to receive(:build_auth_message_from_request).with(mock_adaptor).and_return(@auth_message)
|
189
|
+
expect(@iut).to receive(:verify_auth_message).and_return true
|
190
|
+
allow(@auth_message).to receive(:encrypt).and_return true
|
191
|
+
expect(mock_adaptor).to receive(:body).and_return("body")
|
192
|
+
expect(Smaak::Crypto).to receive(:sink).with("body").and_return("body")
|
193
|
+
expect(Smaak::Crypto).to receive(:decrypt).with("body", @iut.private_key)
|
194
|
+
expect {
|
195
|
+
@iut.verify_signed_request(@request)
|
196
|
+
}.to raise_error
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should return false, nil if the headers could not be authorized" do
|
200
|
+
mock_adaptor = double(Smaak::RackAdaptor)
|
201
|
+
expect(Smaak).to receive(:create_adaptor).with(@request).and_return(mock_adaptor)
|
202
|
+
expect(@iut).to receive(:build_auth_message_from_request).with(mock_adaptor).and_return(@auth_message)
|
203
|
+
expect(@iut).to receive(:verify_auth_message).and_return true
|
204
|
+
allow(@auth_message).to receive(:encrypt).and_return true
|
205
|
+
expect(mock_adaptor).to receive(:body).and_return("body")
|
206
|
+
expect(Smaak::Crypto).to receive(:sink).with("body").and_return("body")
|
207
|
+
expect(Smaak::Crypto).to receive(:decrypt).with("body", @iut.private_key).and_return("body")
|
208
|
+
expect(Smaak).to receive(:verify_authorization_headers).with(mock_adaptor, @test_server_public_key).and_return(false)
|
209
|
+
auth_message, body = @iut.verify_signed_request(@request)
|
210
|
+
expect(auth_message).to eql(false)
|
211
|
+
expect(body).to eql(nil)
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should return the auth_message and the body if successfully verified" do
|
215
|
+
mock_adaptor = double(Smaak::RackAdaptor)
|
216
|
+
expect(Smaak).to receive(:create_adaptor).with(@request).and_return(mock_adaptor)
|
217
|
+
expect(@iut).to receive(:build_auth_message_from_request).with(mock_adaptor).and_return(@auth_message)
|
218
|
+
expect(@iut).to receive(:verify_auth_message).and_return true
|
219
|
+
allow(@auth_message).to receive(:encrypt).and_return true
|
220
|
+
expect(mock_adaptor).to receive(:body).and_return("body")
|
221
|
+
expect(Smaak::Crypto).to receive(:sink).with("body").and_return("body")
|
222
|
+
expect(Smaak::Crypto).to receive(:decrypt).with("body", @iut.private_key).and_return("body")
|
223
|
+
expect(Smaak).to receive(:verify_authorization_headers).with(mock_adaptor, @test_server_public_key).and_return(true)
|
224
|
+
auth_message, body = @iut.verify_signed_request(@request)
|
225
|
+
expect(auth_message).to eql(@auth_message)
|
226
|
+
expect(body).to eql("body")
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
context "when asked to compile a response" do
|
232
|
+
it "should return the data provided if the auth_message provided does not indicate encryption" do
|
233
|
+
expect(Smaak::Crypto).not_to receive(:encrypt)
|
234
|
+
result = @iut.compile_response(@auth_message, "data")
|
235
|
+
expect(result).to eql("data")
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should return the data provided, encrypted with the public key of the associate identified by the auth_message if the auth_message provided indicates encryption" do
|
239
|
+
allow(@auth_message).to receive(:encrypt).and_return true
|
240
|
+
expect(Smaak::Crypto).to receive(:encrypt).with("data", @test_server_public_key).and_return("encrypted_data")
|
241
|
+
result = @iut.compile_response(@auth_message, "data")
|
242
|
+
expect(result).to eql("encrypted_data")
|
243
|
+
end
|
94
244
|
end
|
95
245
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
|
3
|
+
class Tester < Smaak::SmaakService
|
4
|
+
attr_reader :configured
|
5
|
+
|
6
|
+
def self.mutex
|
7
|
+
@@mutex
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.instance
|
11
|
+
@@instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def configure_service
|
15
|
+
super
|
16
|
+
@configured = true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Smaak::SmaakService do
|
21
|
+
before :all do
|
22
|
+
@iut = Tester.get_instance
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when initialized" do
|
26
|
+
it "should instantiate itself and remember it" do
|
27
|
+
expect(@iut.smaak_server.nil?).to eq(false)
|
28
|
+
expect(@iut.smaak_server.is_a?(Smaak::Server)).to eq(true)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should call configure_service as an IOC seam" do
|
32
|
+
expect(@iut.configured).to eq(true)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when asked for an instance of itself" do
|
37
|
+
it "should always return the same instance" do
|
38
|
+
a = Smaak::SmaakService.get_instance
|
39
|
+
b = Smaak::SmaakService.get_instance
|
40
|
+
c = Smaak::SmaakService.get_instance
|
41
|
+
expect(a).to eq(b)
|
42
|
+
expect(b).to eq(c)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "as a singleton" do
|
47
|
+
it "should implement the singleton pattern and be thread-safe" do
|
48
|
+
expect(Tester::mutex.is_a? Mutex).to eq(true)
|
49
|
+
expect(Tester::instance.nil?).to eq(false)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|