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
Functions
@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- at/0; it is revalidated before export.format-:jwk,:pem,:der, or:raw.
Returns
{:ok, encoded}- for:raw, thematerialtuple; for:jwk, a map with the required members plus anyalg,use, andkey_opsrestrictions; for:der, a SubjectPublicKeyInfo binary; for:pem, aPUBLIC KEYPEM binary.{:error, %RequestSeal.Crypto.Error{}}- reasons this function can return::invalid_key(keyfails validation) and:unsupported_format(unknownformat; a key withalgorithm,use, oroperationsrestrictions exported to a format other than:jwk, which would drop them; or a PSS-only:rsa_psskey 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}}
@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, matchingformat::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 symmetrickreject.alg(printable ASCII, 1–64 bytes),use("sig"or"enc"), andkey_ops(at most eight distinct registered operations) are retained; an explicitnullrejects, and simultaneoususeandkey_opsrestrictions 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{}}- validatedmaterialin the:rawshape;algorithm,use, andoperationshold JWK metadata, otherwisenil.{:error, %RequestSeal.Crypto.Error{}}- reasons this function can return::unsupported_format(formatis 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}}
@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- at/0; it is revalidated first.
Returns
{:ok, thumbprint}- the unpadded Base64url SHA-256 digest (43 characters).{:error, %RequestSeal.Crypto.Error{}}- reason:invalid_keywhenkeyfails 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}}