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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65ccb6f89aab47730e202a8ad376b2ceb310213b2a140d194b2b6fd285aa710f
4
- data.tar.gz: 485efd259e353048041528c38f0072945592cfe6f8d4c4e6ea20af5546e6cb94
3
+ metadata.gz: 5cfa8303792377ef25d8a81300b1e22c2e6593237436e152ec04e99176eea1f7
4
+ data.tar.gz: e2f89b7b4d771a9912000961862bf77cda5ccf2caf40a3906394afa1b1595adc
5
5
  SHA512:
6
- metadata.gz: a01a27867e6dd628365268f1106880b7f61ad80b4d314ea336e21e4f1a4edbb17394d0c7da4f23256566e34a6acb2e6eec54680461141d917189a37219d3c84e
7
- data.tar.gz: 2efef6b1fcad53964e1f54c8f1453b2ad3aa481ca98960ada2e735439d0736624118a99ab58d1f73fdef517f9802e1453215e17bfc660e56dae81cd5b044769d
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
- ### Use the signed request with an HTTP client:
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, MyOwnResponseAdapter)
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
@@ -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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linzer
4
+ module Options
5
+ DEFAULT = {
6
+ covered_components: %w[@method @request-target @authority date]
7
+ }.freeze
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linzer
4
- VERSION = "0.7.0.beta3"
4
+ VERSION = "0.7.0.beta4"
5
5
  end
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "yaml"
2
4
 
3
5
  module Rack
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "linzer"
2
4
  require "logger"
3
5
  require_relative "signature/helpers"
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.beta3
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: 2025-05-06 00:00:00.000000000 Z
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.2
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: []