linzer 0.7.2 → 0.7.3
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 +13 -0
- data/README.md +4 -0
- data/examples/sinatra/Gemfile +1 -1
- data/lib/linzer/common.rb +3 -0
- data/lib/linzer/helper.rb +32 -0
- data/lib/linzer/jws.rb +60 -0
- data/lib/linzer/key/helper.rb +8 -0
- data/lib/linzer/message/adapter/rack/common.rb +5 -15
- data/lib/linzer/message/adapter/rack/response.rb +2 -2
- data/lib/linzer/version.rb +1 -1
- data/lib/linzer.rb +4 -25
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13262f7f33737c2ad3aec4007e612001dc9d72c97f9a0636681f0ae695d98315
|
4
|
+
data.tar.gz: c9663a699455f8cab8cc2214c0af3da835a4e46542f6029dcddece40ad5b9a35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4e7386cf5bb74bd20d2c74bd3fd8054c068ccc1b8704a088296ead0f61c5029cccd51744d974ef1123166a30068790590cafe6c4e3f27c55c67e0637a0073bb
|
7
|
+
data.tar.gz: 798032dd1a2c345f51fd23281bd9028a1ff31495f41e8d4c241d653d1857233e1c9c3bb327539c2296a399d0b5df4bbf02b9954e252ba486a022c80f79066785
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.7.3] - 2025-06-01
|
4
|
+
|
5
|
+
- Fix broken retrieval of header names from Rack responses.
|
6
|
+
Previously, this caused signatures attached to Rack response instances
|
7
|
+
to use incorrect header names, making them unverifiable.
|
8
|
+
|
9
|
+
- Add Linzer.signature_base method.
|
10
|
+
|
11
|
+
- Add initial support for JWS algorithms. See Linzer::JWS module for more details.
|
12
|
+
In this initial preview, only EdDSA algorithm (Ed25519) is supported).
|
13
|
+
|
14
|
+
- Add a simple integration test to verify signatures on HTTP responses.
|
15
|
+
|
3
16
|
## [0.7.2] - 2025-05-21
|
4
17
|
|
5
18
|
- Add a few integration tests against CloudFlare test server.
|
data/README.md
CHANGED
@@ -356,6 +356,10 @@ in subsequent releases.
|
|
356
356
|
|
357
357
|
linzer is built in [Continuous Integration](https://github.com/nomadium/linzer/actions/workflows/main.yml) on Ruby 3.0+.
|
358
358
|
|
359
|
+
## Security
|
360
|
+
|
361
|
+
This gem is provided “as is” without any warranties. It has not been audited for security vulnerabilities. Users are advised to review the code and assess its suitability for their use case, particularly in production environments.
|
362
|
+
|
359
363
|
## Development
|
360
364
|
|
361
365
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/examples/sinatra/Gemfile
CHANGED
data/lib/linzer/common.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Linzer
|
4
|
+
module Helper
|
5
|
+
def sign!(request_or_response, **args)
|
6
|
+
message = Message.new(request_or_response)
|
7
|
+
options = {}
|
8
|
+
|
9
|
+
label = args[:label]
|
10
|
+
options[:label] = label if label
|
11
|
+
options.merge!(args.fetch(:params, {}))
|
12
|
+
|
13
|
+
key = args.fetch(:key)
|
14
|
+
signature = Linzer::Signer.sign(key, message, args.fetch(:components), options)
|
15
|
+
message.attach!(signature)
|
16
|
+
end
|
17
|
+
|
18
|
+
def verify!(request_or_response, key: nil, no_older_than: 900)
|
19
|
+
message = Message.new(request_or_response)
|
20
|
+
signature_headers = {}
|
21
|
+
%w[signature-input signature].each do |name|
|
22
|
+
value = message.header(name)
|
23
|
+
signature_headers[name] = value if value
|
24
|
+
end
|
25
|
+
signature = Signature.build(signature_headers)
|
26
|
+
keyid = signature.parameters["keyid"]
|
27
|
+
raise Linzer::Error, "key not found" if !key && !keyid
|
28
|
+
verify_key = block_given? ? (yield keyid) : key
|
29
|
+
Linzer.verify(verify_key, message, signature, no_older_than: no_older_than)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/linzer/jws.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jwt"
|
4
|
+
require "jwt/eddsa"
|
5
|
+
require "ed25519"
|
6
|
+
|
7
|
+
module Linzer
|
8
|
+
module JWS
|
9
|
+
def jwk_import(key, params = {})
|
10
|
+
material = JWT::JWK.import(key)
|
11
|
+
Linzer::JWS::Key.new(material, params)
|
12
|
+
end
|
13
|
+
module_function :jwk_import
|
14
|
+
|
15
|
+
def generate_key(algorithm:)
|
16
|
+
case String(algorithm)
|
17
|
+
when "EdDSA"
|
18
|
+
ed25519_keypair = ::Ed25519::SigningKey.generate
|
19
|
+
material = JWT::JWK.new(ed25519_keypair)
|
20
|
+
Linzer::JWS::Key.new(material)
|
21
|
+
else
|
22
|
+
err_msg = "Algorithm '#{algorithm}' is unsupported or not implemented yet."
|
23
|
+
raise Linzer::Error, err_msg
|
24
|
+
end
|
25
|
+
end
|
26
|
+
module_function :generate_key
|
27
|
+
|
28
|
+
class Key < Linzer::Key
|
29
|
+
def sign(data)
|
30
|
+
raise Linzer::SigningError, "Private key is missing!" if !material.private?
|
31
|
+
algo = resolve_algorithm
|
32
|
+
algo.sign(data: data, signing_key: signing_key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify(signature, data)
|
36
|
+
algo = resolve_algorithm
|
37
|
+
algo.verify(data: data, signature: signature, verification_key: verify_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def resolve_algorithm
|
43
|
+
case
|
44
|
+
when material.verify_key.is_a?(::Ed25519::VerifyKey)
|
45
|
+
JWT::JWA.resolve("EdDSA")
|
46
|
+
else
|
47
|
+
raise Linzer::Error, "Unknown/unsupported algorithm"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def verify_key
|
52
|
+
material.verify_key
|
53
|
+
end
|
54
|
+
|
55
|
+
def signing_key
|
56
|
+
material.signing_key
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/linzer/key/helper.rb
CHANGED
@@ -85,6 +85,14 @@ module Linzer
|
|
85
85
|
key = OpenSSL::PKey::EC.new(material)
|
86
86
|
Linzer::ECDSA::Key.new(key, id: key_id, digest: "SHA384")
|
87
87
|
end
|
88
|
+
|
89
|
+
def generate_jws_key(algorithm:)
|
90
|
+
Linzer::JWS.generate_key(algorithm: algorithm)
|
91
|
+
end
|
92
|
+
|
93
|
+
def jwk_import(key, params = {})
|
94
|
+
Linzer::JWS.jwk_import(key, params)
|
95
|
+
end
|
88
96
|
end
|
89
97
|
end
|
90
98
|
end
|
@@ -28,8 +28,11 @@ module Linzer
|
|
28
28
|
raise ArgumentError.new, "Blank header name." if name.empty?
|
29
29
|
name.to_str
|
30
30
|
rescue => ex
|
31
|
+
# :nocov:
|
32
|
+
# XXX: this block of code seems to be unreachable
|
31
33
|
err_msg = "Invalid header name: '#{name}'"
|
32
|
-
raise Linzer::Error
|
34
|
+
raise Linzer::Error, err_msg, cause: ex
|
35
|
+
# :nocov:
|
33
36
|
end
|
34
37
|
|
35
38
|
def rack_header_name(field_name)
|
@@ -44,19 +47,6 @@ module Linzer
|
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
|
-
def rack_request_headers(rack_request)
|
48
|
-
rack_request
|
49
|
-
.each_header
|
50
|
-
.to_h
|
51
|
-
.select do |k, _|
|
52
|
-
k.start_with?("HTTP_") || %w[CONTENT_TYPE CONTENT_LENGTH].include?(k)
|
53
|
-
end
|
54
|
-
.transform_keys { |k| k.downcase.tr("_", "-") }
|
55
|
-
.transform_keys do |k|
|
56
|
-
%w[content-type content-length].include?(k) ? k : k.gsub(/^http-/, "")
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
50
|
def derived(name)
|
61
51
|
method = DERIVED_COMPONENT[name.value]
|
62
52
|
|
@@ -76,7 +66,7 @@ module Linzer
|
|
76
66
|
else
|
77
67
|
rack_header_name = rack_header_name(name.value.to_s)
|
78
68
|
value = @operation.env[rack_header_name] if request?
|
79
|
-
value = @operation.get_header(
|
69
|
+
value = @operation.get_header(name.value.to_s) if response?
|
80
70
|
end
|
81
71
|
value.dup&.strip
|
82
72
|
end
|
@@ -17,12 +17,12 @@ module Linzer
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def header(name)
|
20
|
-
@operation.get_header(
|
20
|
+
@operation.get_header(name)
|
21
21
|
end
|
22
22
|
|
23
23
|
def attach!(signature)
|
24
24
|
signature.to_h.each do |h, v|
|
25
|
-
@operation.set_header(
|
25
|
+
@operation.set_header(h, v)
|
26
26
|
end
|
27
27
|
@operation
|
28
28
|
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/helper"
|
12
13
|
require_relative "linzer/options"
|
13
14
|
require_relative "linzer/message"
|
14
15
|
require_relative "linzer/message/adapter"
|
@@ -35,6 +36,7 @@ module Linzer
|
|
35
36
|
|
36
37
|
class << self
|
37
38
|
include Key::Helper
|
39
|
+
include Helper
|
38
40
|
|
39
41
|
def verify(pubkey, message, signature, no_older_than: nil)
|
40
42
|
Linzer::Verifier.verify(pubkey, message, signature, no_older_than: no_older_than)
|
@@ -44,31 +46,8 @@ module Linzer
|
|
44
46
|
Linzer::Signer.sign(key, message, components, options)
|
45
47
|
end
|
46
48
|
|
47
|
-
def
|
48
|
-
message
|
49
|
-
options = {}
|
50
|
-
|
51
|
-
label = args[:label]
|
52
|
-
options[:label] = label if label
|
53
|
-
options.merge!(args.fetch(:params, {}))
|
54
|
-
|
55
|
-
key = args.fetch(:key)
|
56
|
-
signature = Linzer::Signer.sign(key, message, args.fetch(:components), options)
|
57
|
-
message.attach!(signature)
|
58
|
-
end
|
59
|
-
|
60
|
-
def verify!(request_or_response, key: nil, no_older_than: 900)
|
61
|
-
message = Message.new(request_or_response)
|
62
|
-
signature_headers = {}
|
63
|
-
%w[signature-input signature].each do |name|
|
64
|
-
value = message.header(name)
|
65
|
-
signature_headers[name] = value if value
|
66
|
-
end
|
67
|
-
signature = Signature.build(signature_headers)
|
68
|
-
keyid = signature.parameters["keyid"]
|
69
|
-
raise Linzer::Error, "key not found" if !key && !keyid
|
70
|
-
verify_key = block_given? ? (yield keyid) : key
|
71
|
-
Linzer.verify(verify_key, message, signature, no_older_than: no_older_than)
|
49
|
+
def signature_base(message, components, parameters)
|
50
|
+
Linzer::Common.signature_base(message, components, parameters)
|
72
51
|
end
|
73
52
|
end
|
74
53
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miguel Landaeta
|
@@ -192,10 +192,12 @@ files:
|
|
192
192
|
- lib/linzer/common.rb
|
193
193
|
- lib/linzer/ecdsa.rb
|
194
194
|
- lib/linzer/ed25519.rb
|
195
|
+
- lib/linzer/helper.rb
|
195
196
|
- lib/linzer/hmac.rb
|
196
197
|
- lib/linzer/http.rb
|
197
198
|
- lib/linzer/http/bootstrap.rb
|
198
199
|
- lib/linzer/http/signature_feature.rb
|
200
|
+
- lib/linzer/jws.rb
|
199
201
|
- lib/linzer/key.rb
|
200
202
|
- lib/linzer/key/helper.rb
|
201
203
|
- lib/linzer/message.rb
|