Module: Linzer::Signer

Extended by:
Common
Defined in:
lib/linzer/signer.rb

Overview

Handles HTTP message signing according to RFC 9421.

This module provides the core signing functionality. It creates signatures by computing a signature base from the message components and signing it with the provided key.

Examples:

Direct usage (prefer Linzer.sign for convenience)

message = Linzer::Message.new(request)
components = %w[@method @path content-type]
signature = Linzer::Signer.sign(key, message, components)

See Also:

Constant Summary collapse

DEFAULT_LABEL =

Default label used for signatures when none is specified.

Returns:

  • (String)
"sig1"

Class Method Summary collapse

Methods included from Common

signature_base, signature_base_line, signature_params_line

Class Method Details

.default_labelString

Returns the default signature label.

Returns:

  • (String)

    The default label (“sig1”)



101
102
103
# File 'lib/linzer/signer.rb', line 101

def default_label
  DEFAULT_LABEL
end

.sign(key, message, components, options = {}) ⇒ Linzer::Signature

Signs an HTTP message.

Creates a signature by:

  1. Serializing the component identifiers

  2. Building the signature base from the message and parameters

  3. Signing the signature base with the key

  4. Returning a Signature object with the result

Examples:

Basic signing

signature = Linzer::Signer.sign(key, message, %w[@method @path])

With all options

signature = Linzer::Signer.sign(key, message, %w[@method @path date],
  created: Time.now.to_i,
  keyid: "my-key-2024",
  label: "request-sig",
  nonce: SecureRandom.hex(16),
  tag: "my-app"
)

Parameters:

  • key (Linzer::Key)

    The private key to sign with. Must respond to ‘#sign` and should contain private key material.

  • message (Linzer::Message)

    The HTTP message to sign

  • components (Array<String>)

    Component identifiers to include in the signature. Can be header names (e.g., ‘“content-type”`) or derived components (e.g., `“@method”`, `“@path”`). May include parameters (e.g., `“content-type”;bs` for binary-wrapped).

  • options (Hash) (defaults to: {})

    Additional signature parameters

Options Hash (options):

  • :created (Integer)

    Unix timestamp for signature creation. Defaults to the current UTC time.

  • :keyid (String)

    Key identifier. If not provided, uses the key’s ‘key_id` if available.

  • :label (String)

    The signature label (defaults to “sig1”). Multiple signatures on a message must have distinct labels.

  • :nonce (String)

    A unique nonce value to prevent replay

  • :tag (String)

    Application-specific tag

  • :expires (Integer)

    Unix timestamp when signature expires

  • :alg (String)

    Algorithm identifier (usually inferred from key)

Returns:

Raises:

  • (SigningError)

    If the message, key, or components are invalid

  • (SigningError)

    If required components are missing from the message

  • (SigningError)

    If components are duplicated

  • (SigningError)

    If ‘@signature-params` is included in components



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/linzer/signer.rb', line 70

def sign(key, message, components, options = {})
  serialized_components, field_ids =
    FieldId.serialize_components_with_field_ids(Array(components))
  validate key, message, serialized_components, field_ids: field_ids

  # Reuse the already-parsed items from field_ids
  parsed_items = field_ids.map(&:item)

  parameters = populate_parameters(key, options)
  signature_base = signature_base(message, serialized_components, parameters,
                                  field_ids: field_ids)

  raw_signature = key.sign(signature_base)
  label = options[:label] || DEFAULT_LABEL

  # Build the signature directly, bypassing the serialize -> parse round-trip
  headers = serialize(raw_signature, serialized_components, parameters, label)

  Linzer::Signature.from_components(
    components:    serialized_components,
    raw_signature: raw_signature,
    label:         label,
    parameters:    parameters,
    parsed_items:  parsed_items,
    headers:       headers
  )
end