smaak 0.0.10 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,88 +1,166 @@
1
1
  require './spec/spec_helper.rb'
2
+ require 'net/http'
2
3
 
3
4
  describe Smaak do
5
+ before :all do
6
+ @test_server_private_key = OpenSSL::PKey::RSA.new(4096)
7
+ @test_server_public_key = @test_server_private_key.public_key
8
+ @request = Net::HTTP::Post.new("http://rubygems.org:80/gems/smaak")
9
+ @request.body = "body-data"
10
+ @test_nonce = 1234567890
11
+ @test_expires = Time.now.to_i + 5
12
+ @test_psk = "testpresharedkey"
13
+ @test_identifier = 'test-service-1.cpt1.host-h.net'
14
+ @test_recipient = @test_server_public_key.export
15
+ @test_encrypt = true
16
+ @auth_message = Smaak::AuthMessage.new(@test_identifier, @test_nonce, @test_expires, Smaak::Crypto::obfuscate_psk(@test_psk), @test_recipient, @test_encrypt)
17
+ @adaptor = Smaak::create_adaptor(@request)
18
+ @mock_specification = Smaak::Cavage04.new(@adaptor)
19
+ end
20
+
4
21
  context "when loaded" do
5
22
  it "should specify a default token life" do
6
23
  expect(Smaak::DEFAULT_TOKEN_LIFE).to eq(2)
7
24
  end
8
25
  end
9
26
 
10
- context "when asked to obfuscate a clear-text psk" do
11
- it "should reverse the psk and appluy an MD5 hexadecimal digest to the result" do
12
- expect(Smaak::obfuscate_psk('sharedsecret')).to eq(Digest::MD5.hexdigest('sharedsecret'.reverse))
27
+ context "when asked which headers should be signed" do
28
+ it "should list all the smaak header extensions" do
29
+ expect(Smaak::headers_to_be_signed.include?("x-smaak-recipient")).to eql(true)
30
+ expect(Smaak::headers_to_be_signed.include?("x-smaak-identifier")).to eql(true)
31
+ expect(Smaak::headers_to_be_signed.include?("x-smaak-psk")).to eql(true)
32
+ expect(Smaak::headers_to_be_signed.include?("x-smaak-expires")).to eql(true)
33
+ expect(Smaak::headers_to_be_signed.include?("x-smaak-nonce")).to eql(true)
34
+ expect(Smaak::headers_to_be_signed.include?("x-smaak-encrypt")).to eql(true)
13
35
  end
14
36
  end
15
37
 
16
- context "when asked to build an auth message" do
17
- it "should base 64 encode a json representation of the message" do
18
- testm = {'a' => 'A'}
19
- expect(Smaak::build_message(testm)).to eq(Base64::encode64(testm.to_json))
38
+ context "when told about a new request adaptor" do
39
+ it "should remember the adaptor class associated with the request class" do
40
+ Smaak::add_request_adaptor(Integer, String)
41
+ expect(Smaak::adaptors[Integer]).to eql(String)
20
42
  end
21
43
  end
22
44
 
23
- context "when asked to sign a message given a private key" do
24
- it "should sign the message with the key using a 256 bit digest" do
25
- private_key = OpenSSL::PKey::RSA.new(4096)
26
- message_data = {'a' => 'B'}
27
- message = Smaak::build_message(message_data)
28
- digest = OpenSSL::Digest::SHA256.new
29
- expect(Smaak::sign_message_data(message_data, private_key)).to eq(private_key.sign(digest, message))
45
+ context "when asked to create a request adaptor" do
46
+ it "should raise an ArgumentError if the request type does not have an adaptor configured" do
47
+ expect {
48
+ Smaak::create_adaptor(0.1)
49
+ }.to raise_error ArgumentError, "Unknown request class Float. Add an adaptor using Smaak::add_request_adaptor."
50
+ end
51
+
52
+ it "should create a new instance of the adaptor class specified in the request adaptor dictionary" do
53
+ adaptor = Smaak::create_adaptor(Net::HTTP::Post.new("http://rubygems.org:80/gems/smaak"))
54
+ expect(adaptor.is_a? Smaak::NetHttpAdaptor).to eql(true)
30
55
  end
