AudioProxy.Signature (audio_proxy v0.4.0)

Copy Markdown View Source

URL signature generation and verification (API doc §1).

A signed URL carries its signature as the first path segment:

/{signature}/{options}/{source}

The signature is base64url(HMAC-SHA256(key, salt ‖ rest-of-path)), where rest-of-path is the exact byte sequence after the signature segment, leading / included. Output is unpadded base64url; verification accepts padded and unpadded input. Key and salt are the hex-decoded AP_KEY/AP_SALT values from AudioProxy.Config.

sign/3 is the reference signer — tests use it, and clients can mirror it. verify/2 is the request-path gate: constant-time comparison, and the literal insecure segment accepted only when AP_ALLOW_INSECURE is set.

Summary

Types

Verification failure reason. The only one callers ever get.

Functions

Signs rest_of_path with key and salt, returning unpadded base64url.

Verifies sig_segment against rest_of_path using the configured key/salt.

Types

error_reason()

@type error_reason() :: :invalid_signature

Verification failure reason. The only one callers ever get.

Functions

sign(rest_of_path, key, salt)

@spec sign(binary(), binary(), binary()) :: String.t()

Signs rest_of_path with key and salt, returning unpadded base64url.

rest_of_path is everything after the signature segment, leading / required — exactly what verify/2 expects back. A path without the leading slash can never verify (the request gate always prepends it), so it is rejected here with a FunctionClauseError instead of producing an unusable signature.

verify(sig_segment, rest_of_path)

@spec verify(binary(), binary()) :: :ok | {:error, error_reason()}

Verifies sig_segment against rest_of_path using the configured key/salt.

Returns :ok or {:error, :invalid_signature} — callers get no finer distinction, so a missing key, garbage base64url, and a wrong MAC are indistinguishable from the outside.

The literal insecure segment passes only when AP_ALLOW_INSECURE is enabled; otherwise it is just another invalid signature. Comparison runs on the decoded MAC bytes via Plug.Crypto.secure_compare/2 (constant-time).

Verification is strict about canonicality: a signature is 43 unpadded base64url characters or the same 43 with one trailing =, and the decoded bytes must re-encode to exactly the supplied characters. Over-padded input and the four final-character variants that decode to the same bytes are rejected — a signature is non-malleable, with exactly two accepted spellings. (Canonicalization of the path itself — percent-encoding, option order — belongs to the options parser, not this layer.)