linzer 0.6.2 → 0.6.4

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: db6a27eef77de787dbb8e481a845c44b2df6c284d98adc01d872fc10bb9f6061
4
- data.tar.gz: c500a2ee97f3b9b9ec7717fc9162c9f26d979301dfdcd8aad50298126e9943a3
3
+ metadata.gz: bff7f24f79244ceffa58e5b005b45b2384b34f2d23eda4c0a613060c4f708fa4
4
+ data.tar.gz: bfee805d74cf0e1fc3d3588ec6eac8a1de5b96a16021198d016f0be9fb48e371
5
5
  SHA512:
6
- metadata.gz: 828eabfdd8bb62382eb97b05b9ad7e75a8c8a71bdf082594d73abbf964735bf60fe8e31dcbfcd16e157bf14f369747e71631f54d7b896c7be3a8dfb336a1977f
7
- data.tar.gz: 65d6be87fe00af2685a7bed82990d3ffa3fd92f4bd5375d00b34cb7d4756e9dca4beee0871c410b62918236cf4fdfd23f0304f5f51294cd855f9774fe104b1d0
6
+ metadata.gz: d62f773a0d20b09bb97987180f251a70b4db5c29eb4ecf2112e486c9169e418085823c813fa68e4063347d421328f171604c3f82bdf1b6ffae0385bfc41fd8ce
7
+ data.tar.gz: 0e53c41f9485a8028f11f67e368ca0ec60a82f8e795973d258fb96a57cfdcfe085308f4cd2eb0ac55fe8b5ee39c1a4f8d56d3d9d09fa57ee720eda17831e66ae
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.4] - 2025-04-04
4
+
5
+ - Allow validating the `created` parameter to mitigate the
6
+ risk of replay attacks.
7
+ Pull request [#8](https://github.com/nomadium/linzer/pull/8)
8
+ by [oneiros](https://github.com/oneiros).
9
+
10
+ ## [0.6.3] - 2025-03-29
11
+
12
+ - Parse signature structured fields values as ASCII string.
13
+
3
14
  ## [0.6.2] - 2024-12-10
4
15
 
5
16
  - Remove dependency on ed25519 gem. Pull request
data/README.md CHANGED
@@ -117,6 +117,15 @@ Linzer.verify(pubkey, message, signature)
117
117
  # => true
118
118
  ```
119
119
 
120
+ To mitigate the risk of "replay attacks" (i.e. an attacker capturing a message with a valid signature and re-sending it at a later point) applications may want to validate the `created` parameter of the signature. Linzer can do this automatically when given the optional `no_older_than` keyword argument:
121
+
122
+ ```ruby
123
+ Linzer.verify(pubkey, message, signature, no_older_than: 500)
124
+ ```
125
+
126
+ `no_older_than` expects a number of seconds, but you can pass anything that to responds to `#to_i`, including an `ActiveSupport::Duration`.
127
+ `::verify` will raise if the `created` parameter of the signature is older than the given number of seconds.
128
+
120
129
  ### What if an invalid signature if verified?
121
130
 
122
131
  ```ruby
@@ -14,6 +14,18 @@ module Linzer
14
14
  alias_method :components, :metadata
15
15
  alias_method :bytes, :value
16
16
 
17
+ def created
18
+ Integer(parameters["created"])
19
+ rescue
20
+ return nil if parameters["created"].nil?
21
+ raise Error.new "Signature has a non-integer `created` parameter"
22
+ end
23
+
24
+ def older_than?(seconds)
25
+ raise Error.new "Signature is missing the `created` parameter" if created.nil?
26
+ (Time.now.to_i - created) > seconds
27
+ end
28
+
17
29
  def to_h
18
30
  {
19
31
  "signature" => Starry.serialize({label => value}),
@@ -31,11 +43,11 @@ module Linzer
31
43
  headers.transform_keys!(&:downcase)
32
44
  validate headers
33
45
 
34
- input = parse_field(headers, "signature-input")
46
+ input = parse_structured_field(headers, "signature-input")
35
47
  reject_multiple_signatures if input.size > 1 && options[:label].nil?
36
48
  label = options[:label] || input.keys.first
37
49
 
38
- signature = parse_field(headers, "signature")
50
+ signature = parse_structured_field(headers, "signature")
39
51
  fail_with_signature_not_found label unless signature.key?(label)
40
52
 
41
53
  raw_signature =
@@ -44,8 +56,7 @@ module Linzer
44
56
 
45
57
  fail_due_invalid_components unless input[label].value.respond_to?(:each)
46
58
 
47
- ascii = Encoding::US_ASCII
48
- components = input[label].value.map { |c| c.value.encode(ascii) }
59
+ components = input[label].value.map(&:value)
49
60
  parameters = input[label].parameters
50
61
 
51
62
  new(components, raw_signature, label, parameters)
@@ -75,8 +86,11 @@ module Linzer
75
86
  raise Error.new "Unexpected value for covered components."
76
87
  end
77
88
 
78
- def parse_field(hsh, field_name)
79
- Message.parse_structured_dictionary(hsh[field_name], field_name)
89
+ def parse_structured_field(hsh, field_name)
90
+ # Serialized Structured Field values for HTTP are ASCII strings.
91
+ # See: RFC 8941 (https://datatracker.ietf.org/doc/html/rfc8941)
92
+ value = hsh[field_name].encode(Encoding::US_ASCII)
93
+ Message.parse_structured_dictionary(value, field_name)
80
94
  end
81
95
  end
82
96
  end
@@ -5,8 +5,8 @@ module Linzer
5
5
  class << self
6
6
  include Common
7
7
 
8
- def verify(key, message, signature)
9
- validate message, key, signature
8
+ def verify(key, message, signature, no_older_than: nil)
9
+ validate message, key, signature, no_older_than: no_older_than
10
10
 
11
11
  parameters = signature.parameters
12
12
  components = signature.components
@@ -18,7 +18,7 @@ module Linzer
18
18
 
19
19
  private
20
20
 
21
- def validate(message, key, signature)
21
+ def validate(message, key, signature, no_older_than: nil)
22
22
  raise Error.new "Message to verify cannot be null" if message.nil?
23
23
  raise Error.new "Key to verify signature cannot be null" if key.nil?
24
24
  raise Error.new "Signature to verify cannot be null" if signature.nil?
@@ -31,6 +31,9 @@ module Linzer
31
31
  raise Error.new "Components cannot be null" if signature.components.nil?
32
32
 
33
33
  validate_components message, signature.components
34
+
35
+ return unless no_older_than
36
+ raise Error.new "Signature created more than #{no_older_than} seconds ago" if signature.older_than?(no_older_than.to_i)
34
37
  end
35
38
 
36
39
  def verify_or_fail(key, signature, data)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linzer
4
- VERSION = "0.6.2"
4
+ VERSION = "0.6.4"
5
5
  end
data/lib/linzer.rb CHANGED
@@ -32,8 +32,8 @@ module Linzer
32
32
  Linzer::Request.build(verb, uri, params, headers)
33
33
  end
34
34
 
35
- def verify(pubkey, message, signature)
36
- Linzer::Verifier.verify(pubkey, message, signature)
35
+ def verify(pubkey, message, signature, no_older_than: nil)
36
+ Linzer::Verifier.verify(pubkey, message, signature, no_older_than: no_older_than)
37
37
  end
38
38
 
39
39
  def sign(key, message, components, options = {})
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Landaeta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-10 00:00:00.000000000 Z
11
+ date: 2025-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: openssl