smaak 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/smaak/associate.rb +53 -0
- data/lib/smaak/auth_message.rb +74 -0
- data/lib/smaak/client.rb +33 -0
- data/lib/smaak/server.rb +33 -0
- data/lib/smaak/version.rb +3 -0
- data/lib/smaak.rb +33 -0
- data/smaak.gemspec +30 -0
- data/spec/lib/smaak/associate_spec.rb +98 -0
- data/spec/lib/smaak/auth_message_spec.rb +238 -0
- data/spec/lib/smaak/client_spec.rb +71 -0
- data/spec/lib/smaak/server_spec.rb +91 -0
- data/spec/lib/smaak_spec.rb +87 -0
- data/spec/spec_helper.rb +40 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b213ebcf6f507b5c7ecd8b92bc4ea8c5d38a9f1f
|
4
|
+
data.tar.gz: 951d7a6c199366cabbc88d386f7e05adcfbb3fe7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2022526d04a845e57d9d8837ed3105ae31c17d7e5fbab4ac2a5cc154c6c733f82c174ad958fae1beed1b4e38f36a5bac16f889f687b391cbd000301f1e99ad2f
|
7
|
+
data.tar.gz: 43307e566b241328de13db9a69d07cdd3ae1ce697d25ab18c4cc884941772d82d1d4cdb44a25c7f6930e838cf8741ecd2d61fbe9fff099891446004acfe27130
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
smaak
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p451
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Ernst van Graan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Smaak
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'smaak'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install smaak
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'persistent-cache'
|
2
|
+
|
3
|
+
module Smaak
|
4
|
+
class Associate
|
5
|
+
attr_reader :association_store
|
6
|
+
attr_reader :token_life
|
7
|
+
attr_reader :key
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@association_store = Persistent::Cache.new("association_store", nil, Persistent::Cache::STORAGE_RAM)
|
11
|
+
@token_life = Smaak::DEFAULT_TOKEN_LIFE
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_association(identity, key, psk)
|
15
|
+
the_key = key.is_a?(String) ? OpenSSL::PKey::RSA.new(key) : key
|
16
|
+
raise ArgumentError.new("Key needs to be valid") if not validate_key(the_key)
|
17
|
+
@association_store[identity] = { 'public_key' => the_key, 'psk' => psk }
|
18
|
+
|
19
|
+
rescue OpenSSL::PKey::RSAError
|
20
|
+
raise ArgumentError.new("Key needs to be valid")
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_token_life(token_life)
|
24
|
+
raise ArgumentError.new("Token life has to be a positive number of seconds") if not validate_token_life(token_life)
|
25
|
+
@token_life = token_life
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_key(key)
|
29
|
+
the_key = key.is_a?(String) ? OpenSSL::PKey::RSA.new(key) : key
|
30
|
+
raise ArgumentError.new("Key needs to be valid") if not validate_key(the_key)
|
31
|
+
@key = the_key
|
32
|
+
|
33
|
+
rescue OpenSSL::PKey::RSAError
|
34
|
+
raise ArgumentError.new("Key needs to be valid")
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def validate_key(key)
|
40
|
+
return false if key.nil?
|
41
|
+
return false if key.is_a? String and key.empty?
|
42
|
+
return false if not key.is_a? OpenSSL::PKey::RSA
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_token_life(token_life)
|
47
|
+
return false if token_life.nil?
|
48
|
+
return false if not token_life.is_a? Integer
|
49
|
+
return false if not token_life > 0
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Smaak
|
2
|
+
class AuthMessage
|
3
|
+
attr_reader :message
|
4
|
+
attr_reader :message_data
|
5
|
+
attr_reader :identity
|
6
|
+
attr_reader :nonce
|
7
|
+
|
8
|
+
def initialize(message)
|
9
|
+
raise ArgumentError.new("Message not specified") if message.nil?
|
10
|
+
@message = message.dup
|
11
|
+
@message.freeze
|
12
|
+
begin
|
13
|
+
@message_data = JSON.parse(Base64.decode64(message))
|
14
|
+
@message_data.freeze
|
15
|
+
rescue => ex
|
16
|
+
raise ArgumentError.new("Message must have valid message data")
|
17
|
+
end
|
18
|
+
raise ArgumentError.new("Message must have a valid expiry set") if not validate_expiry
|
19
|
+
@identity = @message_data['identity']
|
20
|
+
@identity.freeze
|
21
|
+
raise ArgumentError.new("Message must have a valid identity set") if @identity.nil? or @identity.empty?
|
22
|
+
@nonce = @message_data['nonce']
|
23
|
+
@nonce.freeze
|
24
|
+
raise ArgumentError.new("Message must have a valid nonce") if not validate_nonce(@nonce)
|
25
|
+
end
|
26
|
+
|
27
|
+
def expired?
|
28
|
+
@message_data['expires'].to_i < Time.now.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def signature_ok?(signature, pubkey)
|
32
|
+
return false if signature.nil?
|
33
|
+
return false if pubkey.nil?
|
34
|
+
digest = OpenSSL::Digest::SHA256.new
|
35
|
+
pubkey.verify(digest, signature, @message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def psk_match?(psk)
|
39
|
+
return false if psk.nil?
|
40
|
+
return false if @message_data['psk'].nil?
|
41
|
+
@message_data['psk'] == psk
|
42
|
+
end
|
43
|
+
|
44
|
+
def intended_for_recipient?(pubkey)
|
45
|
+
return false if pubkey.nil?
|
46
|
+
return false if @message_data['recipient'].nil?
|
47
|
+
@message_data['recipient'] == pubkey
|
48
|
+
end
|
49
|
+
|
50
|
+
def verify(signature, pubkey, psk)
|
51
|
+
return false if expired?
|
52
|
+
return false if not signature_ok?(signature, pubkey)
|
53
|
+
return false if not psk_match?(Smaak::obfuscate_psk(psk))
|
54
|
+
return false if not intended_for_recipient?(pubkey.export)
|
55
|
+
identity
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def validate_nonce(nonce)
|
61
|
+
return false if nonce.nil?
|
62
|
+
return false if nonce.to_i == 0
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_expiry
|
67
|
+
return false if @message_data.nil?
|
68
|
+
return false if not @message_data.is_a? Hash
|
69
|
+
return false if @message_data['expires'].nil?
|
70
|
+
return false if not (@message_data['expires'].to_i > 0)
|
71
|
+
true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/smaak/client.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Smaak
|
2
|
+
class Client < Associate
|
3
|
+
attr_accessor :identity
|
4
|
+
attr_accessor :private_key
|
5
|
+
|
6
|
+
def set_private_key(key)
|
7
|
+
set_key(key)
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_identity(identity)
|
11
|
+
@identity = identity
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_auth_header(associate_identity)
|
15
|
+
raise ArgumentError.new("Associate invalid") if not validate_associate(associate_identity)
|
16
|
+
associate = @association_store[associate_identity]
|
17
|
+
message_data = Smaak::compile_auth_message_data(associate['public_key'], associate['psk'], @token_life, @identity)
|
18
|
+
signature = Smaak::sign_message_data(message_data, @key)
|
19
|
+
message = Smaak::build_message(message_data)
|
20
|
+
auth_body = { 'message' => message,
|
21
|
+
'signature' => Base64.encode64(signature) }
|
22
|
+
auth = auth_body.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def validate_associate(associate_identity)
|
28
|
+
return false if associate_identity.nil?
|
29
|
+
return false if @association_store[associate_identity].nil?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/smaak/server.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'smaak/associate.rb'
|
2
|
+
|
3
|
+
module Smaak
|
4
|
+
class Server < Associate
|
5
|
+
attr_accessor :nonce_store
|
6
|
+
attr_accessor :public_key
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
@nonce_store = Persistent::Cache.new("nonce_store", @token_life, Persistent::Cache::STORAGE_RAM)
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_public_key(key)
|
14
|
+
set_key(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def auth_message_unique?(auth_message)
|
18
|
+
if nonce_store[auth_message.nonce].nil?
|
19
|
+
nonce_store[auth_message.nonce] = 1
|
20
|
+
return true
|
21
|
+
end
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def verify_auth_message(auth_message, signature)
|
26
|
+
return false if not auth_message_unique?(auth_message)
|
27
|
+
identity = auth_message.identity
|
28
|
+
pubkey = @association_store[identity]['public_key']
|
29
|
+
psk = @association_store[identity]['psk']
|
30
|
+
auth_message.verify(signature, pubkey, psk)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/smaak.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "smaak/version"
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Smaak
|
6
|
+
DEFAULT_TOKEN_LIFE = 2 unless defined? DEFAULT_TOKEN_LIFE; DEFAULT_TOKEN_LIFE.freeze
|
7
|
+
|
8
|
+
def self.obfuscate_psk(psk)
|
9
|
+
Digest::MD5.hexdigest(psk.reverse)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.build_message(message_data)
|
13
|
+
Base64.encode64(message_data.to_json)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.sign_message_data(message_data, private_key)
|
17
|
+
digest = OpenSSL::Digest::SHA256.new
|
18
|
+
private_key.sign(digest, Smaak::build_message(message_data))
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.generate_nonce
|
22
|
+
SecureRandom::random_number(10000000000)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.compile_auth_message_data(recipient_public_key, recipient_psk, token_life, identity)
|
26
|
+
{ 'recipient' => recipient_public_key.export,
|
27
|
+
'identity' => identity,
|
28
|
+
'psk' => Smaak::obfuscate_psk(recipient_psk),
|
29
|
+
'expires' => Time.now.to_i + token_life,
|
30
|
+
'nonce' => Smaak::generate_nonce }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
data/smaak.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'smaak/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "smaak"
|
8
|
+
spec.version = Smaak::VERSION
|
9
|
+
spec.authors = ["Ernst van Graan"]
|
10
|
+
spec.email = ["ernst.van.graan@hetzner.co.za"]
|
11
|
+
spec.description = %q{Signed Message Authentication and Authorization with Key validation}
|
12
|
+
spec.summary = %q{This gems caters for both client and server side of a signed message interaction over HTTP or HTTPS implementing the RFC2617 Digest Access Authentication. The following compromises are protected against as specified: Man in the middle / snooping (HTTPS turned on), Replay (nonce + expires), Forgery (signature), Masquerading (recipient pub key check), Clear-text password compromise (MD5 pre-shared key)}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
spec.required_ruby_version = '>= 2.0'
|
21
|
+
|
22
|
+
spec.add_dependency "persistent-cache", ">= 0.3.9"
|
23
|
+
spec.add_dependency "unirest"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "byebug"
|
27
|
+
spec.add_development_dependency 'simplecov'
|
28
|
+
spec.add_development_dependency 'simplecov-rcov'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
require 'smaak'
|
3
|
+
|
4
|
+
describe Smaak::Associate do
|
5
|
+
before :all do
|
6
|
+
@test_private_key = OpenSSL::PKey::RSA.new(4096)
|
7
|
+
@test_psk = "testpresharedkey"
|
8
|
+
@test_public_key = @test_private_key.public_key
|
9
|
+
end
|
10
|
+
|
11
|
+
before :each do
|
12
|
+
@iut = Smaak::Associate.new
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when initialized" do
|
16
|
+
it "should have an association store" do
|
17
|
+
expect(@iut.association_store.class).to eq(Persistent::Cache)
|
18
|
+
end
|
19
|
+
it "should default to DEFAULT_TOKEN_LIFE if a token life is not provided" do
|
20
|
+
expect(@iut.token_life).to eq(Smaak::DEFAULT_TOKEN_LIFE)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when given its own key" do
|
25
|
+
it "should remember its own key" do
|
26
|
+
@iut.set_key(@test_public_key)
|
27
|
+
expect(@iut.key).to eq(@test_public_key)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should convert its own key from string to RSA if given a string" do
|
31
|
+
@iut.set_key(@test_public_key.export)
|
32
|
+
expect(@iut.key.export).to eq(@test_public_key.export)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should raise an ArgumentError if the key is not a valid key" do
|
36
|
+
error = "Key needs to be valid"
|
37
|
+
expect {
|
38
|
+
@iut.set_key(nil)
|
39
|
+
}.to raise_error ArgumentError, error
|
40
|
+
expect {
|
41
|
+
@iut.set_key(1)
|
42
|
+
}.to raise_error ArgumentError, error
|
43
|
+
expect {
|
44
|
+
@iut.set_key("")
|
45
|
+
}.to raise_error ArgumentError, error
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when given a token life" do
|
50
|
+
it "should remember a token life provided" do
|
51
|
+
@iut.set_token_life(6)
|
52
|
+
expect(@iut.token_life).to eq(6)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should raise an ArgumentError if the token life provided is not a valid number > 0" do
|
56
|
+
error = "Token life has to be a positive number of seconds"
|
57
|
+
expect {
|
58
|
+
@iut.set_token_life(nil)
|
59
|
+
}.to raise_error ArgumentError, error
|
60
|
+
expect {
|
61
|
+
@iut.set_token_life(0)
|
62
|
+
}.to raise_error ArgumentError, error
|
63
|
+
expect {
|
64
|
+
@iut.set_token_life(-1)
|
65
|
+
}.to raise_error ArgumentError, error
|
66
|
+
expect {
|
67
|
+
@iut.set_token_life("1")
|
68
|
+
}.to raise_error ArgumentError, error
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when told about an association" do
|
73
|
+
error = "Key needs to be valid"
|
74
|
+
it "should raise an ArgumentError if the association does not have a valid key" do
|
75
|
+
expect {
|
76
|
+
@iut.add_association("test", nil, "psk")
|
77
|
+
}.to raise_error ArgumentError, error
|
78
|
+
expect {
|
79
|
+
@iut.add_association("test", 1, "psk")
|
80
|
+
}.to raise_error ArgumentError, error
|
81
|
+
expect {
|
82
|
+
@iut.add_association("test", "", "psk")
|
83
|
+
}.to raise_error ArgumentError, error
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should remember the association's name, public key and pre-shared key" do
|
87
|
+
@iut.add_association("test", @test_public_key, @test_psk)
|
88
|
+
expect(@iut.association_store["test"].nil?).to eq(false)
|
89
|
+
expect(@iut.association_store["test"]["public_key"]).to eq(@test_public_key)
|
90
|
+
expect(@iut.association_store["test"]["psk"]).to eq(@test_psk)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should convert string keys into RSA keys" do
|
94
|
+
@iut.add_association("test", @test_public_key.export, @test_psk)
|
95
|
+
expect(@iut.association_store["test"]["public_key"].export).to eq(@test_public_key.export)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
require 'smaak'
|
3
|
+
|
4
|
+
describe Smaak::AuthMessage do
|
5
|
+
before :all do
|
6
|
+
@test_nonce = 1234567890
|
7
|
+
@test_server_private_key = OpenSSL::PKey::RSA.new(4096)
|
8
|
+
@test_psk = "testpresharedkey"
|
9
|
+
@test_server_public_key = @test_server_private_key.public_key
|
10
|
+
@test_identity = 'test-service'
|
11
|
+
end
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
@test_message_data = { 'recipient' => @test_server_public_key.export,
|
15
|
+
'identity' => @test_identity,
|
16
|
+
'psk' => Smaak::obfuscate_psk(@test_psk),
|
17
|
+
'expires' => Time.now.to_i + 10,
|
18
|
+
'nonce' => @test_nonce }
|
19
|
+
@test_message = Smaak::build_message(@test_message_data)
|
20
|
+
|
21
|
+
@iut = Smaak::AuthMessage.new(@test_message)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when initialized" do
|
25
|
+
it "should raise an ArgumentError if no message is provided" do
|
26
|
+
expect {
|
27
|
+
Smaak::AuthMessage.new(nil)
|
28
|
+
}.to raise_error ArgumentError, "Message not specified"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should remember the message provided" do
|
32
|
+
expect(@iut.message).to eq(@test_message)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should unpack the message data using Base64::decode and JSON.parse" do
|
36
|
+
expect(@iut.message_data).to eq(@test_message_data)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise an ArgumentError if the message does not have a valid expiry set" do
|
40
|
+
error = "Message must have a valid expiry set"
|
41
|
+
|
42
|
+
expired_data = @test_message_data.dup
|
43
|
+
|
44
|
+
expect {
|
45
|
+
expired_data['expires'] = ""
|
46
|
+
Smaak::AuthMessage.new(Smaak::build_message(expired_data))
|
47
|
+
}.to raise_error ArgumentError, error
|
48
|
+
expect {
|
49
|
+
expired_data['expires'] = nil
|
50
|
+
Smaak::AuthMessage.new(Smaak::build_message(expired_data))
|
51
|
+
}.to raise_error ArgumentError, error
|
52
|
+
expect {
|
53
|
+
expired_data['expires'] = 'sometimes'
|
54
|
+
Smaak::AuthMessage.new(Smaak::build_message(expired_data))
|
55
|
+
}.to raise_error ArgumentError, error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should prevent its message from being changed" do
|
59
|
+
expect{
|
60
|
+
@iut.message = ""
|
61
|
+
}.to raise_error NoMethodError
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should prevent its message data from being changed" do
|
65
|
+
expect{
|
66
|
+
@iut.message_data = ""
|
67
|
+
}.to raise_error NoMethodError
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should extract the identity from the message data and remember it" do
|
71
|
+
expect(@iut.identity).to eq("test-service")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should prevent its identity from being changed" do
|
75
|
+
expect{
|
76
|
+
@iut.identity = ""
|
77
|
+
}.to raise_error NoMethodError
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should raise an ArgumentError if the message does not have a valid nonce set" do
|
81
|
+
error = "Message must have a valid nonce"
|
82
|
+
|
83
|
+
noncense_data = @test_message_data.dup
|
84
|
+
|
85
|
+
expect {
|
86
|
+
noncense_data['nonce'] = "broken"
|
87
|
+
Smaak::AuthMessage.new(Smaak::build_message(noncense_data))
|
88
|
+
}.to raise_error ArgumentError, error
|
89
|
+
expect {
|
90
|
+
noncense_data['nonce'] = nil
|
91
|
+
Smaak::AuthMessage.new(Smaak::build_message(noncense_data))
|
92
|
+
}.to raise_error ArgumentError, error
|
93
|
+
expect {
|
94
|
+
noncense_data['nonce'] = 0
|
95
|
+
Smaak::AuthMessage.new(Smaak::build_message(noncense_data))
|
96
|
+
}.to raise_error ArgumentError, error
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should extract the nonce from the message data and remember it" do
|
100
|
+
expect(@iut.nonce).to eq(@test_nonce)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should prevent its nonce from being changed" do
|
104
|
+
expect{
|
105
|
+
@iut.nonce = ""
|
106
|
+
}.to raise_error NoMethodError
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should raise an ArgumentError if the message data does not contain an identity" do
|
110
|
+
error = "Message must have a valid identity set"
|
111
|
+
anonymous_data = @test_message_data.dup
|
112
|
+
anonymous_data['identity'] = nil
|
113
|
+
expect {
|
114
|
+
Smaak::AuthMessage.new(Smaak::build_message(anonymous_data))
|
115
|
+
}.to raise_error ArgumentError, error
|
116
|
+
anonymous_data['identity'] = ""
|
117
|
+
expect {
|
118
|
+
Smaak::AuthMessage.new(Smaak::build_message(anonymous_data))
|
119
|
+
}.to raise_error ArgumentError, error
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "when asked if it has expired" do
|
124
|
+
it "should return true if the current timestamp exceeds that of the message expiry" do
|
125
|
+
expired_data = @test_message_data.dup
|
126
|
+
expired_data['expires'] = Time.now - 1
|
127
|
+
iut = Smaak::AuthMessage.new(Smaak::build_message(expired_data))
|
128
|
+
expect(iut.expired?).to eq(true)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should return false if the current timestamp does not exceed that of the message expiry" do
|
132
|
+
expect(@iut.expired?).to eq(false)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "when asked if the signature is ok given a signature and public key" do
|
137
|
+
before :each do
|
138
|
+
@test_signature = Smaak::sign_message_data(@test_message_data, @test_server_private_key)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should return false if the public key provided is not the correct key for the signature" do
|
142
|
+
mismatched = OpenSSL::PKey::RSA.new(4096).public_key
|
143
|
+
expect(@iut.signature_ok?(@test_signature, mismatched)).to eq(false)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should return false if the signature is nil" do
|
147
|
+
expect(@iut.signature_ok?(nil, @test_server_public_key)).to eq(false)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should return false if the public key is nil" do
|
151
|
+
expect(@iut.signature_ok?(@test_signature, nil)).to eq(false)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should return false if OpenSSL cannot verify the message against the signature using the public key" do
|
155
|
+
broken = @test_message_data
|
156
|
+
broken['identity'] = 'suspect-identity'
|
157
|
+
iut = Smaak::AuthMessage.new(Smaak::build_message(broken))
|
158
|
+
expect(iut.signature_ok?(@test_signature, @test_server_public_key)).to eq(false)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should return true if OpenSSL succesfully verifies message against the signature using the public key" do
|
162
|
+
expect(@iut.signature_ok?(@test_signature, @test_server_public_key)).to eq(true)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when asked whether a message's psk matched" do
|
167
|
+
it "should return false if no psk was provided" do
|
168
|
+
expect(@iut.psk_match?(nil)).to eq(false)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should return false if the message data does not include a psk" do
|
172
|
+
broken = @test_message_data
|
173
|
+
broken['psk'] = nil
|
174
|
+
iut = Smaak::AuthMessage.new(Smaak::build_message(broken))
|
175
|
+
expect(iut.psk_match?(@test_psk)).to eq(false)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should return false if the PSKs do not match" do
|
179
|
+
expect(@iut.psk_match?("doesnotmatch")).to eq(false)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should return true if the PSKs do match" do
|
183
|
+
expect(@iut.psk_match?(@test_psk)).to eq(false)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "when asked whether this message is intended for a recipient, identified by public key" do
|
188
|
+
it "should return false if the recipient does not match the public key specified" do
|
189
|
+
mismatched = OpenSSL::PKey::RSA.new(4096).public_key
|
190
|
+
expect(@iut.intended_for_recipient?(mismatched.export)).to eq (false)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should return false if the message_data does not include a recipient" do
|
194
|
+
broken = @test_message_data
|
195
|
+
broken['recipient'] = nil
|
196
|
+
iut = Smaak::AuthMessage.new(Smaak::build_message(broken))
|
197
|
+
expect(iut.intended_for_recipient?(@test_server_public_key.export)).to eq (false)
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should return false if the public key is not specified" do
|
201
|
+
expect(@iut.intended_for_recipient?(nil)).to eq (false)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should return true if the recipient matches the public key specified" do
|
205
|
+
expect(@iut.intended_for_recipient?(@test_server_public_key.export)).to eq (true)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context "when asked to verify the message using a signature" do
|
210
|
+
before :each do
|
211
|
+
@test_signature = Smaak::sign_message_data(@test_message_data, @test_server_private_key)
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should check message expiry return false if the message has expired" do
|
215
|
+
expect(@iut).to(receive(:expired?)).and_return(true)
|
216
|
+
expect(@iut.verify(@test_signature, @test_server_public_key, @test_psk)).to eq(false)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should check the signature and return false if not OK" do
|
220
|
+
expect(@iut).to(receive(:signature_ok?)).and_return(false)
|
221
|
+
expect(@iut.verify(@test_signature, @test_server_public_key, @test_psk)).to eq(false)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should try and match the PSK and return false if it cannot" do
|
225
|
+
expect(@iut).to(receive(:psk_match?)).and_return(false)
|
226
|
+
expect(@iut.verify(@test_signature, @test_server_public_key, @test_psk)).to eq(false)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should ensure the message is intended for this service and return false if it is not" do
|
230
|
+
expect(@iut).to(receive(:intended_for_recipient?)).and_return(false)
|
231
|
+
expect(@iut.verify(@test_signature, @test_server_public_key, @test_psk)).to eq(false)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should return the identity specified in the message if the message was successfully verified" do
|
235
|
+
expect(@iut.verify(@test_signature, @test_server_public_key, @test_psk)).to eq(@test_identity)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
require 'smaak'
|
3
|
+
|
4
|
+
describe Smaak::Client do
|
5
|
+
before :all do
|
6
|
+
@test_service_identity = 'service-to-talk-to'
|
7
|
+
@test_service_psk = 'testsharedsecret'
|
8
|
+
@test_client_private_key = OpenSSL::PKey::RSA.new(4096)
|
9
|
+
@test_service_private_key = OpenSSL::PKey::RSA.new(4096)
|
10
|
+
@test_service_public_key = @test_service_private_key.public_key
|
11
|
+
@iut = Smaak::Client.new
|
12
|
+
@test_identity = 'test-client'
|
13
|
+
@test_token_life = 5
|
14
|
+
@iut.set_identity(@test_identity)
|
15
|
+
@iut.set_private_key(@test_client_private_key)
|
16
|
+
@iut.set_token_life(@test_token_life)
|
17
|
+
@iut.add_association(@test_service_identity, @test_service_public_key, @test_service_psk)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when given an identity" do
|
21
|
+
it "should remember an identity provided" do
|
22
|
+
iut = Smaak::Client.new
|
23
|
+
expect(iut.identity).to eq(nil)
|
24
|
+
iut.set_identity('test-client')
|
25
|
+
expect(iut.identity).to eq('test-client')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when asked to build an auth header destined for an associate" do
|
30
|
+
it "should raise an ArgumentError if the associate is unknown" do
|
31
|
+
expect{
|
32
|
+
@iut.build_auth_header("unknown")
|
33
|
+
}.to raise_error ArgumentError, "Associate invalid"
|
34
|
+
expect{
|
35
|
+
@iut.build_auth_header(nil)
|
36
|
+
}.to raise_error ArgumentError, "Associate invalid"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should compile the auth message data using the associate details" do
|
40
|
+
expect(Smaak).to receive(:compile_auth_message_data).with(@test_service_public_key, @test_service_psk, @test_token_life, @test_identity)
|
41
|
+
@iut.build_auth_header(@test_service_identity)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should build the message" do
|
45
|
+
expect(Smaak).to receive(:generate_nonce).twice().and_return(12345)
|
46
|
+
test_message_data = Smaak::compile_auth_message_data(@test_service_public_key, @test_service_psk, @test_token_life, @test_identity)
|
47
|
+
expect(Smaak).to receive(:build_message).with(test_message_data)
|
48
|
+
expect{
|
49
|
+
@iut.build_auth_header(@test_service_identity)
|
50
|
+
}.to raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should sign the message" do
|
54
|
+
expect(Smaak).to receive(:generate_nonce).twice().and_return(12345)
|
55
|
+
test_message_data = Smaak::compile_auth_message_data(@test_service_public_key, @test_service_psk, @test_token_life, @test_identity)
|
56
|
+
expect(Smaak).to receive(:sign_message_data).with(test_message_data, @test_client_private_key)
|
57
|
+
expect{
|
58
|
+
@iut.build_auth_header(@test_service_identity)
|
59
|
+
}.to raise_error
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should place the message and its signature (encoded base64) in a JSON dictionary" do
|
63
|
+
expect(Smaak).to receive(:generate_nonce).twice().and_return(12345)
|
64
|
+
test_message_data = Smaak::compile_auth_message_data(@test_service_public_key, @test_service_psk, @test_token_life, @test_identity)
|
65
|
+
test_signature = Smaak::sign_message_data(test_message_data, @test_client_private_key)
|
66
|
+
test_message = Smaak::build_message(test_message_data)
|
67
|
+
test_envelope = { 'message' => test_message, 'signature' => Base64.encode64(test_signature) }.to_json
|
68
|
+
expect(@iut.build_auth_header(@test_service_identity)).to eq(test_envelope)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
require 'smaak'
|
3
|
+
|
4
|
+
describe Smaak::Server do
|
5
|
+
before :all do
|
6
|
+
@iut = Smaak::Server.new
|
7
|
+
@iut.set_token_life(2)
|
8
|
+
@test_nonce = 1234567890
|
9
|
+
@test_server_private_key = OpenSSL::PKey::RSA.new(4096)
|
10
|
+
@test_psk = "testpresharedkey"
|
11
|
+
@test_server_public_key = @test_server_private_key.public_key
|
12
|
+
@test_identity = 'test-service'
|
13
|
+
end
|
14
|
+
|
15
|
+
before :each do
|
16
|
+
@test_message_data = { 'recipient' => @test_server_public_key.export,
|
17
|
+
'identity' => @test_identity,
|
18
|
+
'psk' => Smaak::obfuscate_psk(@test_psk),
|
19
|
+
'expires' => Time.now.to_i + 10,
|
20
|
+
'nonce' => @test_nonce }
|
21
|
+
@message = Smaak::AuthMessage.new(Smaak::build_message(@test_message_data))
|
22
|
+
@iut.nonce_store[@test_nonce] = nil
|
23
|
+
expect(@iut.nonce_store[@test_nonce]).to eq(nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when initialized" do
|
27
|
+
it "should have a nonce store" do
|
28
|
+
expect(@iut.nonce_store.class).to eq(Persistent::Cache)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not know its own public key" do
|
32
|
+
iut = Smaak::Server.new
|
33
|
+
expect(iut.public_key).to eq(nil)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when preventing replay attacks" do
|
38
|
+
it "should forget about nonces older than token_life" do
|
39
|
+
nonces = @iut.nonce_store
|
40
|
+
nonces[@test_nonce] = 1
|
41
|
+
expect(nonces[@test_nonce]).to eq(1)
|
42
|
+
sleep @iut.token_life
|
43
|
+
expect(nonces[@test_nonce]).to eq(nil)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should remember nonces younger than token_life" do
|
47
|
+
nonces = @iut.nonce_store
|
48
|
+
nonces[@test_nonce] = 1
|
49
|
+
expect(nonces[@test_nonce]).to eq(1)
|
50
|
+
sleep @iut.token_life - 1
|
51
|
+
expect(nonces[@test_nonce]).to eq(1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when asked if a message is unique" do
|
56
|
+
it "should store a nonce once it has seen it" do
|
57
|
+
@iut.auth_message_unique?(@message)
|
58
|
+
expect(@iut.nonce_store[@test_nonce]).to eq(1)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return true if the message nonce was not seen in the last token_life period" do
|
62
|
+
expect(@iut.auth_message_unique?(@message)).to eq(true)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should return false if the message was seen in the last token_life period" do
|
66
|
+
expect(@iut.auth_message_unique?(@message)).to eq(true)
|
67
|
+
expect(@iut.auth_message_unique?(@message)).to eq(false)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when asked to verify a message given a signature" do
|
72
|
+
before :each do
|
73
|
+
@signature = Smaak::sign_message_data(@test_message_data, @test_server_private_key)
|
74
|
+
@iut.set_public_key(@test_server_public_key.export)
|
75
|
+
@iut.add_association(@test_identity, @test_server_public_key.export, @test_psk)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should look up the identity specified in the message and retrieve its public key from the associations store" do
|
79
|
+
expect(@iut.verify_auth_message(@message, @signature)).to eq(@test_identity)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should look up the identity specified in the message and retrieve its PSK from the associations store" do
|
83
|
+
expect(@iut.verify_auth_message(@message, @signature)).to eq(@test_identity)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should check nonce uniqueness and return false if not unique" do
|
87
|
+
expect(@iut.auth_message_unique?(@message)).to eq(true)
|
88
|
+
expect(@iut.verify_auth_message(@message, @signature)).to eq(false)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Smaak do
|
4
|
+
context "when loaded" do
|
5
|
+
it "should specify a default token life" do
|
6
|
+
expect(Smaak::DEFAULT_TOKEN_LIFE).to eq(2)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
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))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
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))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
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))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
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)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
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
|
+
end
|
61
|
+
|
62
|
+
before :each do
|
63
|
+
@iut = Smaak::compile_auth_message_data(@associate_public_key, @associate_psk, @token_life, @identity)
|
64
|
+
@timestamp = Time.now.to_i
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should set the recipient to the recipient specified" do
|
68
|
+
expect(@iut['recipient']).to eq(@associate_public_key.export)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should set the identity to the sender's identity" do
|
72
|
+
expect(@iut['identity']).to eq(@identity)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should set the psk to the psk specified, obfuscated" do
|
76
|
+
expect(@iut['psk']).to eq(Smaak::obfuscate_psk(@associate_psk))
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should set the expiry to now + the token life specified" do
|
80
|
+
expect(@iut['expires']).to eq(@timestamp + @token_life)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should generate and assign a nonce" do
|
84
|
+
expect(@iut['nonce'] > 0).to eq(true)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rspec/mocks'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'simplecov'
|
5
|
+
require 'simplecov-rcov'
|
6
|
+
require 'byebug'
|
7
|
+
|
8
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
9
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
10
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
11
|
+
# loaded once.
|
12
|
+
#
|
13
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
14
|
+
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'smaak'))
|
16
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..'))
|
17
|
+
|
18
|
+
require 'lib/smaak.rb'
|
19
|
+
require 'lib/smaak/associate.rb'
|
20
|
+
require 'lib/smaak/server.rb'
|
21
|
+
require 'lib/smaak/client.rb'
|
22
|
+
require 'lib/smaak/auth_message.rb'
|
23
|
+
|
24
|
+
RSpec.configure do |config|
|
25
|
+
config.run_all_when_everything_filtered = true
|
26
|
+
config.filter_run :focus
|
27
|
+
#config.expect_with(:rspec) { |c| c.syntax = :should }
|
28
|
+
|
29
|
+
# Run specs in random order to surface order dependencies. If you find an
|
30
|
+
# order dependency and want to debug it, you can fix the order by providing
|
31
|
+
# the seed, which is printed after each run.
|
32
|
+
# --seed 1234
|
33
|
+
config.order = 'random'
|
34
|
+
end
|
35
|
+
|
36
|
+
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
|
37
|
+
SimpleCov.start do
|
38
|
+
add_filter "/spec/"
|
39
|
+
end
|
40
|
+
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smaak
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ernst van Graan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: persistent-cache
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.9
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.3.9
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: unirest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov-rcov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Signed Message Authentication and Authorization with Key validation
|
126
|
+
email:
|
127
|
+
- ernst.van.graan@hetzner.co.za
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- .gitignore
|
133
|
+
- .ruby-gemset
|
134
|
+
- .ruby-version
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE.txt
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- lib/smaak.rb
|
140
|
+
- lib/smaak/associate.rb
|
141
|
+
- lib/smaak/auth_message.rb
|
142
|
+
- lib/smaak/client.rb
|
143
|
+
- lib/smaak/server.rb
|
144
|
+
- lib/smaak/version.rb
|
145
|
+
- smaak.gemspec
|
146
|
+
- spec/lib/smaak/associate_spec.rb
|
147
|
+
- spec/lib/smaak/auth_message_spec.rb
|
148
|
+
- spec/lib/smaak/client_spec.rb
|
149
|
+
- spec/lib/smaak/server_spec.rb
|
150
|
+
- spec/lib/smaak_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
homepage: ''
|
153
|
+
licenses:
|
154
|
+
- MIT
|
155
|
+
metadata: {}
|
156
|
+
post_install_message:
|
157
|
+
rdoc_options: []
|
158
|
+
require_paths:
|
159
|
+
- lib
|
160
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - '>='
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '2.0'
|
165
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - '>='
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0'
|
170
|
+
requirements: []
|
171
|
+
rubyforge_project:
|
172
|
+
rubygems_version: 2.0.14
|
173
|
+
signing_key:
|
174
|
+
specification_version: 4
|
175
|
+
summary: 'This gems caters for both client and server side of a signed message interaction
|
176
|
+
over HTTP or HTTPS implementing the RFC2617 Digest Access Authentication. The following
|
177
|
+
compromises are protected against as specified: Man in the middle / snooping (HTTPS
|
178
|
+
turned on), Replay (nonce + expires), Forgery (signature), Masquerading (recipient
|
179
|
+
pub key check), Clear-text password compromise (MD5 pre-shared key)'
|
180
|
+
test_files:
|
181
|
+
- spec/lib/smaak/associate_spec.rb
|
182
|
+
- spec/lib/smaak/auth_message_spec.rb
|
183
|
+
- spec/lib/smaak/client_spec.rb
|
184
|
+
- spec/lib/smaak/server_spec.rb
|
185
|
+
- spec/lib/smaak_spec.rb
|
186
|
+
- spec/spec_helper.rb
|