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