31
56
  end
32
57
 
33
- context "when asked to generate a nonce" do
34
- it "should generate a random nonce with at most 1 in a ten billion probability of a consecutive clash" do
35
- expect(SecureRandom).to receive(:random_number).with(10000000000)
36
- Smaak::generate_nonce
37
- end
38
-
39
- it "should generate a different nonce every time with high probability (less than 1 in 10000) given a history of 1000000 nonces" do
40
- repeat = {}
41
- failed = 0
42
- threshold = 1000000
43
- for i in 1..threshold do
44
- value = Smaak::generate_nonce
45
- failed = failed + 1 if repeat[value] == 1
46
- repeat[value] = 1
47
- end
48
- failed_p = (failed.to_f / threshold) * 100
49
- puts "I've seen #{failed_p} % of nonces before in #{threshold} generations"
50
- expect(failed_p < 0.01).to eq(true)
58
+ context "when asked to select a header signature specification" do
59
+ it "should raise an ArgumentError if the specification is unknown" do
60
+ expect {
61
+ Smaak::select_specification(@adaptor, "unknown specification")
62
+ }.to raise_error ArgumentError, "Unknown specification"
63
+ end
64
+
65
+ it "should return an instance of a known specification" do
66
+ expect(Smaak::select_specification(@adaptor, Smaak::Cavage04::SPECIFICATION).is_a?(Smaak::Cavage04)).to eql(true)
67
+ end
68
+
69
+ it "should raise an ArgumentError if the adaptor specified is nil" do
70
+ expect {
71
+ Smaak::select_specification(nil, Smaak::Cavage04::SPECIFICATION)
72
+ }.to raise_error ArgumentError, "Adaptor must be provided"
51
73
  end
52
74
  end
53
75
 
54
- context "when asked to compile an auth message data dictionary with addressed to an associate" do
55
- before :all do
56
- @associate_public_key = OpenSSL::PKey::RSA.new(4096).public_key
57
- @associate_psk = 'sharedsecret'
58
- @token_life = 3
59
- @identity = 'test-service'
60
- @test_data = {}
76
+ context "when asked to sign authorization headers given a key, auth_message, request adaptor and specification" do
77
+ it "should select the requested specification" do
78
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
79
+ Smaak::sign_authorization_headers(@test_server_private_key, @auth_message, @adaptor, Smaak::Cavage04::SPECIFICATION)
80
+ end
81
+
82
+ it "should compile the signature header from the auth_message using the specification" do
83
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
84
+ expect(@mock_specification).to receive(:compile_signature_headers).with(@auth_message).and_return "headers"
85
+ Smaak::sign_authorization_headers(@test_server_private_key, @auth_message, @adaptor, Smaak::Cavage04::SPECIFICATION)
86
+ end
87
+
88
+ it "should sign the signature headers using the key" do
89
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
90
+ expect(@mock_specification).to receive(:compile_signature_headers).with(@auth_message).and_return "headers"
91
+ expect(Smaak::Crypto).to receive(:sign_data).with("headers", @test_server_private_key).and_return("signed headers")
92
+ Smaak::sign_authorization_headers(@test_server_private_key, @auth_message, @adaptor, Smaak::Cavage04::SPECIFICATION)
93
+ end
94
+
95
+ it "should compile an auth header using the signature as the signature data base 64 encoded" do
96
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
97
+ expect(@mock_specification).to receive(:compile_signature_headers).with(@auth_message).and_return "headers"
98
+ expect(Smaak::Crypto).to receive(:sign_data).with("headers", @test_server_private_key).and_return("signed headers")
99
+ expect(@mock_specification).to receive(:compile_auth_header).with(Base64::strict_encode64("signed headers"))
100
+ Smaak::sign_authorization_headers(@test_server_private_key, @auth_message, @adaptor, Smaak::Cavage04::SPECIFICATION)
101
+ end
102
+
103
+ it "should return the adapter" do
104
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
105
+ expect(Smaak::sign_authorization_headers(@test_server_private_key, @auth_message, @adaptor, Smaak::Cavage04::SPECIFICATION)).to eql(@adaptor)
106
+ end
107
+ end
108
+
109
+ context "when asked if signed authorization headers are ok given a public key" do
110
+ it "should select the requested specification" do
111
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
112
+ expect {
113
+ Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)
114
+ }.to raise_error
115
+ end
116
+
117
+ it "should extract the signature headers using the specification" do
118
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
119
+ expect(@mock_specification).to receive(:extract_signature_headers).and_return "headers"
120
+ expect {
121
+ Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)
122
+ }.to raise_error
123
+ end
124
+
125
+ it "should extract the signature using the specification" do
126
+ expect(Smaak::Cavage04).to receive(:new).and_return(@mock_specification)
127
+ expect(@mock_specification).to receive(:extract_signature_headers).and_return "headers"
128
+ expect(@mock_specification).to receive(:extract_signature).and_return "signature"
129
+ Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)
61
130
  end
