hmac_authentication 0.0.0 → 1.0.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/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
|