smaak 0.0.10 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +112 -53
- data/lib/smaak/adaptors/net_http_adaptor.rb +43 -0
- data/lib/smaak/adaptors/rack_adaptor.rb +32 -0
- data/lib/smaak/associate.rb +12 -6
- data/lib/smaak/auth_message.rb +55 -39
- data/lib/smaak/cavage_04.rb +84 -0
- data/lib/smaak/client.rb +38 -21
- data/lib/smaak/crypto.rb +45 -0
- data/lib/smaak/server.rb +33 -16
- data/lib/smaak/smaak_service.rb +29 -0
- data/lib/smaak/utils.rb +10 -0
- data/lib/smaak/version.rb +1 -1
- data/lib/smaak.rb +58 -18
- data/smaak.gemspec +1 -0
- data/spec/lib/smaak/adaptors/net_http_adaptor_spec.rb +99 -0
- data/spec/lib/smaak/adaptors/rack_adaptor_spec.rb +83 -0
- data/spec/lib/smaak/auth_message_spec.rb +78 -147
- data/spec/lib/smaak/cavage_04_spec.rb +231 -0
- data/spec/lib/smaak/client_spec.rb +147 -34
- data/spec/lib/smaak/crypto_spec.rb +94 -0
- data/spec/lib/smaak/server_spec.rb +172 -22
- data/spec/lib/smaak/smaak_service_spec.rb +52 -0
- data/spec/lib/smaak_spec.rb +130 -52
- data/spec/spec_helper.rb +3 -0
- metadata +32 -3
- data/lib/smaak/request_signing_validator.rb +0 -20
data/lib/smaak/server.rb
CHANGED
@@ -5,7 +5,7 @@ require 'smaak/auth_message'
|
|
5
5
|
module Smaak
|
6
6
|
class Server < Associate
|
7
7
|
attr_accessor :nonce_store
|
8
|
-
|
8
|
+
attr_reader :private_key
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
super
|
@@ -16,6 +16,10 @@ module Smaak
|
|
16
16
|
set_key(key)
|
17
17
|
end
|
18
18
|
|
19
|
+
def set_private_key(key)
|
20
|
+
@private_key = adapt_rsa_key(key)
|
21
|
+
end
|
22
|
+
|
19
23
|
def auth_message_unique?(auth_message)
|
20
24
|
if nonce_store[auth_message.nonce].nil?
|
21
25
|
nonce_store[auth_message.nonce] = 1
|
@@ -24,26 +28,39 @@ module Smaak
|
|
24
28
|
false
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
def build_auth_message_from_request(adaptor)
|
32
|
+
recipient_public_key = Base64.decode64(adaptor.header("x-smaak-recipient"))
|
33
|
+
psk = adaptor.header("x-smaak-psk")
|
34
|
+
expires = adaptor.header("x-smaak-expires")
|
35
|
+
identifier = adaptor.header("x-smaak-identifier")
|
36
|
+
nonce = adaptor.header("x-smaak-nonce")
|
37
|
+
encrypt = adaptor.header("x-smaak-encrypt")
|
38
|
+
auth_message = Smaak::AuthMessage.build(recipient_public_key, psk, expires, identifier, nonce, encrypt)
|
39
|
+
end
|
40
|
+
|
41
|
+
def verify_auth_message(auth_message)
|
42
|
+
return false if not auth_message_unique?(auth_message)
|
43
|
+
return false if not auth_message.intended_for_recipient?(@key.export)
|
44
|
+
identifier = auth_message.identifier
|
45
|
+
psk = @association_store[identifier]['psk']
|
46
|
+
return false if not auth_message.verify(psk)
|
47
|
+
true
|
33
48
|
end
|
34
49
|
|
35
50
|
def verify_signed_request(request)
|
36
|
-
|
37
|
-
|
51
|
+
adaptor = Smaak::create_adaptor(request)
|
52
|
+
auth_message = build_auth_message_from_request(adaptor)
|
53
|
+
return false if not verify_auth_message(auth_message)
|
54
|
+
pubkey = @association_store[auth_message.identifier]['public_key']
|
55
|
+
body = Smaak::Crypto::sink(adaptor.body)
|
56
|
+
body = Smaak::Crypto::decrypt(body, @private_key) if auth_message.encrypt
|
57
|
+
return false, nil if not Smaak::verify_authorization_headers(adaptor, pubkey)
|
58
|
+
return auth_message, body # TBD return ID from cert
|
38
59
|
end
|
39
60
|
|
40
|
-
def
|
41
|
-
return
|
42
|
-
|
43
|
-
identity = auth_message.identity
|
44
|
-
pubkey = @association_store[identity]['public_key']
|
45
|
-
psk = @association_store[identity]['psk']
|
46
|
-
auth_message.verify(signature, pubkey, psk)
|
61
|
+
def compile_response(auth_message, data)
|
62
|
+
return Smaak::Crypto::encrypt(data, @association_store[auth_message.identifier]['public_key']) if auth_message.encrypt
|
63
|
+
data
|
47
64
|
end
|
48
65
|
end
|
49
66
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'smaak.rb'
|
2
|
+
require 'smaak/server.rb'
|
3
|
+
|
4
|
+
module Smaak
|
5
|
+
class SmaakService
|
6
|
+
@@mutex = Mutex.new
|
7
|
+
@@instance = nil
|
8
|
+
attr_reader :smaak_server
|
9
|
+
|
10
|
+
def self.get_instance
|
11
|
+
@@mutex.synchronize do
|
12
|
+
if (@@instance.nil?)
|
13
|
+
@@instance = self.new
|
14
|
+
end
|
15
|
+
@@instance
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@smaak_server = Smaak::Server.new
|
21
|
+
configure_service
|
22
|
+
end
|
23
|
+
|
24
|
+
def configure_service
|
25
|
+
# @smaak_server.set_public_key(File.read('/service-provider-pub.pem'))
|
26
|
+
# @smaak_server.add_association('service-client-01', File.read('service-client-01-public.pem'), 'pre-shared-key')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/smaak/utils.rb
ADDED
data/lib/smaak/version.rb
CHANGED
data/lib/smaak.rb
CHANGED
@@ -1,34 +1,74 @@
|
|
1
|
-
require "smaak/version"
|
2
|
-
|
3
1
|
require 'openssl'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
require "smaak/version"
|
5
|
+
require "smaak/cavage_04"
|
6
|
+
require "smaak/adaptors/net_http_adaptor"
|
7
|
+
require "smaak/adaptors/rack_adaptor"
|
8
|
+
require "smaak/crypto"
|
4
9
|
|
5
10
|
module Smaak
|
6
11
|
DEFAULT_TOKEN_LIFE = 2 unless defined? DEFAULT_TOKEN_LIFE; DEFAULT_TOKEN_LIFE.freeze
|
7
12
|
|
8
|
-
|
9
|
-
|
13
|
+
@@adaptors = { Net::HTTPRequest => NetHttpAdaptor, Rack::Request => RackAdaptor}
|
14
|
+
|
15
|
+
def self.headers_to_be_signed
|
16
|
+
[ "x-smaak-recipient",
|
17
|
+
"x-smaak-identifier",
|
18
|
+
"x-smaak-psk",
|
19
|
+
"x-smaak-expires",
|
20
|
+
"x-smaak-nonce",
|
21
|
+
"x-smaak-encrypt" ]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.adaptors
|
25
|
+
@@adaptors
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.add_request_adaptor(request_clazz, adaptor_clazz)
|
29
|
+
@@adaptors[request_clazz] = adaptor_clazz
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.create_adaptor(request)
|
33
|
+
@@adaptors.each do |r, a|
|
34
|
+
return a.new(request) if request.is_a? r
|
35
|
+
end
|
36
|
+
raise ArgumentError.new("Unknown request class #{request.class}. Add an adaptor using Smaak::add_request_adaptor.")
|
10
37
|
end
|
11
38
|
|
12
|
-
def self.
|
13
|
-
|
39
|
+
def self.select_specification(adaptor, specification)
|
40
|
+
raise ArgumentError.new("Adaptor must be provided") if adaptor.nil?
|
41
|
+
return Cavage04.new(adaptor) if specification == Smaak::Cavage04::SPECIFICATION
|
42
|
+
raise ArgumentError.new("Unknown specification")
|
14
43
|
end
|
15
44
|
|
16
|
-
def self.
|
17
|
-
|
18
|
-
|
45
|
+
def self.sign_authorization_headers(key, auth_message, adaptor, specification = Smaak::Cavage04::SPECIFICATION)
|
46
|
+
specification = Smaak::select_specification(adaptor, specification)
|
47
|
+
|
48
|
+
signature_headers = specification.compile_signature_headers(auth_message)
|
49
|
+
signature_data = Smaak::Crypto::sign_data(signature_headers, key)
|
50
|
+
signature = Smaak::Crypto::encode64(signature_data)
|
51
|
+
specification.compile_auth_header(signature)
|
52
|
+
specification.adaptor
|
19
53
|
end
|
20
54
|
|
21
|
-
def self.
|
22
|
-
|
55
|
+
def self.verify_authorization_headers(adaptor, pubkey)
|
56
|
+
raise ArgumentError.new("Key is required") if pubkey.nil?
|
57
|
+
signature_headers, signature = Smaak::get_signature_data_from_request(adaptor)
|
58
|
+
return false if signature.nil?
|
59
|
+
return false if signature_headers.nil?
|
60
|
+
Smaak::Crypto::verify_signature(signature, Smaak::Crypto::encode64(signature_headers), pubkey)
|
23
61
|
end
|
24
62
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
63
|
+
private
|
64
|
+
|
65
|
+
def self.get_signature_data_from_request(adaptor, specification = Smaak::Cavage04::SPECIFICATION)
|
66
|
+
specification = Smaak::select_specification(adaptor, specification)
|
67
|
+
|
68
|
+
signature_headers = specification.extract_signature_headers
|
69
|
+
signature = specification.extract_signature
|
70
|
+
|
71
|
+
return signature_headers, Base64.decode64(signature)
|
32
72
|
end
|
33
73
|
end
|
34
74
|
|
data/smaak.gemspec
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Smaak::NetHttpAdaptor do
|
4
|
+
before :each do
|
5
|
+
@request = Net::HTTP::Post.new("http://rubygems.org:80/gems/smaak")
|
6
|
+
@request.body = "body-data"
|
7
|
+
@iut = Smaak::NetHttpAdaptor.new(@request)
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when initialized" do
|
11
|
+
it "should remember the request provided" do
|
12
|
+
expect(@iut.request).to eq(@request)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should raise an ArgumentError if request is not a Net::HTTPRequest" do
|
16
|
+
expect{
|
17
|
+
Smaak::NetHttpAdaptor.new("notarequest")
|
18
|
+
}.to raise_error ArgumentError, "Must provide a Net::HTTPRequest"
|
19
|
+
expect{
|
20
|
+
Smaak::NetHttpAdaptor.new(nil)
|
21
|
+
}.to raise_error ArgumentError, "Must provide a Net::HTTPRequest"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when asked to set a header" do
|
26
|
+
it "should raise an ArgumentError if the header is not a non-blank string" do
|
27
|
+
expect {
|
28
|
+
@iut.set_header("", "value")
|
29
|
+
}.to raise_error ArgumentError, "Header must be a non-blank string"
|
30
|
+
expect {
|
31
|
+
@iut.set_header(" ", "value")
|
32
|
+
}.to raise_error ArgumentError, "Header must be a non-blank string"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should raise and ArgumentError if the header is not a string" do
|
36
|
+
expect {
|
37
|
+
@iut.set_header({:a => 'A'}, "value")
|
38
|
+
}.to raise_error ArgumentError, "Header must be a non-blank string"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should set the header on the request" do
|
42
|
+
@iut.set_header("one", "1")
|
43
|
+
@iut.set_header("two", "2")
|
44
|
+
expect(@iut.request["one"]).to eq("1")
|
45
|
+
expect(@iut.request["two"]).to eq("2")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when asked to iterate all headers" do
|
50
|
+
it "should iterate each header if there are headers set" do
|
51
|
+
@iut.set_header("one", "1")
|
52
|
+
@iut.set_header("two", "2")
|
53
|
+
count = 0
|
54
|
+
one = false
|
55
|
+
two = false
|
56
|
+
@iut.each_header do |header, value|
|
57
|
+
count = count + 1
|
58
|
+
one = true if header == "one" and value == "1"
|
59
|
+
two = true if header == "two" and value == "2"
|
60
|
+
end
|
61
|
+
expect(count).to eq(5) #some default headers come with a vanilla request
|
62
|
+
expect(one).to eq(true)
|
63
|
+
expect(two).to eq(true)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not iterate any headers if there are no headers set" do
|
67
|
+
count = 0
|
68
|
+
@iut.each_header do |header, value|
|
69
|
+
count = count + 1
|
70
|
+
end
|
71
|
+
expect(count).to eq(3) #some default headers come with a vanilla request
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when asked for request attributes" do
|
76
|
+
it "should provide an accessor to the host" do
|
77
|
+
expect(@iut.host).to eq("rubygems.org")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should provide an accessor to the path" do
|
81
|
+
expect(@iut.path).to eq("/gems/smaak")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should provide an accessor to the method" do
|
85
|
+
expect(@iut.method).to eq("POST")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should provide an accessor to the body" do
|
89
|
+
expect(@iut.body).to eq("body-data")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "when setting the body" do
|
94
|
+
it "should set the request's body attribute" do
|
95
|
+
@iut.body = "new body"
|
96
|
+
expect(@iut.body).to eq("new body")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require './spec/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Smaak::RackAdaptor do
|
4
|
+
before :each do
|
5
|
+
@env = {"CONTENT_LENGTH" => "25", "REQUEST_METHOD" => "POST", "PATH_INFO" => "/gems/smaak", "HTTP_X_SMAAK_ENCRYPT" => "false"}
|
6
|
+
@request = Rack::Request.new(@env)
|
7
|
+
|
8
|
+
@iut = Smaak::RackAdaptor.new(@request)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "when initialized" do
|
12
|
+
it "should remember the request provided" do
|
13
|
+
expect(@iut.request).to eq(@request)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should raise an ArgumentError if request is not a Rack::Request" do
|
17
|
+
expect{
|
18
|
+
Smaak::NetHttpAdaptor.new("notarequest")
|
19
|
+
}.to raise_error ArgumentError, "Must provide a Net::HTTPRequest"
|
20
|
+
expect{
|
21
|
+
Smaak::NetHttpAdaptor.new(nil)
|
22
|
+
}.to raise_error ArgumentError, "Must provide a Net::HTTPRequest"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when asked for a header" do
|
27
|
+
it "should return an ArgumentError if the header is blank" do
|
28
|
+
expect {
|
29
|
+
@iut.header("")
|
30
|
+
}.to raise_error ArgumentError, "Header must be a non-blank string"
|
31
|
+
expect {
|
32
|
+
@iut.header(" ")
|
33
|
+
}.to raise_error ArgumentError, "Header must be a non-blank string"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should raise and ArgumentError if the header is not a string" do
|
37
|
+
expect {
|
38
|
+
@iut.header({:a => 'A'})
|
39
|
+
}.to raise_error ArgumentError, "Header must be a non-blank string"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "return the value of the requested header" do
|
43
|
+
expect(@iut.header("content-length")).to eq("25")
|
44
|
+
expect(@iut.header("x-smaak-encrypt")).to eq("false")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should translate the header from the Rack HTTP_header index in the Rack upcase" do
|
48
|
+
expect(@iut.header("x-smaak-encrypt")).to eq("false")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should translate the header from the Rack underscore index" do
|
52
|
+
expect(@iut.header("x-smaak-encrypt")).to eq("false")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should ignore header case" do
|
56
|
+
expect(@iut.header("x-SMAAK-encrypt")).to eq("false")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should understand that content-length does not have HTTP_ prepended in the rack env" do
|
60
|
+
expect(@iut.header("content-length")).to eq("25")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should understand that request-method does not have HTTP_ prepended in the rack env" do
|
64
|
+
expect(@iut.header("request-method")).to eq("POST")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when asked for request attributes" do
|
69
|
+
it "should provide an accessor to the path" do
|
70
|
+
expect(@iut.path).to eq("/gems/smaak")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should provide an accessor to the method" do
|
74
|
+
expect(@iut.method).to eq("POST")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when asked for the body" do
|
79
|
+
it "should return the request body" do
|
80
|
+
@request.body
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|