smaak 0.0.10 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|