linzer 0.7.0.beta3 → 0.7.0.beta4
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/CHANGELOG.md +5 -0
- data/README.md +48 -2
- data/lib/linzer/http/bootstrap.rb +21 -0
- data/lib/linzer/http/signature_feature.rb +53 -0
- data/lib/linzer/http.rb +100 -0
- data/lib/linzer/message/adapter/http_gem/request.rb +22 -0
- data/lib/linzer/options.rb +9 -0
- data/lib/linzer/version.rb +1 -1
- data/lib/linzer.rb +2 -0
- data/lib/rack/auth/signature/helpers.rb +2 -0
- data/lib/rack/auth/signature.rb +2 -0
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cfa8303792377ef25d8a81300b1e22c2e6593237436e152ec04e99176eea1f7
|
4
|
+
data.tar.gz: e2f89b7b4d771a9912000961862bf77cda5ccf2caf40a3906394afa1b1595adc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3881ae41ea932485f39838bba647f60fce089f740dabe7db7e01d34c368ebaf85ec71788d997d64d9cc662811dd703babd62300f7d02c6a21072c1dafbb45ee5
|
7
|
+
data.tar.gz: 7a9589f81440612f7e3bc959973aa947614c132f67db247e043f911d1653c7621c376ade8005181fa6fc4814e542ec82556bf79ec5f2b0d3ede739d48d4ffeb0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.7.0.beta4] - 2025-05-17
|
4
|
+
|
5
|
+
- Provide integration with http.rb gem to allow signing outgoing HTTP requests.
|
6
|
+
- Add simple HTTP client module.
|
7
|
+
|
3
8
|
## [0.7.0.beta3] - 2025-05-06 (aka the ["MiniDebConf Hamburg 2025"](https://wiki.debian.org/DebianEvents/de/2025/MiniDebConfHamburg) release)
|
4
9
|
|
5
10
|
- Refactor to improve Linzer APIs and streamline its usage along with different
|
data/README.md
CHANGED
@@ -60,6 +60,31 @@ To learn about more specific scenarios or use cases, keep reading on below.
|
|
60
60
|
|
61
61
|
### To sign a HTTP request:
|
62
62
|
|
63
|
+
There are several options:
|
64
|
+
|
65
|
+
#### If you are using http gem:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# first require http signatures feature class ready to be used with http gem:
|
69
|
+
require "linzer/http/signature_feature"
|
70
|
+
|
71
|
+
key = Linzer.generate_ed25519_key # generate a new key pair
|
72
|
+
# => #<Linzer::Ed25519::Key:0x00000fe13e9bd208
|
73
|
+
# or load an existing key with:
|
74
|
+
# key = Linzer.new_ed25519_key(IO.read("key"), "mykeyid")
|
75
|
+
|
76
|
+
# then send the request:
|
77
|
+
url = "https://example.org/api"
|
78
|
+
response = HTTP.headers(date: Time.now.to_s, foo: "bar")
|
79
|
+
.use(http_signature: {key: key} # <--- covered components
|
80
|
+
.get(url) # and signature params can also be customized on the client
|
81
|
+
=> #<HTTP::Response/1.1 200 OK {"Content-Type" => ...
|
82
|
+
response.body.to_s
|
83
|
+
=> "protected content..."
|
84
|
+
```
|
85
|
+
|
86
|
+
#### If you are using plain old Net::HTTP:
|
87
|
+
|
63
88
|
```ruby
|
64
89
|
key = Linzer.generate_ed25519_key
|
65
90
|
# => #<Linzer::Ed25519::Key:0x00000fe13e9bd208
|
@@ -84,7 +109,7 @@ request["signature-input"]
|
|
84
109
|
# => "sig1=(\"@method\" \"@request-target\" \"date\" ..."}
|
85
110
|
```
|
86
111
|
|
87
|
-
|
112
|
+
Then you can submit the signed request with Net::HTTP client:
|
88
113
|
|
89
114
|
```ruby
|
90
115
|
require "net/http"
|
@@ -125,6 +150,26 @@ response = http.request(request)
|
|
125
150
|
# => #<Net::HTTPOK 200 OK readbody=true>
|
126
151
|
```
|
127
152
|
|
153
|
+
#### Or you can also use the simple HTTP client bundled with this library:
|
154
|
+
|
155
|
+
(This client is probably not suitable for production use but could be useful
|
156
|
+
enough to get started. It's build on top of Net::HTTP.)
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
key = Linzer.generate_rsa_pss_sha512_key(4096)
|
160
|
+
uri = URI("https://example.org/api/task")
|
161
|
+
headers = {"date" => Time.now.to_s}
|
162
|
+
response =
|
163
|
+
Linzer::HTTP
|
164
|
+
.post("http://httpbin.org/headers",
|
165
|
+
data: "foo",
|
166
|
+
debug: true,
|
167
|
+
key: key,
|
168
|
+
headers: headers)
|
169
|
+
...
|
170
|
+
=> #<Net::HTTPOK 200 OK readbody=true>
|
171
|
+
```
|
172
|
+
|
128
173
|
### To verify an incoming request on the server side:
|
129
174
|
|
130
175
|
The middleware `Rack::Auth::Signature` can be used for this scenario
|
@@ -220,7 +265,8 @@ For how to register a custom adapter and how to verify signatures in a response,
|
|
220
265
|
see this example:
|
221
266
|
|
222
267
|
```ruby
|
223
|
-
Linzer::Message.register_adapter(HTTP::Response,
|
268
|
+
Linzer::Message.register_adapter(HTTP::Response, Linzer::Message::Adapter::HTTPGem::Response)
|
269
|
+
# Linzer::Message.register_adapter(HTTP::Response, MyOwnResponseAdapter) # or use your own adapter
|
224
270
|
response = HTTP.get("http://www.example.com/api/service/task")
|
225
271
|
# => #<HTTP::Response/1.1 200 OK ...
|
226
272
|
response["signature"]
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Linzer
|
4
|
+
module HTTP
|
5
|
+
module Bootstrap
|
6
|
+
class << self
|
7
|
+
def require_dependencies
|
8
|
+
require "http"
|
9
|
+
require_relative "../message/adapter/http_gem/request"
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_dependencies
|
13
|
+
require_dependencies
|
14
|
+
rescue LoadError
|
15
|
+
msg = "http gem is required to be installed to use this feature."
|
16
|
+
raise Linzer::Error, msg
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "bootstrap"
|
4
|
+
|
5
|
+
module Linzer
|
6
|
+
module HTTP
|
7
|
+
class << self
|
8
|
+
def register_adapter
|
9
|
+
request_class = ::HTTP::Request
|
10
|
+
adapter_class = Linzer::Message::Adapter::HTTPGem::Request
|
11
|
+
Linzer::Message.register_adapter(request_class, adapter_class)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Bootstrap.load_dependencies
|
16
|
+
register_adapter
|
17
|
+
|
18
|
+
class SignatureFeature < ::HTTP::Feature
|
19
|
+
def initialize(key:, params: {}, covered_components: default_components)
|
20
|
+
@fields = Array(covered_components)
|
21
|
+
@key = validate_key(key)
|
22
|
+
@params = Hash(params)
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :fields, :params
|
26
|
+
|
27
|
+
def wrap_request(request)
|
28
|
+
message = Linzer::Message.new(request)
|
29
|
+
signature = Linzer.sign(key, message, fields, **params)
|
30
|
+
request.headers.merge!(signature.to_h)
|
31
|
+
request
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_covered_components
|
35
|
+
Linzer::Options::DEFAULT[:covered_components]
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :default_components, :default_covered_components
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :key
|
43
|
+
|
44
|
+
def validate_key(key)
|
45
|
+
raise ::HTTP::Error, "Key can not be nil!" if !key
|
46
|
+
raise ::HTTP::Error, "Key object is invalid!" if !key.respond_to?(:sign)
|
47
|
+
key
|
48
|
+
end
|
49
|
+
|
50
|
+
::HTTP::Options.register_feature(:http_signature, self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/linzer/http.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
|
5
|
+
module Linzer
|
6
|
+
module HTTP
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def self.known_http_methods
|
10
|
+
Net::HTTP
|
11
|
+
.constants
|
12
|
+
.map { |const| Net::HTTP.const_get(const) }
|
13
|
+
.select { |klass| klass.is_a?(Class) && klass.const_defined?(:METHOD) }
|
14
|
+
.map { |klass| klass::METHOD }
|
15
|
+
.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
known_http_methods.each do |http_method| # e.g.:
|
19
|
+
method = http_method.downcase.to_sym #
|
20
|
+
define_method(method) do |uri, options| # def post(uri, **options)
|
21
|
+
options ||= {} # request :post, uri, options
|
22
|
+
request method, uri, options # end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def request(verb, uri, options = {})
|
29
|
+
validate_verb(verb)
|
30
|
+
|
31
|
+
key = options[:key]
|
32
|
+
validate_key(key)
|
33
|
+
|
34
|
+
req_uri = URI(uri)
|
35
|
+
http = Net::HTTP.new(req_uri.host, req_uri.port)
|
36
|
+
http.set_debug_output($stderr) if options[:debug]
|
37
|
+
|
38
|
+
headers = build_headers(options[:headers] || {})
|
39
|
+
request = build_request(verb, uri, headers)
|
40
|
+
message = Linzer::Message.new(request)
|
41
|
+
components = options[:covered_components] || default_components
|
42
|
+
params = options[:params] || {}
|
43
|
+
signature = Linzer.sign(key, message, components, **params)
|
44
|
+
|
45
|
+
do_request(http, uri, verb, options[:data], signature, headers)
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_components
|
49
|
+
Linzer::Options::DEFAULT[:covered_components]
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_verb(verb)
|
53
|
+
method_name = verb.to_s.upcase
|
54
|
+
if !known_http_methods.include?(method_name)
|
55
|
+
raise Linzer::Error, "Unknown/unsupported HTTP method: '#{method_name}'"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_key(key)
|
60
|
+
raise Linzer::Error, "Key can not be nil!" if !key
|
61
|
+
raise Linzer::Error, "Key object is invalid!" if !key.respond_to?(:sign)
|
62
|
+
key
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_headers(headers)
|
66
|
+
headers.merge({"user-agent" => "Linzer/#{Linzer::VERSION}"})
|
67
|
+
end
|
68
|
+
|
69
|
+
def build_request(method, uri, headers)
|
70
|
+
request_class = Net::HTTP.const_get(method.to_s.capitalize)
|
71
|
+
request = request_class.new(URI(uri))
|
72
|
+
headers.map { |k, v| request[k] = v }
|
73
|
+
request
|
74
|
+
end
|
75
|
+
|
76
|
+
def with_body?(verb)
|
77
|
+
# common HTTP
|
78
|
+
return false if %i[get head options trace delete].include?(verb)
|
79
|
+
# WebDAV
|
80
|
+
return false if %i[copy move].include?(verb)
|
81
|
+
|
82
|
+
# everything else that could have a body:
|
83
|
+
# common HTTP: post, put, patch
|
84
|
+
# WebDAV: lock, unlock, mkcol, propfind, proppatch
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def do_request(http, uri, verb, data, signature, headers)
|
89
|
+
if with_body?(verb)
|
90
|
+
if !data
|
91
|
+
missed_body = "Missing request body on HTTP request: '#{verb.upcase}'"
|
92
|
+
raise Linzer::Error, missed_body
|
93
|
+
end
|
94
|
+
http.public_send(verb, uri, data, headers.merge(signature.to_h))
|
95
|
+
else
|
96
|
+
http.public_send(verb, uri, headers.merge(signature.to_h))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Linzer
|
4
|
+
class Message
|
5
|
+
module Adapter
|
6
|
+
module HTTPGem
|
7
|
+
class Request < Linzer::Message::Adapter::NetHTTP::Request
|
8
|
+
def headers
|
9
|
+
@operation.headers.to_h
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def derived(name)
|
15
|
+
return @operation.verb.to_s.upcase if name.value == :method
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/linzer/version.rb
CHANGED
data/lib/linzer.rb
CHANGED
@@ -9,6 +9,7 @@ require "net/http"
|
|
9
9
|
|
10
10
|
require_relative "linzer/version"
|
11
11
|
require_relative "linzer/common"
|
12
|
+
require_relative "linzer/options"
|
12
13
|
require_relative "linzer/message"
|
13
14
|
require_relative "linzer/message/adapter"
|
14
15
|
require_relative "linzer/message/wrapper"
|
@@ -22,6 +23,7 @@ require_relative "linzer/ecdsa"
|
|
22
23
|
require_relative "linzer/key/helper"
|
23
24
|
require_relative "linzer/signer"
|
24
25
|
require_relative "linzer/verifier"
|
26
|
+
require_relative "linzer/http"
|
25
27
|
require_relative "rack/auth/signature"
|
26
28
|
|
27
29
|
module Linzer
|
data/lib/rack/auth/signature.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.0.
|
4
|
+
version: 0.7.0.beta4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Landaeta
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: openssl
|
@@ -157,6 +157,20 @@ dependencies:
|
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: 0.6.0
|
160
|
+
- !ruby/object:Gem::Dependency
|
161
|
+
name: cgi
|
162
|
+
requirement: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 0.4.2
|
167
|
+
type: :runtime
|
168
|
+
prerelease: false
|
169
|
+
version_requirements: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.4.2
|
160
174
|
email:
|
161
175
|
- miguel@miguel.cc
|
162
176
|
executables: []
|
@@ -179,11 +193,15 @@ files:
|
|
179
193
|
- lib/linzer/ecdsa.rb
|
180
194
|
- lib/linzer/ed25519.rb
|
181
195
|
- lib/linzer/hmac.rb
|
196
|
+
- lib/linzer/http.rb
|
197
|
+
- lib/linzer/http/bootstrap.rb
|
198
|
+
- lib/linzer/http/signature_feature.rb
|
182
199
|
- lib/linzer/key.rb
|
183
200
|
- lib/linzer/key/helper.rb
|
184
201
|
- lib/linzer/message.rb
|
185
202
|
- lib/linzer/message/adapter.rb
|
186
203
|
- lib/linzer/message/adapter/abstract.rb
|
204
|
+
- lib/linzer/message/adapter/http_gem/request.rb
|
187
205
|
- lib/linzer/message/adapter/http_gem/response.rb
|
188
206
|
- lib/linzer/message/adapter/net_http/request.rb
|
189
207
|
- lib/linzer/message/adapter/net_http/response.rb
|
@@ -191,6 +209,7 @@ files:
|
|
191
209
|
- lib/linzer/message/adapter/rack/request.rb
|
192
210
|
- lib/linzer/message/adapter/rack/response.rb
|
193
211
|
- lib/linzer/message/wrapper.rb
|
212
|
+
- lib/linzer/options.rb
|
194
213
|
- lib/linzer/rsa.rb
|
195
214
|
- lib/linzer/rsa_pss.rb
|
196
215
|
- lib/linzer/signature.rb
|
@@ -220,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
239
|
- !ruby/object:Gem::Version
|
221
240
|
version: '0'
|
222
241
|
requirements: []
|
223
|
-
rubygems_version: 3.6.
|
242
|
+
rubygems_version: 3.6.8
|
224
243
|
specification_version: 4
|
225
244
|
summary: An implementation of HTTP Messages Signatures (RFC9421)
|
226
245
|
test_files: []
|