linzer 0.7.0.beta1 → 0.7.0.beta3

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.
@@ -1,6 +1,6 @@
1
1
  require "linzer"
2
2
  require "logger"
3
- require "yaml"
3
+ require_relative "signature/helpers"
4
4
 
5
5
  # Rack::Auth::Signature implements HTTP Message Signatures, as per RFC 9421.
6
6
  #
@@ -11,9 +11,11 @@ require "yaml"
11
11
  module Rack
12
12
  module Auth
13
13
  class Signature
14
+ include Helpers
15
+
14
16
  def initialize(app, options = {}, &block)
15
17
  @app = app
16
- @options = load_options(options)
18
+ @options = load_options(Hash(options))
17
19
  instance_eval(&block) if block
18
20
  end
19
21
 
@@ -23,7 +25,7 @@ module Rack
23
25
  if excluded? || allowed?
24
26
  @app.call(env)
25
27
  else
26
- response = options[:error_response].values
28
+ response = options[:signatures][:error_response].values
27
29
  Rack::Response.new(*response).finish
28
30
  end
29
31
  end
@@ -39,21 +41,6 @@ module Rack
39
41
  has_signature? && acceptable? && verifiable?
40
42
  end
41
43
 
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
44
  attr_reader :request, :options
58
45
 
59
46
  def params
@@ -65,51 +52,22 @@ module Rack
65
52
  end
66
53
 
67
54
  def has_signature?
68
- @message = Linzer::Message.new(request)
69
- @signature = Linzer::Signature.build(@message.headers)
70
- request.env["rack.signature"] = @signature
55
+ @signature = build_signature
71
56
  (@signature.to_h.keys & %w[signature signature-input]).size == 2
72
57
  rescue => ex
73
58
  logger.error ex.message
74
59
  false
75
60
  end
76
61
 
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
62
+ def build_signature
63
+ signature_opts = {}
64
+ label = options[:signatures][:default_label]
65
+ signature_opts[:label] = label if label
104
66
 
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
67
+ @message = Linzer::Message.new(request)
68
+ signature = Linzer::Signature.build(@message.headers, **signature_opts)
69
+ request.env["rack.signature"] = signature
70
+ signature
113
71
  end
114
72
 
115
73
  def has_required_params?
@@ -121,7 +79,7 @@ module Rack
121
79
 
122
80
  def has_required_components?
123
81
  components = @signature.components || []
124
- covered_components = options[:covered_components]
82
+ covered_components = options[:signatures][:covered_components]
125
83
  warning = "Insufficient coverage by signature. Consult S 7.2.1. in RFC"
126
84
  logger.warn warning if covered_components.empty?
127
85
  (covered_components || []).all? { |c| components.include?(c) }
@@ -132,74 +90,25 @@ module Rack
132
90
  end
133
91
 
134
92
  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
-
93
+ verify_opts = build_and_check_verify_opts || {}
145
94
  Linzer.verify(key, @message, @signature, **verify_opts)
146
95
  rescue => ex
147
96
  logger.error ex.message
148
97
  false
149
98
  end
150
99
 
151
- def key
152
- build_key(params["keyid"] || :default)
153
- end
100
+ def build_and_check_verify_opts
101
+ verify_opts = {}
102
+ reject_older = options[:signatures][:reject_older_than]
103
+ warning = "Risk of signature replay! (Consult S 7.2.2. in RFC)"
104
+ logger.warn warning unless reject_older
154
105
 
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
106
+ if reject_older
107
+ age = Integer(reject_older)
108
+ verify_opts[:no_older_than] = age
164
109
  end
165
110
 
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
- {}
111
+ verify_opts
203
112
  end
204
113
  end
205
114
  end
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.beta1
4
+ version: 0.7.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Landaeta
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2025-05-06 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: openssl
@@ -123,6 +123,40 @@ dependencies:
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
125
  version: 1.7.0
126
+ - !ruby/object:Gem::Dependency
127
+ name: forwardable
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '1.3'
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 1.3.3
136
+ type: :runtime
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: '1.3'
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 1.3.3
146
+ - !ruby/object:Gem::Dependency
147
+ name: net-http
148
+ requirement: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.6.0
153
+ type: :runtime
154
+ prerelease: false
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.6.0
126
160
  email:
127
161
  - miguel@miguel.cc
128
162
  executables: []
@@ -148,8 +182,15 @@ files:
148
182
  - lib/linzer/key.rb
149
183
  - lib/linzer/key/helper.rb
150
184
  - lib/linzer/message.rb
