smaak 0.0.1
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 +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
|