linzer 0.6.4 → 0.7.0.beta1
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/.standard.yml +1 -0
- data/CHANGELOG.md +10 -0
- data/README.md +28 -1
- data/examples/sinatra/Gemfile +9 -0
- data/examples/sinatra/config.ru +11 -0
- data/examples/sinatra/http-signatures.yml +26 -0
- data/examples/sinatra/myapp.rb +10 -0
- data/lib/linzer/key/helper.rb +22 -8
- data/lib/linzer/rsa.rb +1 -7
- data/lib/linzer/rsa_pss.rb +40 -0
- data/lib/linzer/version.rb +1 -1
- data/lib/linzer.rb +2 -0
- data/lib/rack/auth/signature.rb +206 -0
- metadata +29 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a19b05f41aa933779f504056b56b00fc1a8a6cda19bb26065dce56177caf0c2
|
4
|
+
data.tar.gz: 25d7d8b4afbd60a3e6e872064d92f9be22670466292e23a6ba8010ca546cdf23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 527fd9a73a1605b706fc7ca597ce14f4f5c95be099907bf61832aa698b11fe51dbf25a5eaf70d48d7c6715c91c27bfa391482a88a67f0e932c5f60ff8f3f16df
|
7
|
+
data.tar.gz: 2b30454801734973711afe81559ad18f09f6d08f473796241d56e1b13d5619971a697945703b85012e18ae969059ca5a3718e677fc71716151bd46e795eac8a7
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.7.0.beta1] - 2025-04-12
|
4
|
+
|
5
|
+
- Introduce Rack::Auth::Signature middleware.
|
6
|
+
|
7
|
+
## [0.6.5] - 2025-04-09
|
8
|
+
|
9
|
+
- Add support for RSA (RSASSA-PKCS1-V1_5) and improve RSASSA-PSS handling.
|
10
|
+
Pull request [#10](https://github.com/nomadium/linzer/pull/10)
|
11
|
+
by [oneiros](https://github.com/oneiros).
|
12
|
+
|
3
13
|
## [0.6.4] - 2025-04-04
|
4
14
|
|
5
15
|
- Allow validating the `created` parameter to mitigate the
|
data/README.md
CHANGED
@@ -21,6 +21,33 @@ Or just `gem install linzer`.
|
|
21
21
|
|
22
22
|
## Usage
|
23
23
|
|
24
|
+
### TL;DR: I just want to protect my application!!
|
25
|
+
|
26
|
+
Add the following middleware to you run Rack application, e.g.:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# config.ru
|
30
|
+
use Rack::Auth::Signature, except: "/login",
|
31
|
+
default_key: {"key" => IO.read("app/config/pubkey.pem"), "alg" => "ed25519"}
|
32
|
+
```
|
33
|
+
|
34
|
+
or on more complex scenarios:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# config.ru
|
38
|
+
use Rack::Auth::Signature, except: "/login",
|
39
|
+
config_path: "app/configuration/http-signatures.yml"
|
40
|
+
```
|
41
|
+
|
42
|
+
And that's it, all routes in the example app (except `/login`) above will
|
43
|
+
require a valid signature created with the respective private key held by a
|
44
|
+
client. For more details on what configuration options are available, take a
|
45
|
+
look at
|
46
|
+
[examples/sinatra/http-signatures.yml](https://github.com/nomadium/linzer/tree/master/examples/sinatra/http-signatures.yml) to get started and/or
|
47
|
+
[lib/rack/auth/signature.rb](https://github.com/nomadium/linzer/tree/master/lib/rack/auth/signature.rb) for full implementation details.
|
48
|
+
|
49
|
+
To learn about more specific scenarios or use cases, keep reading on below.
|
50
|
+
|
24
51
|
### To sign a HTTP message:
|
25
52
|
|
26
53
|
```ruby
|
@@ -160,7 +187,7 @@ pp signature.to_h
|
|
160
187
|
|
161
188
|
For now, to consult additional details just take a look at source code and/or the unit tests.
|
162
189
|
|
163
|
-
Please note that is still early days and extensive testing is still ongoing. For now
|
190
|
+
Please note that is still early days and extensive testing is still ongoing. For now the following algorithms are supported: RSASSA-PSS using SHA-512, RSASSA-PKCS1-v1_5 using SHA-256, HMAC-SHA256, Ed25519 and ECDSA (P-256 and P-384 curves). JSON Web Signature (JWS) algorithms mentioned in the RFC are not supported yet.
|
164
191
|
|
165
192
|
I'll be expanding the library to cover more functionality specified in the RFC
|
166
193
|
in subsequent releases.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
---
|
2
|
+
no_older_than: 6000
|
3
|
+
created_required: true
|
4
|
+
keyid_required: true
|
5
|
+
# nonce_required: false
|
6
|
+
# alg_required: false
|
7
|
+
# tag_required: false
|
8
|
+
# expires_required: false
|
9
|
+
covered_components:
|
10
|
+
- "@method"
|
11
|
+
- "@request-target"
|
12
|
+
- date
|
13
|
+
known_keys:
|
14
|
+
foo:
|
15
|
+
alg: ed25519
|
16
|
+
key: |
|
17
|
+
-----BEGIN PUBLIC KEY-----
|
18
|
+
MCowBQYDK2VwAyEAMEH9bSanwgAWE5qxUEaXjK6qei8z2hiHT0nlr7ljG0Y=
|
19
|
+
-----END PUBLIC KEY-----
|
20
|
+
bar:
|
21
|
+
alg: hmac-sha256
|
22
|
+
key: !binary |-
|
23
|
+
KuOR/Q8U1Crgp0WsFqW+5ZuC+KbN41dIlXWp71UhPEeJcRfuxZEy6XAUE9nf0yl4jt55yRASBnD0kQKucO6SfA==
|
24
|
+
baz:
|
25
|
+
alg: rsa-pss-sha512
|
26
|
+
path: rsa.pem
|
data/lib/linzer/key/helper.rb
CHANGED
@@ -4,24 +4,38 @@ module Linzer
|
|
4
4
|
class Key
|
5
5
|
module Helper
|
6
6
|
def generate_rsa_pss_sha512_key(size, key_id = nil)
|
7
|
-
material = OpenSSL::PKey
|
8
|
-
Linzer::
|
7
|
+
material = OpenSSL::PKey.generate_key("RSASSA-PSS")
|
8
|
+
Linzer::RSAPSS::Key.new(material, id: key_id, digest: "SHA512")
|
9
9
|
end
|
10
10
|
|
11
11
|
def new_rsa_pss_sha512_key(material, key_id = nil)
|
12
12
|
key = OpenSSL::PKey.read(material)
|
13
|
-
Linzer::
|
13
|
+
Linzer::RSAPSS::Key.new(key, id: key_id, digest: "SHA512")
|
14
14
|
end
|
15
15
|
|
16
|
+
# XXX: investigate: was this method made redundant after:
|
17
|
+
# https://github.com/nomadium/linzer/pull/10
|
16
18
|
def new_rsa_pss_sha512_public_key(material, key_id = nil)
|
17
19
|
key = OpenSSL::PKey::RSA.new(material)
|
18
|
-
Linzer::
|
20
|
+
Linzer::RSAPSS::Key.new(key, id: key_id, digest: "SHA512")
|
19
21
|
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
def generate_rsa_v1_5_sha256_key(size, key_id = nil)
|
24
|
+
material = OpenSSL::PKey::RSA.generate(size)
|
25
|
+
Linzer::RSA::Key.new(material, id: key_id, digest: "SHA256")
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_rsa_v1_5_sha256_key(material, key_id = nil)
|
29
|
+
key = OpenSSL::PKey.read(material)
|
30
|
+
Linzer::RSA::Key.new(key, id: key_id, digest: "SHA256")
|
31
|
+
end
|
32
|
+
|
33
|
+
# XXX: investigate: was this method made redundant after:
|
34
|
+
# https://github.com/nomadium/linzer/pull/10
|
35
|
+
def new_rsa_v1_5_sha256_public_key(material, key_id = nil)
|
36
|
+
key = OpenSSL::PKey.read(material)
|
37
|
+
Linzer::RSA::Key.new(key, id: key_id, digest: "SHA256")
|
38
|
+
end
|
25
39
|
|
26
40
|
def generate_hmac_sha256_key(key_id = nil)
|
27
41
|
material = OpenSSL::Random.random_bytes(64)
|
data/lib/linzer/rsa.rb
CHANGED
@@ -15,13 +15,7 @@ module Linzer
|
|
15
15
|
|
16
16
|
def verify(signature, data)
|
17
17
|
# XXX: should check if the key is usable for verifying
|
18
|
-
return true if @material.
|
19
|
-
@params[:digest],
|
20
|
-
signature,
|
21
|
-
data,
|
22
|
-
salt_length: @params[:salt_length] || :auto,
|
23
|
-
mgf1_hash: @params[:digest]
|
24
|
-
)
|
18
|
+
return true if @material.verify(@params[:digest], signature, data)
|
25
19
|
false
|
26
20
|
end
|
27
21
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Linzer
|
4
|
+
module RSAPSS
|
5
|
+
SALT_LENGTH = 64
|
6
|
+
|
7
|
+
class Key < Linzer::Key
|
8
|
+
def validate
|
9
|
+
super
|
10
|
+
validate_digest
|
11
|
+
end
|
12
|
+
|
13
|
+
def sign(data)
|
14
|
+
# XXX: should check if the key is usable for signing
|
15
|
+
@material.sign(@params[:digest], data, signature_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def verify(signature, data)
|
19
|
+
# XXX: should check if the key is usable for verifying
|
20
|
+
return true if @material.verify(
|
21
|
+
@params[:digest],
|
22
|
+
signature,
|
23
|
+
data,
|
24
|
+
signature_options
|
25
|
+
)
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def signature_options
|
32
|
+
{
|
33
|
+
rsa_padding_mode: "pss",
|
34
|
+
rsa_pss_saltlen: @params[:salt_length] || SALT_LENGTH,
|
35
|
+
rsa_mgf1_md: @params[:digest]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/linzer/version.rb
CHANGED
data/lib/linzer.rb
CHANGED
@@ -14,12 +14,14 @@ require_relative "linzer/message"
|
|
14
14
|
require_relative "linzer/signature"
|
15
15
|
require_relative "linzer/key"
|
16
16
|
require_relative "linzer/rsa"
|
17
|
+
require_relative "linzer/rsa_pss"
|
17
18
|
require_relative "linzer/hmac"
|
18
19
|
require_relative "linzer/ed25519"
|
19
20
|
require_relative "linzer/ecdsa"
|
20
21
|
require_relative "linzer/key/helper"
|
21
22
|
require_relative "linzer/signer"
|
22
23
|
require_relative "linzer/verifier"
|
24
|
+
require_relative "rack/auth/signature"
|
23
25
|
|
24
26
|
module Linzer
|
25
27
|
class Error < StandardError; end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require "linzer"
|
2
|
+
require "logger"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
# Rack::Auth::Signature implements HTTP Message Signatures, as per RFC 9421.
|
6
|
+
#
|
7
|
+
# Initialize with the Rack application that you want protecting.
|
8
|
+
# A hash with options and a block can be passed to customize, enhance
|
9
|
+
# or disable security checks applied to incoming requests.
|
10
|
+
#
|
11
|
+
module Rack
|
12
|
+
module Auth
|
13
|
+
class Signature
|
14
|
+
def initialize(app, options = {}, &block)
|
15
|
+
@app = app
|
16
|
+
@options = load_options(options)
|
17
|
+
instance_eval(&block) if block
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
@request = Rack::Request.new(env)
|
22
|
+
|
23
|
+
if excluded? || allowed?
|
24
|
+
@app.call(env)
|
25
|
+
else
|
26
|
+
response = options[:error_response].values
|
27
|
+
Rack::Response.new(*response).finish
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def excluded?
|
34
|
+
return false if !request
|
35
|
+
Array(options[:except]).include?(request.path_info)
|
36
|
+
end
|
37
|
+
|
38
|
+
def allowed?
|
39
|
+
has_signature? && acceptable? && verifiable?
|
40
|
+
end
|
41
|
+
|
42
|
+
DEFAULT_OPTIONS = {
|
43
|
+
no_older_than: 900,
|
44
|
+
created_required: true,
|
45
|
+
nonce_required: false,
|
46
|
+
alg_required: false,
|
47
|
+
tag_required: false,
|
48
|
+
expires_required: false,
|
49
|
+
keyid_required: false,
|
50
|
+
known_keys: {},
|
51
|
+
covered_components: %w[@method @request-target @authority date],
|
52
|
+
error_response: {body: [], status: 401, headers: {}}
|
53
|
+
}
|
54
|
+
|
55
|
+
private_constant :DEFAULT_OPTIONS
|
56
|
+
|
57
|
+
attr_reader :request, :options
|
58
|
+
|
59
|
+
def params
|
60
|
+
@signature.parameters || {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def logger
|
64
|
+
@logger ||= request.logger || ::Logger.new($stderr)
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_signature?
|
68
|
+
@message = Linzer::Message.new(request)
|
69
|
+
@signature = Linzer::Signature.build(@message.headers)
|
70
|
+
request.env["rack.signature"] = @signature
|
71
|
+
(@signature.to_h.keys & %w[signature signature-input]).size == 2
|
72
|
+
rescue => ex
|
73
|
+
logger.error ex.message
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
def created?
|
78
|
+
required = options[:created_required]
|
79
|
+
if required
|
80
|
+
params.key?("created") && Integer(params["created"])
|
81
|
+
else
|
82
|
+
!required
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def expires?
|
87
|
+
required = options[:expires_required]
|
88
|
+
if required
|
89
|
+
params.key?("expires") && Integer(params["expires"]) > Time.now.to_i
|
90
|
+
else
|
91
|
+
!required
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def keyid?
|
96
|
+
required = options[:keyid_required]
|
97
|
+
required ? params.key?("keyid") : !required
|
98
|
+
end
|
99
|
+
|
100
|
+
def nonce?
|
101
|
+
required = options[:nonce_required]
|
102
|
+
required ? params.key?("nonce") : !required
|
103
|
+
end
|
104
|
+
|
105
|
+
def alg?
|
106
|
+
required = options[:alg_required]
|
107
|
+
required ? params.key?("alg") : !required
|
108
|
+
end
|
109
|
+
|
110
|
+
def tag?
|
111
|
+
required = options[:tag_required]
|
112
|
+
required ? params.key?("tag") : !required
|
113
|
+
end
|
114
|
+
|
115
|
+
def has_required_params?
|
116
|
+
created? && expires? && keyid? && nonce? && alg? && tag?
|
117
|
+
rescue => ex
|
118
|
+
logger.error ex.message
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
122
|
+
def has_required_components?
|
123
|
+
components = @signature.components || []
|
124
|
+
covered_components = options[:covered_components]
|
125
|
+
warning = "Insufficient coverage by signature. Consult S 7.2.1. in RFC"
|
126
|
+
logger.warn warning if covered_components.empty?
|
127
|
+
(covered_components || []).all? { |c| components.include?(c) }
|
128
|
+
end
|
129
|
+
|
130
|
+
def acceptable?
|
131
|
+
has_required_params? && has_required_components?
|
132
|
+
end
|
133
|
+
|
134
|
+
def verifiable?
|
135
|
+
verify_opts = {}
|
136
|
+
|
137
|
+
warning = "Risk of signature replay! (Consult S 7.2.2. in RFC)"
|
138
|
+
logger.warn warning unless options[:no_older_than]
|
139
|
+
|
140
|
+
if options[:no_older_than]
|
141
|
+
age = Integer(options[:no_older_than])
|
142
|
+
verify_opts[:no_older_than] = age
|
143
|
+
end
|
144
|
+
|
145
|
+
Linzer.verify(key, @message, @signature, **verify_opts)
|
146
|
+
rescue => ex
|
147
|
+
logger.error ex.message
|
148
|
+
false
|
149
|
+
end
|
150
|
+
|
151
|
+
def key
|
152
|
+
build_key(params["keyid"] || :default)
|
153
|
+
end
|
154
|
+
|
155
|
+
def build_key(keyid)
|
156
|
+
material = if keyid == :default
|
157
|
+
options[:default_key]
|
158
|
+
else
|
159
|
+
key_data = options[:known_keys][keyid] || {}
|
160
|
+
if !key_data.key?("key") && key_data.key?("path")
|
161
|
+
key_data["key"] = IO.read(key_data["path"]) rescue nil
|
162
|
+
end
|
163
|
+
key_data
|
164
|
+
end
|
165
|
+
|
166
|
+
key_not_found = "Key not found. Signature cannot be verified."
|
167
|
+
raise Linzer::Error.new key_not_found if !material || !material["key"]
|
168
|
+
|
169
|
+
alg = @signature.parameters["alg"] || material["alg"] || :unknown
|
170
|
+
instantiate_key(keyid, alg, material)
|
171
|
+
end
|
172
|
+
|
173
|
+
def instantiate_key(keyid, alg, material)
|
174
|
+
key_methods = {
|
175
|
+
"rsa-pss-sha512" => :new_rsa_pss_sha512_key,
|
176
|
+
"rsa-v1_5-sha256" => :new_rsa_v1_5_sha256_key,
|
177
|
+
"hmac-sha256" => :new_hmac_sha256_key,
|
178
|
+
"ecdsa-p256-sha256" => :new_ecdsa_p256_sha256_key,
|
179
|
+
"ecdsa-p384-sha384" => :new_ecdsa_p384_sha384_key,
|
180
|
+
"ed25519" => :new_ed25519_public_key
|
181
|
+
}
|
182
|
+
method = key_methods[alg]
|
183
|
+
|
184
|
+
alg_error = "Unsupported or unknown signature algorithm"
|
185
|
+
raise Linzer::Error.new alg_error unless method
|
186
|
+
|
187
|
+
Linzer.public_send(method, material["key"], keyid.to_s)
|
188
|
+
end
|
189
|
+
|
190
|
+
def load_options(options)
|
191
|
+
DEFAULT_OPTIONS
|
192
|
+
.merge(load_options_from_config_file(options))
|
193
|
+
.merge(Hash(options)) || {}
|
194
|
+
end
|
195
|
+
|
196
|
+
def load_options_from_config_file(options)
|
197
|
+
config_path = options[:config_path]
|
198
|
+
YAML
|
199
|
+
.safe_load_file(config_path)
|
200
|
+
.transform_keys(&:to_sym)
|
201
|
+
rescue
|
202
|
+
{}
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Landaeta
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: openssl
|
@@ -104,7 +103,26 @@ dependencies:
|
|
104
103
|
- - ">="
|
105
104
|
- !ruby/object:Gem::Version
|
106
105
|
version: 3.1.2
|
107
|
-
|
106
|
+
- !ruby/object:Gem::Dependency
|
107
|
+
name: logger
|
108
|
+
requirement: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - "~>"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '1.7'
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: 1.7.0
|
116
|
+
type: :runtime
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - "~>"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '1.7'
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.7.0
|
108
126
|
email:
|
109
127
|
- miguel@miguel.cc
|
110
128
|
executables: []
|
@@ -118,6 +136,10 @@ files:
|
|
118
136
|
- LICENSE.txt
|
119
137
|
- README.md
|
120
138
|
- Rakefile
|
139
|
+
- examples/sinatra/Gemfile
|
140
|
+
- examples/sinatra/config.ru
|
141
|
+
- examples/sinatra/http-signatures.yml
|
142
|
+
- examples/sinatra/myapp.rb
|
121
143
|
- lib/linzer.rb
|
122
144
|
- lib/linzer/common.rb
|
123
145
|
- lib/linzer/ecdsa.rb
|
@@ -129,10 +151,12 @@ files:
|
|
129
151
|
- lib/linzer/request.rb
|
130
152
|
- lib/linzer/response.rb
|
131
153
|
- lib/linzer/rsa.rb
|
154
|
+
- lib/linzer/rsa_pss.rb
|
132
155
|
- lib/linzer/signature.rb
|
133
156
|
- lib/linzer/signer.rb
|
134
157
|
- lib/linzer/verifier.rb
|
135
158
|
- lib/linzer/version.rb
|
159
|
+
- lib/rack/auth/signature.rb
|
136
160
|
homepage: https://github.com/nomadium/linzer
|
137
161
|
licenses:
|
138
162
|
- MIT
|
@@ -140,7 +164,6 @@ metadata:
|
|
140
164
|
homepage_uri: https://github.com/nomadium/linzer
|
141
165
|
source_code_uri: https://github.com/nomadium/linzer
|
142
166
|
changelog_uri: https://github.com/nomadium/linzer/blob/master/CHANGELOG.md
|
143
|
-
post_install_message:
|
144
167
|
rdoc_options: []
|
145
168
|
require_paths:
|
146
169
|
- lib
|
@@ -155,8 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
178
|
- !ruby/object:Gem::Version
|
156
179
|
version: '0'
|
157
180
|
requirements: []
|
158
|
-
rubygems_version: 3.
|
159
|
-
signing_key:
|
181
|
+
rubygems_version: 3.6.7
|
160
182
|
specification_version: 4
|
161
183
|
summary: An implementation of HTTP Messages Signatures (RFC9421)
|
162
184
|
test_files: []
|