Class: Linzer::Signature
- Inherits:
-
Object
- Object
- Linzer::Signature
- Defined in:
- lib/linzer/signature.rb
Overview
Represents an HTTP message signature as defined in RFC 9421.
A Signature encapsulates:
-
The raw signature bytes
-
The covered components (fields included in the signature)
-
The signature parameters (created, keyid, etc.)
-
The signature label (for identifying multiple signatures)
Signatures are immutable once created. Use Signature.build to create instances from HTTP headers, or receive them from Linzer::Signer.sign.
Instance Attribute Summary collapse
-
#label ⇒ Object
readonly
Returns the value of attribute label.
-
#metadata ⇒ Object
(also: #serialized_components)
readonly
Returns the value of attribute metadata.
-
#parameters ⇒ Object
readonly
Returns the value of attribute parameters.
-
#value ⇒ Object
(also: #bytes)
readonly
Returns the value of attribute value.
Class Method Summary collapse
-
.build(headers, options = {}) ⇒ Signature
Builds a Signature from HTTP headers.
-
.from_components(components:, raw_signature:, label:, parameters:, parsed_items:, headers:) ⇒ Signature
private
Creates a Signature directly from its constituent parts.
Instance Method Summary collapse
-
#components ⇒ Array<String>
Returns the deserialized component identifiers.
-
#created ⇒ Integer?
Returns the signature creation timestamp.
-
#expired? ⇒ Boolean
Checks if the signature has expired based on the ‘expires` parameter.
-
#field_ids ⇒ Array<FastIdentifier, FieldId>
Builds FieldId objects for each covered component.
-
#initialize(metadata, value, label, parameters = {}, parsed_items: nil, headers: nil) ⇒ Signature
constructor
private
Use Signature.build or Signature.from_components to create Signature instances.
-
#older_than?(seconds) ⇒ Boolean
Checks if the signature is older than a given number of seconds.
-
#to_h ⇒ Hash{String => String}
Converts the signature to HTTP header format.
Constructor Details
#initialize(metadata, value, label, parameters = {}, parsed_items: nil, headers: nil) ⇒ Signature
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Use build or from_components to create Signature instances.
30 31 32 33 34 35 36 37 38 |
# File 'lib/linzer/signature.rb', line 30 def initialize(, value, label, parameters = {}, parsed_items: nil, headers: nil) @metadata = .clone.freeze @value = value.clone.freeze @parameters = parameters.clone.freeze @label = label.clone.freeze @parsed_items = parsed_items&.freeze @headers = headers&.freeze freeze end |
Instance Attribute Details
#label ⇒ Object (readonly)
Returns the value of attribute label.
56 57 58 |
# File 'lib/linzer/signature.rb', line 56 def label @label end |
#metadata ⇒ Object (readonly) Also known as: serialized_components
Returns the value of attribute metadata.
43 44 45 |
# File 'lib/linzer/signature.rb', line 43 def @metadata end |
#parameters ⇒ Object (readonly)
Returns the value of attribute parameters.
52 53 54 |
# File 'lib/linzer/signature.rb', line 52 def parameters @parameters end |
#value ⇒ Object (readonly) Also known as: bytes
Returns the value of attribute value.
47 48 49 |
# File 'lib/linzer/signature.rb', line 47 def value @value end |
Class Method Details
.build(headers, options = {}) ⇒ Signature
Builds a Signature from HTTP headers.
Parses the ‘signature` and `signature-input` headers according to RFC 9421 and RFC 8941 (Structured Field Values).
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/linzer/signature.rb', line 234 def build(headers, = {}) basic_validate headers headers.transform_keys!(&:downcase) validate headers input = parse_structured_field(headers, "signature-input") reject_multiple_signatures if input.size > 1 && [:label].nil? label = [:label] || input.keys.first raw_signature = extract_raw_signature(headers["signature"], label) fail_due_invalid_components unless input[label].value.respond_to?(:each) parsed_items = input[label].value components = serialize_parsed_items(parsed_items) parameters = input[label].parameters new(components, raw_signature, label, parameters, parsed_items: parsed_items) end |
.from_components(components:, raw_signature:, label:, parameters:, parsed_items:, headers:) ⇒ Signature
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates a Signature directly from its constituent parts.
This avoids the serialize-then-parse round-trip when the caller (e.g. Linzer::Signer.sign) already has all the data.
196 197 198 199 200 201 202 203 |
# File 'lib/linzer/signature.rb', line 196 def from_components(components:, raw_signature:, label:, parameters:, parsed_items:, headers:) # Signature stores parameters with string keys (as produced by Starry # parsing). Convert symbol keys from Signer to match. string_params = {} parameters.each { |k, v| string_params[k.to_s] = v } new(components, raw_signature, label, string_params, parsed_items: parsed_items, headers: headers) end |
Instance Method Details
#components ⇒ Array<String>
Returns the deserialized component identifiers.
Unlike #serialized_components, this returns the components in a more human-readable form.
75 76 77 |
# File 'lib/linzer/signature.rb', line 75 def components FieldId.deserialize_components(serialized_components) end |
#created ⇒ Integer?
Returns the signature creation timestamp.
98 99 100 101 102 103 |
# File 'lib/linzer/signature.rb', line 98 def created Integer(parameters["created"]) rescue return nil if parameters["created"].nil? raise Error.new "Signature has a non-integer `created` parameter" end |
#expired? ⇒ Boolean
Checks if the signature has expired based on the ‘expires` parameter.
If the ‘expires` parameter is not present, the signature is considered not expired (returns false). If the parameter is present but not a valid integer, an error is raised.
134 135 136 137 138 139 |
# File 'lib/linzer/signature.rb', line 134 def expired? return false if !parameters.key?("expires") Time.now.to_i >= Integer(parameters["expires"]) rescue ArgumentError, TypeError raise Error.new "Signature has a non-integer `expires` parameter" end |
#field_ids ⇒ Array<FastIdentifier, FieldId>
Builds FieldId objects for each covered component.
Uses parsed_items when available to create FastIdentifier objects that bypass Starry re-parsing. Falls back to constructing full FieldId objects from the serialized strings.
Returns a fresh array each time because some adapter methods may mutate item parameters during field lookup (e.g., deleting “req”).
89 90 91 |
# File 'lib/linzer/signature.rb', line 89 def field_ids build_field_ids end |
#older_than?(seconds) ⇒ Boolean
Checks if the signature is older than a given number of seconds.
This is useful for implementing replay attack protection by rejecting signatures that are too old.
116 117 118 119 |
# File 'lib/linzer/signature.rb', line 116 def older_than?(seconds) raise Error.new "Signature is missing the `created` parameter" if created.nil? (Time.now.to_i - created) > seconds end |
#to_h ⇒ Hash{String => String}
Converts the signature to HTTP header format.
Returns a hash suitable for setting as HTTP headers on a request or response. The hash contains ‘signature` and `signature-input` keys.
150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/linzer/signature.rb', line 150 def to_h return @headers if @headers items = @parsed_items || serialized_components.map { |c| Starry.parse_item(c) } { "signature" => Starry.serialize({label => value}), "signature-input" => Starry.serialize({ label => Starry::InnerList.new(items, parameters) }) } end |