hmac_authentication 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +36 -8
- data/lib/hmac_authentication/signature.rb +75 -20
- data/lib/hmac_authentication/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 93cb19ecbf56f6f4054b58899b122fd4dd6f0a28
|
4
|
+
data.tar.gz: 88e37b55037f15851158565db2fa11fc81175078
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3ee6450b5e285164b17d74f1a54ee216e6a1dd0853cade9775951a0ec55ddd4df8b081be4ea734792ef4dbde526a25b120654787e658bf7e27394b2b3bca90d
|
7
|
+
data.tar.gz: 0d1aa098c990d41d42d9fcf93b96404700d02388cade6308c0a981504b30c51d87c3e037326b168c94f3ba2d58b1309de45caec4d1588993bafc854a7e601fa1
|
data/README.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# hmac_authentication RubyGem
|
2
2
|
|
3
|
-
Signs and
|
3
|
+
Signs and authenticates HTTP requests based on a shared-secret HMAC signature.
|
4
|
+
|
5
|
+
Developed in parallel with the following packages for other languages:
|
6
|
+
- Go: [github.com/18F/hmacauth](https://github.com/18F/hmacauth/)
|
7
|
+
- Node.js: [hmac-authentication](https://www.npmjs.com/package/hmac-authentication)
|
8
|
+
|
9
|
+
**Warning: Repeated HTTP headers will cause an authentication failure!**
|
10
|
+
Because of the way that Ruby's
|
11
|
+
[Net::HTTPHeader.initialize_http_header](https://github.com/rubysl/rubysl-net-http/blob/2.0/lib/net/http/header.rb)
|
12
|
+
method is implemented, it will discard all but the last of a series of
|
13
|
+
repeated headers. The packages for other languages will combine repeated
|
14
|
+
headers into one. Therefore, if your Ruby service receives an signed request
|
15
|
+
from a server using one of these other modules, and the request has repeated
|
16
|
+
headers, authentication of the request will fail.
|
4
17
|
|
5
18
|
## Installation
|
6
19
|
|
@@ -13,7 +26,7 @@ gem 'hmac_authentication'
|
|
13
26
|
|
14
27
|
If you're not using Bundler, start.
|
15
28
|
|
16
|
-
##
|
29
|
+
## Authenticating incoming requests
|
17
30
|
|
18
31
|
Inject something resembling the following code fragment into your request
|
19
32
|
handling logic as the first thing that happens before the request body is
|
@@ -24,10 +37,15 @@ making the request:
|
|
24
37
|
```ruby
|
25
38
|
require 'hmac_authentication'
|
26
39
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
40
|
+
# When only used for authentication, it doesn't matter what the first argument
|
41
|
+
# is, because the hash algorithm used for authentication will be parsed from
|
42
|
+
# the incoming request signature header.
|
43
|
+
auth = HmacAuthentication::HmacAuth.new(
|
44
|
+
'sha1', secret_key, signature_header, headers)
|
45
|
+
|
46
|
+
def request_handler(request)
|
47
|
+
result, header_sig, computed_sig = auth.authenticate_request request
|
48
|
+
if result != HmacAuthentication::HmacAuth::MATCH
|
31
49
|
# Cancel the request, optionally logging the values above.
|
32
50
|
end
|
33
51
|
end
|
@@ -35,8 +53,18 @@ end
|
|
35
53
|
|
36
54
|
## Signing outgoing requests
|
37
55
|
|
38
|
-
|
39
|
-
|
56
|
+
Do something similar to the following.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
digest_name = 'sha1' # Or any other available Hash algorithm.
|
60
|
+
auth = HmacAuthentication::HmacAuth.new(
|
61
|
+
digest_name, secret_key, signature_header, headers)
|
62
|
+
|
63
|
+
def make_request(request)
|
64
|
+
// Prepare request...
|
65
|
+
auth.sign_request request
|
66
|
+
end
|
67
|
+
```
|
40
68
|
|
41
69
|
## Public domain
|
42
70
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'base64'
|
2
|
+
require 'fast_secure_compare/fast_secure_compare'
|
2
3
|
require 'openssl'
|
3
4
|
|
4
5
|
module HmacAuthentication
|
@@ -8,20 +9,74 @@ module HmacAuthentication
|
|
8
9
|
MATCH = 4
|
9
10
|
MISMATCH = 5
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
RESULT_CODE_STRINGS = %w(
|
13
|
+
NO_SIGNATURE
|
14
|
+
INVALID_FORMAT
|
15
|
+
UNSUPPORTED_ALGORITHM
|
16
|
+
MATCH
|
17
|
+
MISMATCH
|
18
|
+
)
|
14
19
|
|
15
|
-
def self.
|
16
|
-
|
17
|
-
|
18
|
-
.join("\n")
|
20
|
+
def self.result_code_to_string(code)
|
21
|
+
index = code - 1
|
22
|
+
index >= 0 ? RESULT_CODE_STRINGS[index] : nil
|
19
23
|
end
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
class HmacAuth
|
26
|
+
attr_reader :digest, :secret_key, :signature_header, :headers
|
27
|
+
|
28
|
+
def initialize(digest_name, secret_key, signature_header, headers)
|
29
|
+
@digest = HmacAuthentication.parse_digest digest_name
|
30
|
+
if digest.nil?
|
31
|
+
fail "HMAC authentication digest is not supported: #{digest_name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
@secret_key = secret_key
|
35
|
+
@signature_header = signature_header
|
36
|
+
@headers = headers
|
37
|
+
end
|
38
|
+
|
39
|
+
def string_to_sign(req)
|
40
|
+
[req.method,
|
41
|
+
signed_headers(req).join("\n"),
|
42
|
+
HmacAuthentication.hash_url(req)].join("\n") + "\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
def sign_request(req)
|
46
|
+
req[signature_header] = request_signature req
|
47
|
+
end
|
48
|
+
|
49
|
+
def request_signature(request)
|
50
|
+
request_signature_impl request, digest
|
51
|
+
end
|
52
|
+
|
53
|
+
def signature_from_header(request)
|
54
|
+
request[signature_header]
|
55
|
+
end
|
56
|
+
|
57
|
+
def authenticate_request(request)
|
58
|
+
header = signature_from_header request
|
59
|
+
return NO_SIGNATURE unless header
|
60
|
+
components = header.split ' '
|
61
|
+
return INVALID_FORMAT, header unless components.size == 2
|
62
|
+
parsed_digest = HmacAuthentication.parse_digest components.first
|
63
|
+
return UNSUPPORTED_ALGORITHM, header unless parsed_digest
|
64
|
+
computed = request_signature_impl request, parsed_digest
|
65
|
+
[HmacAuthentication.compare_signatures(header, computed),
|
66
|
+
header, computed]
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def signed_headers(request)
|
72
|
+
headers.map { |name| (request.get_fields(name) || []).join(',') }
|
73
|
+
end
|
74
|
+
|
75
|
+
def request_signature_impl(request, digest_)
|
76
|
+
hmac = OpenSSL::HMAC.new secret_key, digest_
|
77
|
+
hmac << string_to_sign(request) << (request.body || '')
|
78
|
+
digest_.name.downcase + ' ' + Base64.strict_encode64(hmac.digest)
|
79
|
+
end
|
25
80
|
end
|
26
81
|
|
27
82
|
def self.parse_digest(name)
|
@@ -30,14 +85,14 @@ module HmacAuthentication
|
|
30
85
|
nil
|
31
86
|
end
|
32
87
|
|
33
|
-
def self.
|
34
|
-
header
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
88
|
+
def self.compare_signatures(header, computed)
|
89
|
+
FastSecureCompare.compare(computed, header) ? MATCH : MISMATCH
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.hash_url(req)
|
93
|
+
result = "#{req.uri.path}"
|
94
|
+
result << '?' << req.uri.query if req.uri.query
|
95
|
+
result << '#' << req.uri.fragment if req.uri.fragment
|
96
|
+
result
|
42
97
|
end
|
43
98
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hmac_authentication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Bland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fast_secure_compare
|
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'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: go_script
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|