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.
@@ -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