151
- - lib/linzer/request.rb
152
- - lib/linzer/response.rb
185
+ - lib/linzer/message/adapter.rb
186
+ - lib/linzer/message/adapter/abstract.rb
187
+ - lib/linzer/message/adapter/http_gem/response.rb
188
+ - lib/linzer/message/adapter/net_http/request.rb
189
+ - lib/linzer/message/adapter/net_http/response.rb
190
+ - lib/linzer/message/adapter/rack/common.rb
191
+ - lib/linzer/message/adapter/rack/request.rb
192
+ - lib/linzer/message/adapter/rack/response.rb
193
+ - lib/linzer/message/wrapper.rb
153
194
  - lib/linzer/rsa.rb
154
195
  - lib/linzer/rsa_pss.rb
155
196
  - lib/linzer/signature.rb
@@ -157,6 +198,7 @@ files:
157
198
  - lib/linzer/verifier.rb
158
199
  - lib/linzer/version.rb
159
200
  - lib/rack/auth/signature.rb
201
+ - lib/rack/auth/signature/helpers.rb
160
202
  homepage: https://github.com/nomadium/linzer
161
203
  licenses:
162
204
  - MIT
@@ -178,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
220
  - !ruby/object:Gem::Version
179
221
  version: '0'
180
222
  requirements: []
181
- rubygems_version: 3.6.7
223
+ rubygems_version: 3.6.2
182
224
  specification_version: 4
183
225
  summary: An implementation of HTTP Messages Signatures (RFC9421)
184
226
  test_files: []
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Linzer
4
- module Request
5
- extend self
6
-
7
- def build(verb, uri = "/", params = {}, headers = {})
8
- validate verb, uri, params, headers
9
-
10
- # XXX: to-do: handle rack request params?
11
- request_method = Rack.const_get(verb.upcase)
12
- args = {
13
- "REQUEST_METHOD" => request_method,
14
- "PATH_INFO" => uri.to_str,
15
- "rack.input" => StringIO.new
16
- }
17
-
18
- Rack::Request.new(build_rack_env(headers).merge(args))
19
- end
20
-
21
- def rack_header_name(field_name)
22
- validate_header_name field_name
23
-
24
- rack_name = field_name.upcase.tr("-", "_")
25
- case field_name.downcase
26
- when "content-type", "content-length"
27
- rack_name
28
- else
29
- "HTTP_#{rack_name}"
30
- end
31
- end
32
-
33
- def headers(rack_request)
34
- rack_request
35
- .each_header
36
- .to_h
37
- .select do |k, _|
38
- k.start_with?("HTTP_") || %w[CONTENT_TYPE CONTENT_LENGTH].include?(k)
39
- end
40
- .transform_keys { |k| k.downcase.tr("_", "-") }
41
- .transform_keys do |k|
42
- %w[content-type content-length].include?(k) ? k : k.gsub(/^http-/, "")
43
- end
44
- end
45
-
46
- private
47
-
48
- def validate(verb, uri, params, headers)
49
- validate_verb verb
50
- validate_uri uri
51
- validate_arg_hash headers: headers
52
- validate_arg_hash params: params
53
- end
54
-
55
- def validate_verb(verb)
56
- Rack.const_get(verb.upcase)
57
- rescue => ex
58
- unknown_method = "Unknown/invalid HTTP request method"
59
- raise Error.new, unknown_method, cause: ex
60
- end
61
-
62
- def validate_uri(uri)
63
- uri.to_str
64
- rescue => ex
65
- invalid_uri = "Invalid URI"
66
- raise Error.new, invalid_uri, cause: ex
67
- end
68
-
69
- def validate_arg_hash(hsh)
70
- arg_name = hsh.keys.first
71
- hsh[arg_name].to_hash
72
- rescue => ex
73
- err_msg = "invalid \"#{arg_name}\" parameter, cannot be converted to hash."
74
- raise Error.new, "Cannot build request: #{err_msg}", cause: ex
75
- end
76
-
77
- def validate_header_name(name)
78
- raise ArgumentError.new, "Blank header name." if name.empty?
79
- name.to_str
80
- rescue => ex
81
- err_msg = "Invalid header name: '#{name}'"
82
- raise Error.new, err_msg, cause: ex
83
- end
84
-
85
- def build_rack_env(headers)
86
- headers
87
- .to_hash
88
- .transform_values(&:to_s)
89
- .transform_keys { |k| k.upcase.tr("-", "_") }
90
- .transform_keys do |k|
91
- %w[CONTENT_TYPE CONTENT_LENGTH].include?(k) ? k : "HTTP_#{k}"
92
- end
93
- end
94
- end
95
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Linzer
4
- module Response
5
- def new_response(body = nil, status = 200, headers = {})
6
- Rack::Response.new(body, status, headers.transform_values(&:to_s))
7
- end
8
- end
9
- end