RequestSeal.PublicKey (RequestSeal v0.5.2)

Copy Markdown View Source

Public-only RSA, P-256, P-384, and Ed25519 key formats and algorithm binding.

Formats follow RFC 7517, RFC 7518, RFC 8037, RFC 4055, RFC 5480, and RFC 8410.

import/2 and export/2 move keys between public JWK maps, PEM, DER SubjectPublicKeyInfo, and explicit components; their documentation lists the accepted shapes. No secret or key ID is retained.

JWK alg, use, and key_ops are retained and checked on every verification. JWK alg uses exact JOSE identifiers even for an HTTP algorithm. Absent metadata imposes no additional restriction; it does not establish trust. RSA-PSS SubjectPublicKeyInfo with absent parameters retains its PSS-only type, which binds only PS256, PS384, and PS512.

RSA moduli are odd, 2048–8192 bits; public exponents are odd, 3–2^32-1 and smaller than the modulus. EC points must have the exact uncompressed width and pass OTP point validation. Ed25519 keys are 32 bytes with canonical encoded y and reject all eight small-order points with :invalid_key in every format. The check ignores the encoded x sign, following the libsodium small-order blocklist (ge25519_has_small_order in ed25519_ref10.c at tag 1.0.18). Supplied structs are revalidated at each use.

Summary

Functions

Export a validated public key without dropping algorithm or operation restrictions.

Import bounded, explicit public key material, preserving JWK restrictions.

Compute the RFC 7638 / RFC 8037 SHA-256 thumbprint of required public members.

Types

t()

@type t() :: %RequestSeal.PublicKey{
  algorithm: binary() | nil,
  material: tuple(),
  operations: [binary()] | nil,
  use: binary() | nil
}

Functions

export(key, format)

@spec export(t(), :jwk | :pem | :der | :raw) ::
  {:ok, term()} | {:error, RequestSeal.Crypto.Error.t()}

Export a validated public key without dropping algorithm or operation restrictions.

Parameters

  • key - a t/0; it is revalidated before export.
  • format - :jwk, :pem, :der, or :raw.

Returns

  • {:ok, encoded} - for :raw, the material tuple; for :jwk, a map with the required members plus any alg, use, and key_ops restrictions; for :der, a SubjectPublicKeyInfo binary; for :pem, a PUBLIC KEY PEM binary.
  • {:error, %RequestSeal.Crypto.Error{}} - reasons this function can return: :invalid_key (key fails validation) and :unsupported_format (unknown format; a key with algorithm, use, or operations restrictions exported to a format other than :jwk, which would drop them; or a PSS-only :rsa_pss key exported to :jwk, which has no equivalent key-type restriction). Meanings: RequestSeal.Crypto.Error.

Examples

