Class: Linzer::Signature
- Inherits:
-
Object
- Object
- Linzer::Signature
- Defined in:
- lib/linzer/signature.rb,
lib/linzer/signature/context.rb,
lib/linzer/signature/profile.rb,
lib/linzer/signature/profile/base.rb,
lib/linzer/signature/profile/example.rb,
lib/linzer/signature/profile/web_bot_auth.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.
Defined Under Namespace
Modules: Profile Classes: Context
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 253 254 255 256 257 |
# File 'lib/linzer/signature.rb', line 234 def build(headers, = {}) basic_validate headers headers.transform_keys!(&:downcase) headers.transform_values! { |v| v.encode(Encoding::ASCII) } validate headers input = HTTP::StructuredField.parse_dictionary( headers["signature-input"], field_name: "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| HTTP::StructuredField.parse_item(c) } { "signature" => HTTP::StructuredField.serialize({label => value}), "signature-input" => HTTP::StructuredField.serialize({ label => HTTP::StructuredField::InnerList.new(items, parameters) }) } end |