http_signature 1.3.1 → 1.4.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/.github/dependabot.yml +9 -0
- data/AGENTS.md +1 -1
- data/CLAUDE.md +1 -0
- data/Gemfile.lock +26 -7
- data/README.md +125 -44
- data/http_signature.gemspec +3 -0
- data/lib/http_signature/faraday.rb +38 -14
- data/lib/http_signature/version.rb +1 -1
- data/lib/http_signature.rb +24 -3
- metadata +45 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e13e2dd019cbe8bc7359f1b49cfcd16bd3bb0e11818c7a4709a5892e7a9b362
|
|
4
|
+
data.tar.gz: 5879cc1525680c68286539b73d7091cdac20218727128af6e42cf2cff36e4c77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 69726bec8a75e3214a9d0c5b3f94f8d65e76c4c64746cc7ade2b11012f8d299d69ac33960f28feb486572702df2460f593cbcc04213f10bf42bd0706404763b8
|
|
7
|
+
data.tar.gz: e0c993df95150e6698faa87327a806262d7fd7f2141e70cae917dfa89f40ba018acbe721a4b7744b8a5af4ae3e3063fbe4427afbb35a562972de396a05d30acd
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Please see the documentation for all configuration options:
|
|
2
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
3
|
+
|
|
4
|
+
version: 2
|
|
5
|
+
updates:
|
|
6
|
+
- package-ecosystem: "bundler"
|
|
7
|
+
directory: "/"
|
|
8
|
+
schedule:
|
|
9
|
+
interval: "weekly"
|
data/AGENTS.md
CHANGED
|
@@ -18,4 +18,4 @@ Always run linters before commit
|
|
|
18
18
|
- Update version number in lib/http_signature/version.rb
|
|
19
19
|
- Run `bundle install`
|
|
20
20
|
- Commit with message "Version x.x.x" and push
|
|
21
|
-
- Create release on github with correct
|
|
21
|
+
- Create release on github with title "Version x.x.x", correct tag as "vx.x.x", e.g., v1.0.0, and auto generated notes
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
http_signature (1.
|
|
4
|
+
http_signature (1.4.0)
|
|
5
5
|
base64
|
|
6
6
|
starry (~> 0.2)
|
|
7
7
|
|
|
@@ -44,15 +44,26 @@ GEM
|
|
|
44
44
|
concurrent-ruby (1.3.6)
|
|
45
45
|
connection_pool (3.0.2)
|
|
46
46
|
crass (1.0.6)
|
|
47
|
+
csv (3.3.5)
|
|
47
48
|
docile (1.4.1)
|
|
48
49
|
drb (2.2.3)
|
|
49
50
|
erubi (1.13.1)
|
|
51
|
+
ethon (0.15.0)
|
|
52
|
+
ffi (>= 1.15.0)
|
|
53
|
+
excon (1.4.0)
|
|
54
|
+
logger
|
|
50
55
|
faraday (2.14.1)
|
|
51
56
|
faraday-net_http (>= 2.0, < 3.5)
|
|
52
57
|
json
|
|
53
58
|
logger
|
|
54
59
|
faraday-net_http (3.4.2)
|
|
55
60
|
net-http (~> 0.5)
|
|
61
|
+
ffi (1.17.3-arm64-darwin)
|
|
62
|
+
ffi (1.17.3-x86_64-linux-gnu)
|
|
63
|
+
httparty (0.24.2)
|
|
64
|
+
csv
|
|
65
|
+
mini_mime (>= 1.0.0)
|
|
66
|
+
multi_xml (>= 0.5.2)
|
|
56
67
|
i18n (1.14.8)
|
|
57
68
|
concurrent-ruby (~> 1.0)
|
|
58
69
|
json (2.18.1)
|
|
@@ -62,8 +73,11 @@ GEM
|
|
|
62
73
|
loofah (2.25.0)
|
|
63
74
|
crass (~> 1.0.2)
|
|
64
75
|
nokogiri (>= 1.12.0)
|
|
76
|
+
mini_mime (1.1.5)
|
|
65
77
|
minitest (6.0.0)
|
|
66
78
|
prism (~> 1.5)
|
|
79
|
+
multi_xml (0.8.1)
|
|
80
|
+
bigdecimal (>= 3.1, < 5)
|
|
67
81
|
net-http (0.9.1)
|
|
68
82
|
uri (>= 0.11.1)
|
|
69
83
|
nokogiri (1.19.0-arm64-darwin)
|
|
@@ -71,10 +85,10 @@ GEM
|
|
|
71
85
|
nokogiri (1.19.0-x86_64-linux-gnu)
|
|
72
86
|
racc (~> 1.4)
|
|
73
87
|
parallel (1.27.0)
|
|
74
|
-
parser (3.3.10.
|
|
88
|
+
parser (3.3.10.2)
|
|
75
89
|
ast (~> 2.4.1)
|
|
76
90
|
racc
|
|
77
|
-
prism (1.
|
|
91
|
+
prism (1.9.0)
|
|
78
92
|
racc (1.8.1)
|
|
79
93
|
rack (3.2.5)
|
|
80
94
|
rack-session (2.1.1)
|
|
@@ -92,7 +106,7 @@ GEM
|
|
|
92
106
|
rainbow (3.1.1)
|
|
93
107
|
rake (13.3.1)
|
|
94
108
|
regexp_parser (2.11.3)
|
|
95
|
-
rubocop (1.
|
|
109
|
+
rubocop (1.84.2)
|
|
96
110
|
json (~> 2.3)
|
|
97
111
|
language_server-protocol (~> 3.17.0.2)
|
|
98
112
|
lint_roller (~> 1.1.0)
|
|
@@ -100,7 +114,7 @@ GEM
|
|
|
100
114
|
parser (>= 3.3.0.2)
|
|
101
115
|
rainbow (>= 2.2.2, < 4.0)
|
|
102
116
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
103
|
-
rubocop-ast (>= 1.
|
|
117
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
104
118
|
ruby-progressbar (~> 1.7)
|
|
105
119
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
106
120
|
rubocop-ast (1.49.0)
|
|
@@ -118,10 +132,10 @@ GEM
|
|
|
118
132
|
simplecov_json_formatter (~> 0.1)
|
|
119
133
|
simplecov-html (0.13.2)
|
|
120
134
|
simplecov_json_formatter (0.1.4)
|
|
121
|
-
standard (1.
|
|
135
|
+
standard (1.54.0)
|
|
122
136
|
language_server-protocol (~> 3.17.0.2)
|
|
123
137
|
lint_roller (~> 1.0)
|
|
124
|
-
rubocop (~> 1.
|
|
138
|
+
rubocop (~> 1.84.0)
|
|
125
139
|
standard-custom (~> 1.0.0)
|
|
126
140
|
standard-performance (~> 1.8)
|
|
127
141
|
standard-custom (1.0.2)
|
|
@@ -132,6 +146,8 @@ GEM
|
|
|
132
146
|
rubocop-performance (~> 1.26.0)
|
|
133
147
|
starry (0.2.0)
|
|
134
148
|
base64
|
|
149
|
+
typhoeus (1.5.0)
|
|
150
|
+
ethon (>= 0.9.0, < 0.16.0)
|
|
135
151
|
tzinfo (2.0.6)
|
|
136
152
|
concurrent-ruby (~> 1.0)
|
|
137
153
|
unicode-display_width (3.2.0)
|
|
@@ -147,13 +163,16 @@ PLATFORMS
|
|
|
147
163
|
DEPENDENCIES
|
|
148
164
|
actionpack (>= 6.1)
|
|
149
165
|
bundler
|
|
166
|
+
excon
|
|
150
167
|
faraday (>= 2.7)
|
|
151
168
|
http_signature!
|
|
169
|
+
httparty
|
|
152
170
|
minitest (>= 5.24)
|
|
153
171
|
rack
|
|
154
172
|
rake
|
|
155
173
|
simplecov
|
|
156
174
|
standard
|
|
175
|
+
typhoeus
|
|
157
176
|
|
|
158
177
|
BUNDLED WITH
|
|
159
178
|
4.0.2
|
data/README.md
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# HTTP Signature
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Ruby gem for signing and verifying HTTP requests. You pick which parts of the request to sign (method, URL, headers, body), and the receiver can verify nothing was tampered with — and that it came from someone who holds the key.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Built on [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421) (HTTP Message Signatures). Works with HMAC, RSA, ECDSA, and Ed25519.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
When using a standard to sign your HTTP requests you don't need to write a custom implementation every time. There's [loads of libraries](https://httpsig.org/) across languages that's implementing it!
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Signature-Input: sig1=("@method" "@target-uri" "date");created=1767816111;keyid="Test";alg="hmac-sha256"
|
|
11
|
-
Signature: sig1=:7a1ajkE2rOu+gnW3WLZ4ZEcgCm3TfExmypM/giIgdM0=:
|
|
12
|
-
```
|
|
9
|
+
Use your favorite http clients to sign requests and your favorite frameworks to verify them, see [Outgoing request examples](#outgoing-request-examples) and [Incoming request examples](#incoming-request-examples)
|
|
13
10
|
|
|
14
11
|
## Installation
|
|
15
12
|
|
|
@@ -21,22 +18,29 @@ bundle add http_signature
|
|
|
21
18
|
|
|
22
19
|
### Create signature
|
|
23
20
|
|
|
24
|
-
`HTTPSignature.create` returns
|
|
21
|
+
`HTTPSignature.create` returns `Signature-Input`, `Signature` and `Content-Digest` headers that you can include in your request.
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
This example will sign:
|
|
24
|
+
- The whole `url` string: `https://example.com/foo?pet=dog`
|
|
25
|
+
- The HTTP method: `POST`
|
|
26
|
+
- The headers `Content-Type` and `Content-Digest`
|
|
27
|
+
- The body, which is in the `Content-Digest` header and is automatically generated when a body is provided
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
```ruby
|
|
30
|
+
HTTPSignature.create(
|
|
30
31
|
url: "https://example.com/foo?pet=dog",
|
|
31
|
-
method: :
|
|
32
|
-
key_id: "
|
|
32
|
+
method: :post,
|
|
33
|
+
key_id: "key-1",
|
|
33
34
|
key: "secret",
|
|
34
|
-
headers:
|
|
35
|
-
|
|
35
|
+
headers: {"Content-Type" => "application/json"},
|
|
36
|
+
body: {payload: {foo: "bar"}}.to_json,
|
|
37
|
+
components: %w[@method @target-uri content-type content-digest]
|
|
36
38
|
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
# =>
|
|
40
|
+
# {"Signature-Input" =>
|
|
41
|
+
# "sig1=(\"@method\" \"@target-uri\" \"content-type\" \"content-digest\");created=1772541832;keyid=\"key-1\";alg=\"hmac-sha256\"",
|
|
42
|
+
# "Signature" => "sig1=:5ij6rnnwS9oOtu78zU4yBFy9uL3ItXM7ug368cJZuTU=:",
|
|
43
|
+
# "Content-Digest" => "sha-256=:zWToMIpmVcAx10/ZGOrMzi7HQyUBat/TskigQnncEQ8=:"}
|
|
40
44
|
```
|
|
41
45
|
#### All options
|
|
42
46
|
|
|
@@ -254,37 +258,35 @@ end
|
|
|
254
258
|
|
|
255
259
|
## Outgoing request examples
|
|
256
260
|
|
|
257
|
-
###
|
|
261
|
+
### Net::HTTP
|
|
258
262
|
|
|
259
263
|
```ruby
|
|
260
264
|
require "net/http"
|
|
261
265
|
require "http_signature"
|
|
262
266
|
|
|
263
267
|
uri = URI("http://example.com/hello")
|
|
268
|
+
body = {name: "World"}.to_json
|
|
269
|
+
headers = {"Content-Type" => "application/json"}
|
|
264
270
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
key_id: "KEY_1",
|
|
274
|
-
algorithm: "hmac-sha256",
|
|
275
|
-
body: request.body || ""
|
|
276
|
-
)
|
|
271
|
+
sig_headers = HTTPSignature.create(
|
|
272
|
+
url: uri.to_s,
|
|
273
|
+
method: :post,
|
|
274
|
+
headers:,
|
|
275
|
+
key: "MYSECRETKEY",
|
|
276
|
+
key_id: "KEY_1",
|
|
277
|
+
body:
|
|
278
|
+
)
|
|
277
279
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
+
req = Net::HTTP::Post.new(uri)
|
|
281
|
+
headers.merge(sig_headers).each { |k, v| req[k] = v }
|
|
282
|
+
req.body = body
|
|
280
283
|
|
|
281
|
-
|
|
282
|
-
end
|
|
284
|
+
Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
|
|
283
285
|
```
|
|
284
286
|
|
|
285
287
|
### Faraday
|
|
286
288
|
|
|
287
|
-
As a
|
|
289
|
+
As a Faraday middleware
|
|
288
290
|
|
|
289
291
|
```ruby
|
|
290
292
|
require "http_signature/faraday"
|
|
@@ -292,17 +294,96 @@ require "http_signature/faraday"
|
|
|
292
294
|
HTTPSignature::Faraday.key = "secret"
|
|
293
295
|
HTTPSignature::Faraday.key_id = "key-1"
|
|
294
296
|
|
|
295
|
-
Faraday.new("http://example.com") do |
|
|
296
|
-
|
|
297
|
-
|
|
297
|
+
conn = Faraday.new("http://example.com") do |f|
|
|
298
|
+
f.use(HTTPSignature::Faraday)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Requests will automatically include Signature-Input, Signature, and Content-Digest headers
|
|
302
|
+
conn.post("/hello") do |req|
|
|
303
|
+
req.headers["Content-Type"] = "application/json"
|
|
304
|
+
req.body = {name: "World"}.to_json
|
|
305
|
+
end
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Example with setting params per request:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
conn = Faraday.new("http://example.com") do |f|
|
|
312
|
+
f.use(
|
|
313
|
+
HTTPSignature::Faraday,
|
|
314
|
+
key: "secret",
|
|
315
|
+
key_id: "key-1",
|
|
316
|
+
components: ["@method", "@target-uri", "content-type", "content-digest"]
|
|
317
|
+
)
|
|
298
318
|
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The middleware also accepts the params: `created:`, `expires:`, `nonce:`, `label:`, `algorithm:`, and `include_alg:`.
|
|
299
322
|
|
|
300
|
-
|
|
301
|
-
|
|
323
|
+
### HTTParty
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
require "httparty"
|
|
327
|
+
require "http_signature"
|
|
328
|
+
|
|
329
|
+
url = "http://example.com/hello"
|
|
330
|
+
body = {name: "World"}.to_json
|
|
331
|
+
headers = {"Content-Type" => "application/json"}
|
|
332
|
+
|
|
333
|
+
sig_headers = HTTPSignature.create(
|
|
334
|
+
url:,
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers:,
|
|
337
|
+
key: "MYSECRETKEY",
|
|
338
|
+
key_id: "KEY_1",
|
|
339
|
+
body:
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
HTTParty.post(url, body:, headers: headers.merge(sig_headers))
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Excon
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
require "excon"
|
|
349
|
+
require "http_signature"
|
|
350
|
+
|
|
351
|
+
url = "http://example.com/hello"
|
|
352
|
+
body = {name: "World"}.to_json
|
|
353
|
+
headers = {"Content-Type" => "application/json"}
|
|
354
|
+
|
|
355
|
+
sig_headers = HTTPSignature.create(
|
|
356
|
+
url:,
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers:,
|
|
359
|
+
key: "MYSECRETKEY",
|
|
360
|
+
key_id: "KEY_1",
|
|
361
|
+
body:
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
Excon.post(url, headers: headers.merge(sig_headers), body:)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Typhoeus
|
|
368
|
+
|
|
369
|
+
```ruby
|
|
370
|
+
require "typhoeus"
|
|
371
|
+
require "http_signature"
|
|
372
|
+
|
|
373
|
+
url = "http://example.com/hello"
|
|
374
|
+
body = {name: "World"}.to_json
|
|
375
|
+
headers = {"Content-Type" => "application/json"}
|
|
376
|
+
|
|
377
|
+
sig_headers = HTTPSignature.create(
|
|
378
|
+
url:,
|
|
379
|
+
method: "POST",
|
|
380
|
+
headers:,
|
|
381
|
+
key: "MYSECRETKEY",
|
|
382
|
+
key_id: "KEY_1",
|
|
383
|
+
body:
|
|
384
|
+
)
|
|
302
385
|
|
|
303
|
-
|
|
304
|
-
# Signature-Input: sig1=("@method" "@authority" "@target-uri" "date");created=...
|
|
305
|
-
# Signature: sig1=:BASE64_SIGNATURE:
|
|
386
|
+
Typhoeus.post(url, body:, headers: headers.merge(sig_headers))
|
|
306
387
|
```
|
|
307
388
|
|
|
308
389
|
## Incoming request examples
|
data/http_signature.gemspec
CHANGED
|
@@ -28,6 +28,9 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
spec.add_development_dependency "standard"
|
|
29
29
|
spec.add_development_dependency "simplecov"
|
|
30
30
|
spec.add_development_dependency "actionpack", ">= 6.1"
|
|
31
|
+
spec.add_development_dependency "httparty"
|
|
32
|
+
spec.add_development_dependency "excon"
|
|
33
|
+
spec.add_development_dependency "typhoeus"
|
|
31
34
|
|
|
32
35
|
spec.add_dependency "base64"
|
|
33
36
|
spec.add_dependency "starry", "~> 0.2"
|
|
@@ -8,8 +8,16 @@ class HTTPSignature::Faraday < Faraday::Middleware
|
|
|
8
8
|
attr_accessor :key, :key_id
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def initialize(app, options = nil)
|
|
12
|
+
super(app)
|
|
13
|
+
@options = options || {}
|
|
14
|
+
end
|
|
15
|
+
|
|
11
16
|
def call(env)
|
|
12
|
-
|
|
17
|
+
options = merged_options(env)
|
|
18
|
+
key = options.fetch(:key, self.class.key)
|
|
19
|
+
key_id = options.fetch(:key_id, self.class.key_id)
|
|
20
|
+
raise "key and key_id needs to be set" if key.nil? || key_id.nil?
|
|
13
21
|
|
|
14
22
|
body =
|
|
15
23
|
if env[:body]&.respond_to?(:read)
|
|
@@ -20,19 +28,7 @@ class HTTPSignature::Faraday < Faraday::Middleware
|
|
|
20
28
|
env[:body].to_s
|
|
21
29
|
end
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
filtered_headers = %w[Host Date Content-Digest]
|
|
25
|
-
headers_to_sign = env[:request_headers].select { |k, _v| filtered_headers.include?(k.to_s) }
|
|
26
|
-
|
|
27
|
-
signature_headers = HTTPSignature.create(
|
|
28
|
-
url: env[:url],
|
|
29
|
-
method: env[:method],
|
|
30
|
-
headers: headers_to_sign,
|
|
31
|
-
key: self.class.key,
|
|
32
|
-
key_id: self.class.key_id,
|
|
33
|
-
algorithm: "hmac-sha256",
|
|
34
|
-
body: body
|
|
35
|
-
)
|
|
31
|
+
signature_headers = HTTPSignature.create(**signature_options(env:, key:, key_id:, body:, options:))
|
|
36
32
|
|
|
37
33
|
signature_headers.each do |header, value|
|
|
38
34
|
env[:request_headers][header] = value
|
|
@@ -40,4 +36,32 @@ class HTTPSignature::Faraday < Faraday::Middleware
|
|
|
40
36
|
|
|
41
37
|
@app.call(env)
|
|
42
38
|
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def merged_options(env)
|
|
43
|
+
request_options = env.request.context&.dig(:http_signature) || {}
|
|
44
|
+
@options.merge(request_options)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def signature_options(env:, key:, key_id:, body:, options:)
|
|
48
|
+
{
|
|
49
|
+
url: env[:url],
|
|
50
|
+
method: env[:method],
|
|
51
|
+
headers: env[:request_headers],
|
|
52
|
+
key:,
|
|
53
|
+
key_id:,
|
|
54
|
+
body:,
|
|
55
|
+
**create_options(options)
|
|
56
|
+
}.tap do |signature_options|
|
|
57
|
+
if options.key?(:components) && !options[:components].nil?
|
|
58
|
+
signature_options[:components] = options[:components]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def create_options(options)
|
|
64
|
+
options.slice(:created, :expires, :nonce, :label, :include_alg, :algorithm)
|
|
65
|
+
.reject { |_key, value| value.nil? }
|
|
66
|
+
end
|
|
43
67
|
end
|
data/lib/http_signature.rb
CHANGED
|
@@ -108,6 +108,8 @@ module HTTPSignature
|
|
|
108
108
|
|
|
109
109
|
validate_components!(components)
|
|
110
110
|
|
|
111
|
+
had_content_digest = normalized_headers.key?("content-digest")
|
|
112
|
+
|
|
111
113
|
normalized_headers =
|
|
112
114
|
if components.include?("content-digest")
|
|
113
115
|
ensure_content_digest(normalized_headers, body)
|
|
@@ -140,10 +142,16 @@ module HTTPSignature
|
|
|
140
142
|
signature_bytes = sign(base_string, key: key, algorithm: algorithm_entry)
|
|
141
143
|
signature_header = build_signature_header(label, signature_bytes)
|
|
142
144
|
|
|
143
|
-
{
|
|
145
|
+
result = {
|
|
144
146
|
"Signature-Input" => signature_input_header,
|
|
145
147
|
"Signature" => signature_header
|
|
146
148
|
}
|
|
149
|
+
|
|
150
|
+
if !had_content_digest && normalized_headers["content-digest"]
|
|
151
|
+
result["Content-Digest"] = normalized_headers["content-digest"]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
result
|
|
147
155
|
end
|
|
148
156
|
|
|
149
157
|
# Verify RFC 9421 Signature headers
|
|
@@ -154,7 +162,8 @@ module HTTPSignature
|
|
|
154
162
|
# @param body [String] Request body (default: "")
|
|
155
163
|
# @param key [String, OpenSSL::PKey::PKey, nil] Verification key. If nil, uses key_resolver or configured keys
|
|
156
164
|
# @param key_resolver [Proc, nil] Callable that receives key_id and returns the key (default: nil)
|
|
157
|
-
# @param label [String] Signature label to verify
|
|
165
|
+
# @param label [String, nil] Signature label to verify. If nil, auto-detects
|
|
166
|
+
# the first label from the Signature-Input header (default: nil)
|
|
158
167
|
# @param query_string_params [Hash] Additional query params to merge into URL (default: {})
|
|
159
168
|
# @param max_age [Integer, nil] Maximum signature age in seconds. Takes precedence over
|
|
160
169
|
# the expires timestamp in the signature (default: nil)
|
|
@@ -174,7 +183,7 @@ module HTTPSignature
|
|
|
174
183
|
body: "",
|
|
175
184
|
key: nil,
|
|
176
185
|
key_resolver: nil,
|
|
177
|
-
label:
|
|
186
|
+
label: nil,
|
|
178
187
|
query_string_params: {},
|
|
179
188
|
max_age: nil,
|
|
180
189
|
algorithm: nil,
|
|
@@ -191,6 +200,8 @@ module HTTPSignature
|
|
|
191
200
|
signature_header = normalized_headers["signature"]
|
|
192
201
|
raise SignatureError, "Signature headers are required for verification" unless signature_input_header && signature_header
|
|
193
202
|
|
|
203
|
+
label ||= detect_label(signature_input_header)
|
|
204
|
+
|
|
194
205
|
parsed_input = parse_signature_input(signature_input_header, label)
|
|
195
206
|
validate_components!(parsed_input[:components])
|
|
196
207
|
parsed_signature = parse_signature(signature_header, label)
|
|
@@ -624,6 +635,16 @@ module HTTPSignature
|
|
|
624
635
|
header.to_s.split(/,(?=[^,]+=)/).map(&:strip)
|
|
625
636
|
end
|
|
626
637
|
|
|
638
|
+
def self.detect_label(signature_input_header)
|
|
639
|
+
entry = split_header(signature_input_header).first
|
|
640
|
+
raise SignatureError, "Signature-Input missing" unless entry
|
|
641
|
+
|
|
642
|
+
label = entry.split("=", 2).first
|
|
643
|
+
raise SignatureError, "Signature-Input missing" if label.nil? || label.empty?
|
|
644
|
+
|
|
645
|
+
label
|
|
646
|
+
end
|
|
647
|
+
|
|
627
648
|
def self.key_from_store(key_id)
|
|
628
649
|
return unless keys && key_id
|
|
629
650
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: http_signature
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joel Larsson
|
|
@@ -121,6 +121,48 @@ dependencies:
|
|
|
121
121
|
- - ">="
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: '6.1'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: httparty
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: excon
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0'
|
|
152
|
+
- !ruby/object:Gem::Dependency
|
|
153
|
+
name: typhoeus
|
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - ">="
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '0'
|
|
159
|
+
type: :development
|
|
160
|
+
prerelease: false
|
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '0'
|
|
124
166
|
- !ruby/object:Gem::Dependency
|
|
125
167
|
name: base64
|
|
126
168
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -156,12 +198,14 @@ executables: []
|
|
|
156
198
|
extensions: []
|
|
157
199
|
extra_rdoc_files: []
|
|
158
200
|
files:
|
|
201
|
+
- ".github/dependabot.yml"
|
|
159
202
|
- ".github/workflows/ci.yml"
|
|
160
203
|
- ".github/workflows/push_gem.yml"
|
|
161
204
|
- ".github/workflows/standardrb.yml"
|
|
162
205
|
- ".gitignore"
|
|
163
206
|
- ".ruby-version"
|
|
164
207
|
- AGENTS.md
|
|
208
|
+
- CLAUDE.md
|
|
165
209
|
- Gemfile
|
|
166
210
|
- Gemfile.lock
|
|
167
211
|
- README.md
|