iex> jwk = %{"kty" => "OKP", "crv" => "Ed25519", "x" => "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "alg" => "EdDSA"}
iex> {:ok, key} = RequestSeal.PublicKey.import(jwk, :jwk)
iex> RequestSeal.PublicKey.export(key, :jwk) == {:ok, jwk}
true
iex> RequestSeal.PublicKey.export(key, :pem)
{:error, %RequestSeal.Crypto.Error{reason: :unsupported_format}}
iex> {:ok, "-----BEGIN PUBLIC KEY-----" <> _} = RequestSeal.PublicKey.export(%{key | algorithm: nil}, :pem)
iex> RequestSeal.PublicKey.export(%{key | material: {:ed25519, "short"}}, :raw)
{:error, %RequestSeal.Crypto.Error{reason: :invalid_key}}

import(value, format)

@spec import(term(), :jwk | :pem | :der | :raw) ::
  {:ok, t()} | {:error, RequestSeal.Crypto.Error.t()}

Import bounded, explicit public key material, preserving JWK restrictions.

Every format applies the validation rules in the module documentation.

Parameters

  • value - the encoded public key, matching format:
    • :raw - {:rsa, n, e}, {:rsa_pss, n, e}, {:ec, "P-256" | "P-384", uncompressed_point}, or {:ed25519, public_bytes}.
    • :jwk - a public JWK map of at most 32 entries: "RSA" ("n", "e"), "EC" ("crv", "x", "y"), or "OKP" Ed25519 ("x"). Components are canonical unpadded Base64url of at most 1,366 characters; RSA integers have no leading zero byte. Private members (d, p, q, dp, dq, qi, oth) and symmetric k reject. alg (printable ASCII, 1–64 bytes), use ("sig" or "enc"), and key_ops (at most eight distinct registered operations) are retained; an explicit null rejects, and simultaneous use and key_ops restrictions must agree.
    • :pem - a binary of 1–16,384 bytes holding exactly one unencrypted SubjectPublicKeyInfo or PKCS #1 RSA public key entry. Private material rejects.
    • :der - a canonical SubjectPublicKeyInfo binary of 1–16,384 bytes. RSA-PSS with absent parameters imports as :rsa_pss; parameter-constrained PSS keys reject instead of discarding constraints.
  • format - :jwk, :pem, :der, or :raw.

Returns

  • {:ok, %RequestSeal.PublicKey{}} - validated material in the :raw shape; algorithm, use, and operations hold JWK metadata, otherwise nil.
  • {:error, %RequestSeal.Crypto.Error{}} - reasons this function can return: :unsupported_format (format is not one of the four) and :invalid_key (any malformed, oversized, noncanonical, private, small-order, or otherwise invalid input). Meanings: RequestSeal.Crypto.Error.

Examples

The Ed25519 public key from RFC 8037 Appendix A.2:

iex> jwk = %{"kty" => "OKP", "crv" => "Ed25519", "x" => "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}
iex> {:ok, %RequestSeal.PublicKey{material: {:ed25519, public}, algorithm: nil}} = RequestSeal.PublicKey.import(jwk, :jwk)
iex> byte_size(public)
32
iex> {:ok, key} = RequestSeal.PublicKey.import(Map.put(jwk, "key_ops", ["verify"]), :jwk)
iex> key.operations
["verify"]
iex> RequestSeal.PublicKey.import(Map.put(jwk, "d", "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"), :jwk)
{:error, %RequestSeal.Crypto.Error{reason: :invalid_key}}
iex> RequestSeal.PublicKey.import({:ed25519, <<1>> <> :binary.copy(<<0>>, 31)}, :raw)
{:error, %RequestSeal.Crypto.Error{reason: :invalid_key}}
iex> RequestSeal.PublicKey.import(jwk, :ssh)
{:error, %RequestSeal.Crypto.Error{reason: :unsupported_format}}

thumbprint(key)

@spec thumbprint(t()) :: {:ok, binary()} | {:error, RequestSeal.Crypto.Error.t()}

Compute the RFC 7638 / RFC 8037 SHA-256 thumbprint of required public members.

Only the required members are hashed (e, kty, n for RSA, including PSS-only keys; crv, kty, x for Ed25519; crv, kty, x, y for EC); alg, use, and key_ops metadata is not thumbprint material.

Parameters

  • key - a t/0; it is revalidated first.

Returns

  • {:ok, thumbprint} - the unpadded Base64url SHA-256 digest (43 characters).
  • {:error, %RequestSeal.Crypto.Error{}} - reason :invalid_key when key fails validation. Meanings: RequestSeal.Crypto.Error.

Examples

RFC 8037 Appendix A.3:

iex> jwk = %{"kty" => "OKP", "crv" => "Ed25519", "x" => "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}
iex> {:ok, key} = RequestSeal.PublicKey.import(jwk, :jwk)
iex> RequestSeal.PublicKey.thumbprint(key)
{:ok, "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k"}
iex> RequestSeal.PublicKey.thumbprint(%{key | use: "encrypt"})
{:error, %RequestSeal.Crypto.Error{reason: :invalid_key}}