http_signature 0.1.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 +5 -5
- data/.github/workflows/ci.yml +32 -0
- data/.github/workflows/standardrb.yml +15 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +143 -10
- data/README.md +78 -118
- data/Rakefile +3 -3
- data/bin/standardrb +16 -0
- data/http_signature.gemspec +26 -18
- data/lib/http_signature/faraday.rb +10 -8
- data/lib/http_signature/rack.rb +30 -47
- data/lib/http_signature/rails.rb +67 -0
- data/lib/http_signature/version.rb +1 -1
- data/lib/http_signature.rb +348 -133
- metadata +80 -12
- data/.circleci/config.yml +0 -0
- data/.rubocop.yml +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0d4b1e73dff1838a018f5e0d5bef1c6a6a14c2b8ef552dbbfd0481ac85cb03b7
|
|
4
|
+
data.tar.gz: 1a087ded86d8f84213984d9bd56fb55462f65feb9abc876514249f12fc5166ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7796499163d70782ea0fd809316c39a4d2a933cf01cc88f6daaa88d673607e9a138643d666fc5080063ea0bdb1068d32c48642fb487c59d6edcb7626fba7ad8e
|
|
7
|
+
data.tar.gz: 1603d71ad33fc087e456949de3e1534f3cf08c894a3fe10a3f456f6751409ddbbda247a53511d201866cf10f629e2c676fbe2eaa7cb0e0535dae81a2cad6e2a2
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Set up Ruby
|
|
16
|
+
uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: '3.4'
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Run tests with coverage
|
|
22
|
+
env:
|
|
23
|
+
COVERAGE: "true"
|
|
24
|
+
run: bundle exec rake test
|
|
25
|
+
|
|
26
|
+
- name: Upload coverage artifact
|
|
27
|
+
uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: coverage-html
|
|
30
|
+
path: coverage/
|
|
31
|
+
if-no-files-found: warn
|
|
32
|
+
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3.4.8
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,26 +1,159 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
http_signature (0.0
|
|
4
|
+
http_signature (1.0.0)
|
|
5
|
+
base64
|
|
5
6
|
|
|
6
7
|
GEM
|
|
7
8
|
remote: https://rubygems.org/
|
|
8
9
|
specs:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
actionpack (8.1.1)
|
|
11
|
+
actionview (= 8.1.1)
|
|
12
|
+
activesupport (= 8.1.1)
|
|
13
|
+
nokogiri (>= 1.8.5)
|
|
14
|
+
rack (>= 2.2.4)
|
|
15
|
+
rack-session (>= 1.0.1)
|
|
16
|
+
rack-test (>= 0.6.3)
|
|
17
|
+
rails-dom-testing (~> 2.2)
|
|
18
|
+
rails-html-sanitizer (~> 1.6)
|
|
19
|
+
useragent (~> 0.16)
|
|
20
|
+
actionview (8.1.1)
|
|
21
|
+
activesupport (= 8.1.1)
|
|
22
|
+
builder (~> 3.1)
|
|
23
|
+
erubi (~> 1.11)
|
|
24
|
+
rails-dom-testing (~> 2.2)
|
|
25
|
+
rails-html-sanitizer (~> 1.6)
|
|
26
|
+
activesupport (8.1.1)
|
|
27
|
+
base64
|
|
28
|
+
bigdecimal
|
|
29
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
30
|
+
connection_pool (>= 2.2.5)
|
|
31
|
+
drb
|
|
32
|
+
i18n (>= 1.6, < 2)
|
|
33
|
+
json
|
|
34
|
+
logger (>= 1.4.2)
|
|
35
|
+
minitest (>= 5.1)
|
|
36
|
+
securerandom (>= 0.3)
|
|
37
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
38
|
+
uri (>= 0.13.1)
|
|
39
|
+
ast (2.4.3)
|
|
40
|
+
base64 (0.3.0)
|
|
41
|
+
bigdecimal (4.0.1)
|
|
42
|
+
builder (3.3.0)
|
|
43
|
+
concurrent-ruby (1.3.6)
|
|
44
|
+
connection_pool (3.0.2)
|
|
45
|
+
crass (1.0.6)
|
|
46
|
+
docile (1.4.1)
|
|
47
|
+
drb (2.2.3)
|
|
48
|
+
erubi (1.13.1)
|
|
49
|
+
faraday (2.14.0)
|
|
50
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
51
|
+
json
|
|
52
|
+
logger
|
|
53
|
+
faraday-net_http (3.4.2)
|
|
54
|
+
net-http (~> 0.5)
|
|
55
|
+
i18n (1.14.8)
|
|
56
|
+
concurrent-ruby (~> 1.0)
|
|
57
|
+
json (2.18.0)
|
|
58
|
+
language_server-protocol (3.17.0.5)
|
|
59
|
+
lint_roller (1.1.0)
|
|
60
|
+
logger (1.7.0)
|
|
61
|
+
loofah (2.25.0)
|
|
62
|
+
crass (~> 1.0.2)
|
|
63
|
+
nokogiri (>= 1.12.0)
|
|
64
|
+
minitest (6.0.0)
|
|
65
|
+
prism (~> 1.5)
|
|
66
|
+
net-http (0.9.1)
|
|
67
|
+
uri (>= 0.11.1)
|
|
68
|
+
nokogiri (1.19.0-arm64-darwin)
|
|
69
|
+
racc (~> 1.4)
|
|
70
|
+
nokogiri (1.19.0-x86_64-linux-gnu)
|
|
71
|
+
racc (~> 1.4)
|
|
72
|
+
parallel (1.27.0)
|
|
73
|
+
parser (3.3.10.0)
|
|
74
|
+
ast (~> 2.4.1)
|
|
75
|
+
racc
|
|
76
|
+
prism (1.7.0)
|
|
77
|
+
racc (1.8.1)
|
|
78
|
+
rack (3.2.4)
|
|
79
|
+
rack-session (2.1.1)
|
|
80
|
+
base64 (>= 0.1.0)
|
|
81
|
+
rack (>= 3.0.0)
|
|
82
|
+
rack-test (2.2.0)
|
|
83
|
+
rack (>= 1.3)
|
|
84
|
+
rails-dom-testing (2.3.0)
|
|
85
|
+
activesupport (>= 5.0.0)
|
|
86
|
+
minitest
|
|
87
|
+
nokogiri (>= 1.6)
|
|
88
|
+
rails-html-sanitizer (1.6.2)
|
|
89
|
+
loofah (~> 2.21)
|
|
90
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
91
|
+
rainbow (3.1.1)
|
|
92
|
+
rake (13.3.1)
|
|
93
|
+
regexp_parser (2.11.3)
|
|
94
|
+
rubocop (1.81.7)
|
|
95
|
+
json (~> 2.3)
|
|
96
|
+
language_server-protocol (~> 3.17.0.2)
|
|
97
|
+
lint_roller (~> 1.1.0)
|
|
98
|
+
parallel (~> 1.10)
|
|
99
|
+
parser (>= 3.3.0.2)
|
|
100
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
101
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
102
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
103
|
+
ruby-progressbar (~> 1.7)
|
|
104
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
105
|
+
rubocop-ast (1.49.0)
|
|
106
|
+
parser (>= 3.3.7.2)
|
|
107
|
+
prism (~> 1.7)
|
|
108
|
+
rubocop-performance (1.26.1)
|
|
109
|
+
lint_roller (~> 1.1)
|
|
110
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
111
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
112
|
+
ruby-progressbar (1.13.0)
|
|
113
|
+
securerandom (0.4.1)
|
|
114
|
+
simplecov (0.22.0)
|
|
115
|
+
docile (~> 1.1)
|
|
116
|
+
simplecov-html (~> 0.11)
|
|
117
|
+
simplecov_json_formatter (~> 0.1)
|
|
118
|
+
simplecov-html (0.13.2)
|
|
119
|
+
simplecov_json_formatter (0.1.4)
|
|
120
|
+
standard (1.52.0)
|
|
121
|
+
language_server-protocol (~> 3.17.0.2)
|
|
122
|
+
lint_roller (~> 1.0)
|
|
123
|
+
rubocop (~> 1.81.7)
|
|
124
|
+
standard-custom (~> 1.0.0)
|
|
125
|
+
standard-performance (~> 1.8)
|
|
126
|
+
standard-custom (1.0.2)
|
|
127
|
+
lint_roller (~> 1.0)
|
|
128
|
+
rubocop (~> 1.50)
|
|
129
|
+
standard-performance (1.9.0)
|
|
130
|
+
lint_roller (~> 1.1)
|
|
131
|
+
rubocop-performance (~> 1.26.0)
|
|
132
|
+
tzinfo (2.0.6)
|
|
133
|
+
concurrent-ruby (~> 1.0)
|
|
134
|
+
unicode-display_width (3.2.0)
|
|
135
|
+
unicode-emoji (~> 4.1)
|
|
136
|
+
unicode-emoji (4.2.0)
|
|
137
|
+
uri (1.1.1)
|
|
138
|
+
useragent (0.16.11)
|
|
14
139
|
|
|
15
140
|
PLATFORMS
|
|
16
|
-
|
|
141
|
+
arm64-darwin-25
|
|
142
|
+
x86_64-linux
|
|
17
143
|
|
|
18
144
|
DEPENDENCIES
|
|
145
|
+
actionpack (>= 6.1)
|
|
19
146
|
bundler
|
|
20
|
-
faraday
|
|
147
|
+
faraday (>= 2.7)
|
|
21
148
|
http_signature!
|
|
22
|
-
minitest
|
|
149
|
+
minitest (>= 5.24)
|
|
150
|
+
rack
|
|
23
151
|
rake
|
|
152
|
+
simplecov
|
|
153
|
+
standard
|
|
154
|
+
|
|
155
|
+
RUBY VERSION
|
|
156
|
+
ruby 3.4.8
|
|
24
157
|
|
|
25
158
|
BUNDLED WITH
|
|
26
|
-
|
|
159
|
+
4.0.2
|
data/README.md
CHANGED
|
@@ -1,114 +1,61 @@
|
|
|
1
1
|
# HTTP Signature
|
|
2
|
-
[](https://circleci.com/gh/bolmaster2/http-signature)
|
|
3
2
|
|
|
4
|
-
Create and validate HTTP
|
|
3
|
+
Create and validate HTTP Message Signatures per [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421) using the `Signature-Input` and `Signature` headers.
|
|
5
4
|
|
|
6
|
-
Aims to only implement the creation and validation of
|
|
7
|
-
The idea is to implement adapters to popular http libraries to make it easy to use.
|
|
5
|
+
Aims to only implement the creation and validation of signatures without any external dependencies. Adapters are provided for common HTTP libraries.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
__NOTE__: RFC 9421 signs components via two headers:
|
|
10
8
|
```
|
|
11
|
-
|
|
9
|
+
Signature-Input: sig1=("@method" "@authority" "@target-uri" "date");created=...
|
|
10
|
+
Signature: sig1=:BASE64_SIGNATURE_BYTES:
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
##
|
|
13
|
+
## Installation
|
|
15
14
|
|
|
16
|
-
```
|
|
17
|
-
|
|
15
|
+
```shell
|
|
16
|
+
gem install http_signature
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
The most basic usage without any extra headers. The default algorithm is `hmac-sha256`. This create the `Signature` header value. Next step is to add the value to the header and 💥 you're done!
|
|
22
|
-
```ruby
|
|
23
|
-
HTTPSignature.create(
|
|
24
|
-
url: 'https://example.com/foo',
|
|
25
|
-
key_id: 'Test',
|
|
26
|
-
key: 'secret 🙈'
|
|
27
|
-
)
|
|
28
|
-
# 'keyId="Test",algorithm="hmac-sha256",headers="(request-target)",signature="OQ/dHqRW9vFmrW/RCHg7O2Fqx+3uqxJw81p6k9Rcyo4="'
|
|
29
|
-
```
|
|
19
|
+
## Usage
|
|
30
20
|
|
|
31
|
-
###
|
|
32
|
-
Uses both query parameters (in query string) and a `json` body as a `POST` request.
|
|
33
|
-
Also shows how to set `rsa-sha256` as algorithm. The `digest` is as you see basically
|
|
34
|
-
a `sha-256` digest of the request body.
|
|
21
|
+
### Create signature
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
params = {
|
|
38
|
-
param: 'value',
|
|
39
|
-
pet: 'dog'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
body = '{"hello": "world"}'
|
|
43
|
-
|
|
44
|
-
headers = {
|
|
45
|
-
'date': 'Thu, 05 Jan 2014 21:31:40 GMT',
|
|
46
|
-
'content-type': 'application/json',
|
|
47
|
-
'digest': HTTPSignature.create_digest(body),
|
|
48
|
-
'content-length': body.length
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
HTTPSignature.create(
|
|
52
|
-
url: 'https://example.com/foo',
|
|
53
|
-
method: :post,
|
|
54
|
-
query_string_params: params,
|
|
55
|
-
headers: headers,
|
|
56
|
-
key_id: 'Test',
|
|
57
|
-
algorithm: 'rsa-sha256',
|
|
58
|
-
key: File.read('key.pem'),
|
|
59
|
-
body: body
|
|
60
|
-
)
|
|
61
|
-
```
|
|
23
|
+
`HTTPSignature.create` returns both `Signature-Input` and `Signature` headers that you can include in your request.
|
|
62
24
|
|
|
63
|
-
### With digest header auto-added
|
|
64
|
-
When digest header is omitted it's auto added as last header generated from the `body`:
|
|
65
25
|
|
|
66
26
|
```ruby
|
|
67
|
-
|
|
27
|
+
headers = { 'date' => 'Tue, 20 Apr 2021 02:07:55 GMT' }
|
|
68
28
|
|
|
69
|
-
HTTPSignature.create(
|
|
70
|
-
url: 'https://example.com/foo',
|
|
29
|
+
sig_headers = HTTPSignature.create(
|
|
30
|
+
url: 'https://example.com/foo?pet=dog',
|
|
31
|
+
method: :get,
|
|
32
|
+
headers: headers,
|
|
71
33
|
key_id: 'Test',
|
|
72
|
-
key: 'secret
|
|
73
|
-
|
|
34
|
+
key: 'secret',
|
|
35
|
+
covered_components: %w[@method @target-uri date],
|
|
74
36
|
)
|
|
75
|
-
|
|
37
|
+
|
|
38
|
+
request['Signature-Input'] = sig_headers['Signature-Input']
|
|
39
|
+
request['Signature'] = sig_headers['Signature']
|
|
76
40
|
```
|
|
77
41
|
|
|
78
|
-
### Validate asymmetric signature
|
|
79
|
-
With an asymmetric algorithm you can't just recreate the same header and see if they
|
|
80
|
-
check out, because you need the private key to do that and because the one validating
|
|
81
|
-
the signature should only have access to the public key, you need to validate it with that.
|
|
82
42
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Host: example.com
|
|
87
|
-
Date: Thu, 05 Jan 2014 21:31:40 GMT
|
|
88
|
-
Content-Type: application/json
|
|
89
|
-
Content-Length: 18
|
|
90
|
-
Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
|
|
91
|
-
Signature: keyId="Test-1",algorithm="rsa-sha256",headers="(request-target) host date content-type content-length digest",signature="YGPVM1tGHD7CHgTmroy9apLtVazdESzMl4vj1koYHNCMmTEDor4Om5TDZDFaJdny5dF3gq+PQQuPwyknNEvACmSjwVXzljPFxaY/JMZTqAdD0yHTP2Rx0Y/J4GwgKARWTZUmccfVYsXp86PhIlCymzleZzYCzj6shyg9NB7Ht+k="
|
|
92
|
-
|
|
93
|
-
{"hello": "world"}
|
|
94
|
-
```
|
|
43
|
+
### Validate signature
|
|
44
|
+
|
|
45
|
+
Call `valid?` with the incoming request headers (including `Signature-Input` and `Signature`)
|
|
95
46
|
|
|
96
|
-
Let's assume we have this request ☝️ in a `request` object for the sake of the example:
|
|
97
47
|
```ruby
|
|
98
48
|
HTTPSignature.valid?(
|
|
99
|
-
url:
|
|
100
|
-
method:
|
|
101
|
-
headers:
|
|
102
|
-
|
|
103
|
-
key: OpenSSL::PKey::RSA.new('public_key.pem'),
|
|
104
|
-
algorithm: 'rsa-sha256'
|
|
49
|
+
url: "https://example.com/foo",
|
|
50
|
+
method: :get,
|
|
51
|
+
headers: headers,
|
|
52
|
+
key: "secret"
|
|
105
53
|
)
|
|
106
54
|
```
|
|
107
55
|
|
|
108
|
-
##
|
|
56
|
+
## Outgoing request examples
|
|
57
|
+
|
|
109
58
|
### NET::HTTP
|
|
110
|
-
Example of using it with `NET::HTTP`. There's no real integration written so it's basically just
|
|
111
|
-
getting the request object's data and create the signature and adding it to the headers.
|
|
112
59
|
|
|
113
60
|
```ruby
|
|
114
61
|
require 'net/http'
|
|
@@ -119,7 +66,7 @@ uri = URI('http://example.com/hello')
|
|
|
119
66
|
Net::HTTP.start(uri.host, uri.port) do |http|
|
|
120
67
|
request = Net::HTTP::Get.new(uri)
|
|
121
68
|
|
|
122
|
-
|
|
69
|
+
sig_headers = HTTPSignature.create(
|
|
123
70
|
url: request.uri,
|
|
124
71
|
method: request.method,
|
|
125
72
|
headers: request.each_header.map { |k, v| [k, v] }.to_h,
|
|
@@ -129,66 +76,81 @@ Net::HTTP.start(uri.host, uri.port) do |http|
|
|
|
129
76
|
body: request.body ? request.body : ''
|
|
130
77
|
)
|
|
131
78
|
|
|
132
|
-
request['Signature'] =
|
|
79
|
+
request['Signature-Input'] = sig_headers['Signature-Input']
|
|
80
|
+
request['Signature'] = sig_headers['Signature']
|
|
133
81
|
|
|
134
82
|
response = http.request(request) # Net::HTTPResponse
|
|
135
83
|
end
|
|
136
84
|
```
|
|
137
85
|
|
|
138
|
-
### Faraday
|
|
139
|
-
|
|
140
|
-
|
|
86
|
+
### Faraday
|
|
87
|
+
|
|
88
|
+
As a faraday middleware
|
|
141
89
|
|
|
142
90
|
```ruby
|
|
143
91
|
require 'http_signature/faraday'
|
|
144
92
|
|
|
145
|
-
HTTPSignature::Faraday.key = '
|
|
146
|
-
HTTPSignature::Faraday.key_id = 'key-1'
|
|
93
|
+
HTTPSignature::Faraday.key = 'secret'
|
|
94
|
+
HTTPSignature::Faraday.key_id = 'key-1'
|
|
147
95
|
|
|
148
|
-
# Tell faraday to use the middleware. Read more about it here: https://github.com/lostisland/faraday#advanced-middleware-usage
|
|
149
96
|
Faraday.new('http://example.com') do |faraday|
|
|
150
97
|
faraday.use(HTTPSignature::Faraday)
|
|
151
98
|
faraday.adapter(Faraday.default_adapter)
|
|
152
99
|
end
|
|
153
100
|
|
|
154
|
-
# Now this request will contain the `Signature`
|
|
101
|
+
# Now this request will contain the `Signature-Input` and `Signature` headers
|
|
155
102
|
response = conn.get('/')
|
|
156
103
|
|
|
157
104
|
# Request looking like:
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
# Signature: keyId="key-1",algorithm="hmac-sha256",headers="(request-target) date",signature="EzFa4vb0z+VFF8VYt9qQlzF9MTf5Izptc02OJ7aajnU="
|
|
105
|
+
# Signature-Input: sig1=("@method" "@authority" "@target-uri" "date");created=...
|
|
106
|
+
# Signature: sig1=:BASE64_SIGNATURE:
|
|
161
107
|
```
|
|
162
108
|
|
|
163
|
-
|
|
164
|
-
|
|
109
|
+
## Incoming request examples
|
|
110
|
+
|
|
111
|
+
### Rack middleware
|
|
112
|
+
Rack middlewares sits in between your app and the HTTP request and validate the signature before hitting your app. Read more about [rack middlewares here](https://codenoble.com/blog/understanding-rack-middleware/).
|
|
113
|
+
|
|
114
|
+
Here is how it could be used with sinatra:
|
|
165
115
|
|
|
166
|
-
#### General Rack application
|
|
167
|
-
Sinatra for example
|
|
168
116
|
```ruby
|
|
169
117
|
require 'http_signature/rack'
|
|
170
118
|
|
|
171
|
-
HTTPSignature.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
119
|
+
HTTPSignature.configure do |config|
|
|
120
|
+
config.keys = [
|
|
121
|
+
{id: 'key-1', value: 'MySecureKey'}
|
|
122
|
+
]
|
|
123
|
+
end
|
|
175
124
|
HTTPSignature::Rack.exclude_paths = ['/', '/hello/*']
|
|
176
125
|
|
|
177
126
|
use HTTPSignature::Rack
|
|
178
127
|
run MyApp
|
|
179
128
|
```
|
|
180
129
|
|
|
181
|
-
|
|
182
|
-
|
|
130
|
+
### Rails
|
|
131
|
+
Opt-in per controller/action using a before_action. It responds with `401 Unauthorized` if the signature is invalid
|
|
132
|
+
|
|
183
133
|
```ruby
|
|
184
|
-
|
|
185
|
-
|
|
134
|
+
# app/controllers/api/base_controller.rb
|
|
135
|
+
|
|
136
|
+
require 'http_signature/rails'
|
|
137
|
+
|
|
138
|
+
class Api::BaseController < ApplicationController
|
|
139
|
+
include HTTPSignature::Rails::Controller
|
|
140
|
+
|
|
141
|
+
before_action :verify_http_signature!
|
|
142
|
+
end
|
|
186
143
|
```
|
|
187
144
|
|
|
188
|
-
|
|
189
|
-
are supported to be able to easily be rotated.
|
|
145
|
+
Set the keys in an initializer
|
|
190
146
|
```ruby
|
|
191
|
-
|
|
147
|
+
# config/initializers/http_signature.rb
|
|
148
|
+
|
|
149
|
+
HTTPSignature.configure do |config|
|
|
150
|
+
config.keys = [
|
|
151
|
+
{id: 'key-1', value: 'MySecureKey'}
|
|
152
|
+
]
|
|
153
|
+
end
|
|
192
154
|
```
|
|
193
155
|
|
|
194
156
|
|
|
@@ -211,11 +173,9 @@ rake test TEST=test/http_signature_test.rb TESTOPTS="--name=/appends\ the\ query
|
|
|
211
173
|
## License
|
|
212
174
|
This project is licensed under the terms of the [MIT license](https://opensource.org/licenses/MIT).
|
|
213
175
|
|
|
214
|
-
##
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
https://tools.ietf.org/html/draft-cavage-http-signatures-08#section-2.3,
|
|
221
|
-
e.g, concatenate multiple instances of the same headers and remove surrounding whitespaces
|
|
176
|
+
## Why/when should I use this?
|
|
177
|
+
When you need to make sure that the request or response has not been tampered with (_integrity_). And you can be sure that the request was sent by someone that had the key (_authenticity_). Don't confuse this with encryption, the signed message is not encrypted. It's just _signed_. You could add a layer of encryption on top of this. Or just use HTTPS and you're _kinda safe_ for not that much hassle, which is totally fine in most cases.
|
|
178
|
+
|
|
179
|
+
[Read more about HMAC here](https://security.stackexchange.com/questions/20129/how-and-when-do-i-use-hmac/20301), even though you can sign your messages with RSA as well, but it's the same principle.
|
|
180
|
+
|
|
181
|
+
Beware that this has not been audited and should be used at your own risk!
|
data/Rakefile
CHANGED
data/bin/standardrb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'standardrb' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
require "rubygems"
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
|
|
16
|
+
load Gem.bin_path("standard", "standardrb")
|
data/http_signature.gemspec
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
lib = File.expand_path(
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
-
require
|
|
3
|
+
require "http_signature/version"
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name
|
|
7
|
-
spec.version
|
|
8
|
-
spec.authors
|
|
9
|
-
spec.email
|
|
6
|
+
spec.name = "http_signature"
|
|
7
|
+
spec.version = HTTPSignature::VERSION
|
|
8
|
+
spec.authors = ["Joel Larsson"]
|
|
9
|
+
spec.email = ["bolmaster2@gmail.com"]
|
|
10
10
|
|
|
11
|
-
spec.summary
|
|
12
|
-
spec.description
|
|
13
|
-
spec.homepage
|
|
14
|
-
spec.license
|
|
11
|
+
spec.summary = "Create and validate HTTP Message Signatures"
|
|
12
|
+
spec.description = "Create and validate HTTP Message Signatures according to RFC 9421: https://www.rfc-editor.org/rfc/rfc9421.html"
|
|
13
|
+
spec.homepage = "https://github.com/bolmaster2/http-signature"
|
|
14
|
+
spec.license = "MIT"
|
|
15
15
|
|
|
16
|
-
spec.files
|
|
17
|
-
spec.bindir
|
|
18
|
-
spec.executables
|
|
19
|
-
spec.require_paths = [
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
+
spec.bindir = "exe"
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
spec.
|
|
22
|
-
|
|
23
|
-
spec.add_development_dependency
|
|
24
|
-
spec.add_development_dependency
|
|
21
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency "bundler"
|
|
24
|
+
spec.add_development_dependency "rake"
|
|
25
|
+
spec.add_development_dependency "minitest", ">= 5.24"
|
|
26
|
+
spec.add_development_dependency "rack"
|
|
27
|
+
spec.add_development_dependency "faraday", ">= 2.7"
|
|
28
|
+
spec.add_development_dependency "standard"
|
|
29
|
+
spec.add_development_dependency "simplecov"
|
|
30
|
+
spec.add_development_dependency "actionpack", ">= 6.1"
|
|
31
|
+
|
|
32
|
+
spec.add_dependency "base64"
|
|
25
33
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "http_signature"
|
|
4
|
+
require "faraday"
|
|
5
5
|
|
|
6
6
|
class HTTPSignature::Faraday < Faraday::Middleware
|
|
7
7
|
class << self
|
|
@@ -9,10 +9,10 @@ class HTTPSignature::Faraday < Faraday::Middleware
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def call(env)
|
|
12
|
-
raise
|
|
12
|
+
raise "key and key_id needs to be set" if self.class.key.nil? || self.class.key_id.nil?
|
|
13
13
|
|
|
14
14
|
body =
|
|
15
|
-
if env[:body]
|
|
15
|
+
if env[:body]&.respond_to?(:read)
|
|
16
16
|
string = env[:body].read
|
|
17
17
|
env[:body].rewind
|
|
18
18
|
string
|
|
@@ -21,20 +21,22 @@ class HTTPSignature::Faraday < Faraday::Middleware
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Choose which headers to sign
|
|
24
|
-
filtered_headers = %w[Host Date Digest]
|
|
24
|
+
filtered_headers = %w[Host Date Content-Digest]
|
|
25
25
|
headers_to_sign = env[:request_headers].select { |k, _v| filtered_headers.include?(k.to_s) }
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
signature_headers = HTTPSignature.create(
|
|
28
28
|
url: env[:url],
|
|
29
29
|
method: env[:method],
|
|
30
30
|
headers: headers_to_sign,
|
|
31
31
|
key: self.class.key,
|
|
32
32
|
key_id: self.class.key_id,
|
|
33
|
-
algorithm:
|
|
33
|
+
algorithm: "hmac-sha256",
|
|
34
34
|
body: body
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
signature_headers.each do |header, value|
|
|
38
|
+
env[:request_headers][header] = value
|
|
39
|
+
end
|
|
38
40
|
|
|
39
41
|
@app.call(env)
|
|
40
42
|
end
|