62
131
 
63
- before :each do
64
- @iut = Smaak::compile_auth_message_data(@associate_public_key, @associate_psk, @token_life, @identity, @test_data)
65
- @timestamp = Time.now.to_i
132
+ it "should return false if the signature is nil" do
133
+ expect(Smaak).to receive(:get_signature_data_from_request).with(@adaptor).and_return(["headers", nil])
134
+ expect(Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)).to eql(false)
66
135
  end
67
136
 
68
- it "should set the recipient to the recipient specified" do
69
- expect(@iut['recipient']).to eq(@associate_public_key.export)
137
+ it "should return false if the signature headers are nil" do
138
+ expect(Smaak).to receive(:get_signature_data_from_request).with(@adaptor).and_return([nil, "signature"])
139
+ expect(Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)).to eql(false)
70
140
  end
71
141
 
72
- it "should set the identity to the sender's identity" do
73
- expect(@iut['identity']).to eq(@identity)
142
+ it "should raise an ArgumentError if the public key is nil" do
143
+ expect {
144
+ Smaak::verify_authorization_headers(@adaptor, nil)
145
+ }.to raise_error ArgumentError, "Key is required"
74
146
  end
75
147
 
76
- it "should set the psk to the psk specified, obfuscated" do
77
- expect(@iut['psk']).to eq(Smaak::obfuscate_psk(@associate_psk))
148
+ it "should verify the signature using the signature headers base 64 encoded, with the public key. I.e. can I produce the same signature?" do
149
+ expect(Smaak).to receive(:get_signature_data_from_request).with(@adaptor).and_return(["headers", "signature"])
150
+ expect(Smaak::Crypto).to receive(:verify_signature).with("signature", Smaak::Crypto::encode64("headers"), @test_server_public_key).and_return true
151
+ Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)
78
152
  end
79
153
 
80
- it "should set the expiry to now + the token life specified" do
81
- expect(@iut['expires']).to eq(@timestamp + @token_life)
154
+ it "should return false if verification fails" do
155
+ expect(Smaak).to receive(:get_signature_data_from_request).with(@adaptor).and_return(["headers", "signature"])
156
+ expect(Smaak::Crypto).to receive(:verify_signature).with("signature", Smaak::Crypto::encode64("headers"), @test_server_public_key).and_return false
157
+ expect(Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)).to eql(false)
82
158
  end
83
159
 
84
- it "should generate and assign a nonce" do
85
- expect(@iut['nonce'] > 0).to eq(true)
160
+ it "should return true if verification succeeds" do
161
+ expect(Smaak).to receive(:get_signature_data_from_request).with(@adaptor).and_return(["headers", "signature"])
162
+ expect(Smaak::Crypto).to receive(:verify_signature).with("signature", Smaak::Crypto::encode64("headers"), @test_server_public_key).and_return true
163
+ expect(Smaak::verify_authorization_headers(@adaptor, @test_server_public_key)).to eql(true)
86
164
  end
87
165
  end
88
166
  end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,8 @@ require 'tempfile'
4
4
  require 'simplecov'
5
5
  require 'simplecov-rcov'
6
6
  require 'byebug'
7
+ require 'net/http'
8
+ require 'rack/request'
7
9
 
8
10
  # This file was generated by the `rspec --init` command. Conventionally, all
