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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +153 -39
- data/examples/sinatra/Gemfile +1 -1
- data/examples/sinatra/http-signatures.yml +25 -18
- data/examples/sinatra/myapp.rb +11 -0
- data/lib/linzer/hmac.rb +13 -0
- data/lib/linzer/message/adapter/abstract.rb +152 -0
- data/lib/linzer/message/adapter/http_gem/response.rb +35 -0
- data/lib/linzer/message/adapter/net_http/request.rb +57 -0
- data/lib/linzer/message/adapter/net_http/response.rb +34 -0
- data/lib/linzer/message/adapter/rack/common.rb +106 -0
- data/lib/linzer/message/adapter/rack/request.rb +30 -0
- data/lib/linzer/message/adapter/rack/response.rb +31 -0
- data/lib/linzer/message/adapter.rb +8 -0
- data/lib/linzer/message/wrapper.rb +52 -0
- data/lib/linzer/message.rb +13 -192
- data/lib/linzer/signature.rb +7 -1
- data/lib/linzer/version.rb +1 -1
- data/lib/linzer.rb +25 -7
- data/lib/rack/auth/signature/helpers.rb +132 -0
- data/lib/rack/auth/signature.rb +25 -116
- metadata +47 -5
- data/lib/linzer/request.rb +0 -95
- data/lib/linzer/response.rb +0 -9
data/lib/rack/auth/signature.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "linzer"
|
2
2
|
require "logger"
|
3
|
-
|
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
|
-
@
|
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
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
152
|
-
|
153
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
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.
|
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:
|
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/
|
152
|
-
- lib/linzer/
|
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.
|
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: []
|
data/lib/linzer/request.rb
DELETED
@@ -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
|