X402.HTTPSignature.Key (X402 v0.9.0)

Copy Markdown View Source

Key material for RFC 9421 HTTP Message Signatures.

A key pairs an algorithm from the HTTP Signature Algorithms registry with its public material and, for signing, its private material:

algPublic materialPrivate material
"ed25519"32-byte public key32-byte seed
"ecdsa-p256-sha256"65-byte uncompressed point32-byte scalar
"rsa-pss-sha512"[e, n] big-endian binaries[e, n, d] binaries

new/1 accepts the material as raw bytes, PEM (PUBLIC KEY, PRIVATE KEY, EC PRIVATE KEY, RSA PRIVATE KEY), a JWK map, or the :public_key records those PEMs decode to; from_jwk/1 and to_jwk/1 convert to and from the JWK form the /.well-known/http-message-signatures-directory document uses. The key identifier defaults to the RFC 7638 JWK thumbprint (thumbprint/1), as draft-meunier-http-message-signatures-directory requires.

ECDSA signatures use the raw r || s form RFC 9421 §3.3.4 mandates, and RSA-PSS uses SHA-512 with a 64-byte salt (§3.3.1).

Summary

Types

An HTTP Signature Algorithms registry name supported here.

t()

Functions

Returns the supported algorithm names.

Builds a key from a JWK map (RFC 7517), including the private part when d is present.

Generates a fresh key pair for an algorithm.

Builds a key from its algorithm and material.

Signs data with the key's private material, returning the raw signature bytes RFC 9421 §3.3 defines for the algorithm.

Computes the RFC 7638 JWK thumbprint (SHA-256, Base64url) of a key.

Returns the public JWK for a key, with kid and the HTTP signature alg name as draft-meunier-http-message-signatures-directory §3 requires.

Verifies a raw signature over data with the key's public material.

Types

alg()

@type alg() :: String.t()

An HTTP Signature Algorithms registry name supported here.

error()

@type error() ::
  :invalid_key | {:unsupported_algorithm, term()} | :missing_private_key

t()

@type t() :: %X402.HTTPSignature.Key{
  alg: alg(),
  kid: String.t() | nil,
  private: term() | nil,
  public: term()
}

Functions

algorithms()

(since 0.9.0)
@spec algorithms() :: [alg()]

Returns the supported algorithm names.

Examples

iex> X402.HTTPSignature.Key.algorithms()
["ed25519", "ecdsa-p256-sha256", "rsa-pss-sha512"]

from_jwk(jwk)

(since 0.9.0)
@spec from_jwk(map()) :: {:ok, t()} | {:error, error()}

Builds a key from a JWK map (RFC 7517), including the private part when d is present.

The alg member may name the HTTP signature algorithm; when absent it is inferred from kty/crv. Any kid is kept.

Examples

iex> jwk = %{"kty" => "OKP", "crv" => "Ed25519", "kid" => "test-key-ed25519",
...>   "x" => "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"}
iex> {:ok, key} = X402.HTTPSignature.Key.from_jwk(jwk)
iex> {key.alg, key.kid, key.private}
{"ed25519", "test-key-ed25519", nil}

iex> X402.HTTPSignature.Key.from_jwk(%{"kty" => "oct", "k" => "secret"})
{:error, {:unsupported_algorithm, "oct"}}

generate(alg)

(since 0.9.0)
@spec generate(alg()) :: {:ok, t()} | {:error, error()}

Generates a fresh key pair for an algorithm.

Examples

iex> {:ok, key} = X402.HTTPSignature.Key.generate("ecdsa-p256-sha256")
iex> {byte_size(key.public), byte_size(key.private), String.length(key.kid)}
{65, 32, 43}

iex> X402.HTTPSignature.Key.generate("hmac-sha256")
{:error, {:unsupported_algorithm, "hmac-sha256"}}

new(opts)

(since 0.9.0)
@spec new(keyword()) :: {:ok, t()} | {:error, error()}

Builds a key from its algorithm and material.

At least one of :public_key and :private_key is required; the public material is derived from the private one when omitted.

Options

  • :alg - Required. The signature algorithm.

  • :kid (String.t/0) - Key identifier; defaults to the JWK thumbprint.

  • :public_key (term/0) - Public material (raw, PEM, JWK, or :public_key record).

  • :private_key (term/0) - Private material (raw, PEM, JWK, or :public_key record).

Examples

iex> seed = :binary.copy(<<7>>, 32)
iex> {:ok, key} = X402.HTTPSignature.Key.new(alg: "ed25519", private_key: seed, kid: "k1")
iex> {key.alg, key.kid, byte_size(key.public), key.private == seed}
{"ed25519", "k1", 32, true}

iex> X402.HTTPSignature.Key.new(alg: "ed25519", public_key: "too short")
{:error, :invalid_key}

iex> X402.HTTPSignature.Key.new(alg: "hmac-sha256", public_key: "x")
{:error, {:unsupported_algorithm, "hmac-sha256"}}

sign(key, data)

(since 0.9.0)
@spec sign(t(), binary()) :: {:ok, binary()} | {:error, :missing_private_key}

Signs data with the key's private material, returning the raw signature bytes RFC 9421 §3.3 defines for the algorithm.

Examples

iex> {:ok, key} = X402.HTTPSignature.Key.generate("ed25519")
iex> {:ok, signature} = X402.HTTPSignature.Key.sign(key, "data")
iex> byte_size(signature)
64

iex> {:ok, key} = X402.HTTPSignature.Key.generate("ed25519")
iex> X402.HTTPSignature.Key.sign(%{key | private: nil}, "data")
{:error, :missing_private_key}

thumbprint(key)

(since 0.9.0)
@spec thumbprint(t()) :: String.t()

Computes the RFC 7638 JWK thumbprint (SHA-256, Base64url) of a key.

Examples

iex> jwk = %{"kty" => "OKP", "crv" => "Ed25519", "x" => "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"}
iex> {:ok, key} = X402.HTTPSignature.Key.from_jwk(jwk)
iex> X402.HTTPSignature.Key.thumbprint(key)
"poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U"

to_jwk(key)

(since 0.9.0)
@spec to_jwk(t()) :: map()

Returns the public JWK for a key, with kid and the HTTP signature alg name as draft-meunier-http-message-signatures-directory §3 requires.

Examples

iex> jwk = %{"kty" => "OKP", "crv" => "Ed25519", "x" => "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs"}
iex> {:ok, key} = X402.HTTPSignature.Key.from_jwk(jwk)
iex> X402.HTTPSignature.Key.to_jwk(key)
%{
  "kty" => "OKP",
  "crv" => "Ed25519",
  "x" => "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs",
  "kid" => "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U",
  "alg" => "ed25519",
  "use" => "sig"
}

verify(key, data, signature)

(since 0.9.0)
@spec verify(t(), binary(), binary()) :: boolean()

Verifies a raw signature over data with the key's public material.

Examples

iex> {:ok, key} = X402.HTTPSignature.Key.generate("ecdsa-p256-sha256")
iex> {:ok, signature} = X402.HTTPSignature.Key.sign(key, "data")
iex> X402.HTTPSignature.Key.verify(key, "data", signature)
true
iex> X402.HTTPSignature.Key.verify(key, "other", signature)
false
iex> X402.HTTPSignature.Key.verify(key, "data", "short")
false