9
11
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
@@ -20,6 +22,7 @@ require 'lib/smaak/associate.rb'
20
22
  require 'lib/smaak/server.rb'
21
23
  require 'lib/smaak/client.rb'
22
24
  require 'lib/smaak/auth_message.rb'
25
+ require 'lib/smaak/smaak_service.rb'
23
26
 
24
27
  RSpec.configure do |config|
25
28
  config.run_all_when_everything_filtered = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smaak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernst van Graan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-15 00:00:00.000000000 Z
11
+ date: 2015-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: persistent-cache
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - '>='
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rack
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  description: Signed Message Authentication and Authorization with Key validation
126
140
  email:
127
141
  - ernst.van.graan@hetzner.co.za
@@ -137,17 +151,27 @@ files:
137
151
  - README.md
138
152
  - Rakefile
139
153
  - lib/smaak.rb
154
+ - lib/smaak/adaptors/net_http_adaptor.rb
155
+ - lib/smaak/adaptors/rack_adaptor.rb
140
156
  - lib/smaak/associate.rb
141
157
  - lib/smaak/auth_message.rb
158
+ - lib/smaak/cavage_04.rb
142
159
  - lib/smaak/client.rb
143
- - lib/smaak/request_signing_validator.rb
160
+ - lib/smaak/crypto.rb
144
161
  - lib/smaak/server.rb
162
+ - lib/smaak/smaak_service.rb
163
+ - lib/smaak/utils.rb
145
164
  - lib/smaak/version.rb
146
165
  - smaak.gemspec
166
+ - spec/lib/smaak/adaptors/net_http_adaptor_spec.rb
167
+ - spec/lib/smaak/adaptors/rack_adaptor_spec.rb
147
168
  - spec/lib/smaak/associate_spec.rb
148
169
  - spec/lib/smaak/auth_message_spec.rb
170
+ - spec/lib/smaak/cavage_04_spec.rb
149
171
  - spec/lib/smaak/client_spec.rb
172
+ - spec/lib/smaak/crypto_spec.rb
150
173
  - spec/lib/smaak/server_spec.rb
174
+ - spec/lib/smaak/smaak_service_spec.rb
151
175
  - spec/lib/smaak_spec.rb
152
176
  - spec/mock/request.rb
153
177
  - spec/spec_helper.rb
@@ -180,10 +204,15 @@ summary: 'This gems caters for both client and server side of a signed message i
180
204
  turned on), Replay (nonce + expires), Forgery (signature), Masquerading (recipient
181
205
  pub key check), Clear-text password compromise (MD5 pre-shared key)'
182
206
  test_files:
207
+ - spec/lib/smaak/adaptors/net_http_adaptor_spec.rb
208
+ - spec/lib/smaak/adaptors/rack_adaptor_spec.rb
183
209
  - spec/lib/smaak/associate_spec.rb
184
210
  - spec/lib/smaak/auth_message_spec.rb
211
+ - spec/lib/smaak/cavage_04_spec.rb
185
212
  - spec/lib/smaak/client_spec.rb
213
+ - spec/lib/smaak/crypto_spec.rb
186
214
  - spec/lib/smaak/server_spec.rb
215
+ - spec/lib/smaak/smaak_service_spec.rb
187
216
  - spec/lib/smaak_spec.rb
188
217
  - spec/mock/request.rb
189
218
  - spec/spec_helper.rb
@@ -1,20 +0,0 @@
1
- module Smaak
2
- class RequestSigningValidator
3
- def validate(data)
4
- raise ArgumentError.new("Request data needs to be a Hash") if not data.is_a? Hash
5
- ensure_present(data, "HTTPRequestMethod")
6
- ensure_present(data, "CanonicalURI")
7
- ensure_present(data, "CanonicalQueryString")
8
- ensure_present(data, "CanonicalHeaders")
9
- ensure_present(data, "SignedHeaders")
10
- ensure_present(data, "RequestPayload")
11
- end
12
-
13
- private
14
-
15
- def ensure_present(data, key)
16
- # raise ArgumentError.new("Request data needs to include #{key}") if data[key].nil?
17
- puts "WARNING: Request data needs to include #{key}" if data[key].nil?
18
- end
19
- end
20
- end