-module(ywt@verify_key). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/ywt/verify_key.gleam"). -export([derived/1, id/1, match/4, algorithm/1, decoder/0, set_decoder/0, to_jwk/1, to_jwks/1]). -export_type([verify_key/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -opaque verify_key() :: {verify_ecdsa, gleam@option:option(binary()), ywt@internal@core:named_curve(), ywt@internal@core:digest_type(), bitstring()} | {verify_rsa, gleam@option:option(binary()), ywt@internal@core:digest_type(), bigi:big_int(), bigi:big_int(), ywt@internal@core:padding()} | {verify_hmac, gleam@option:option(binary()), ywt@internal@core:digest_type(), bitstring()}. -file("src/ywt/verify_key.gleam", 58). ?DOC( " Construct a verification key for token validation from a signing key.\n" "\n" " ## Security Considerations\n" " - For asymmetric keys (RSA/ECDSA), this extracts only the public key\n" " - For symmetric keys (HMAC), this returns the same secret (use with caution)\n" " - Distribute verify keys to services that only need to check token validity\n" " - Never give signing keys to services that only need verification\n" "\n" " ## Example\n" " ```gleam\n" " import gleam/crypto\n" "\n" " let secret = crypto.strong_random_bytes(32)\n" " // Create a signing key\n" " let sign_key = sign_key.hs256(secret)\n" "\n" " // Extract verification key for a service\n" " let verify_key = verify_key.derived(sign_key)\n" "\n" " // This token can be used to verify but not create tokens\n" " ywt.decode(jwt_token, using: payload_decoder, claims: [], keys: [verify_key])\n" " ```\n" "\n" " 💡 **Best Practice:** Only your auth service should have signing keys.\n" " All other services should only have verification keys.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec derived(ywt@sign_key:sign_key()) -> verify_key(). derived(Sign_key) -> ywt@sign_key:match( Sign_key, fun(Id, Curve, Digest_type, Public_key, _) -> {verify_ecdsa, Id, Curve, Digest_type, Public_key} end, fun(Id@1, Digest, E, N, _, Padding) -> {verify_rsa, Id@1, Digest, E, N, Padding} end, fun(Id@2, Digest@1, E@1, N@1, _, _, _, _, _, _, _, Padding@1) -> {verify_rsa, Id@2, Digest@1, E@1, N@1, Padding@1} end, fun(Id@3, Digest_type@1, Secret) -> {verify_hmac, Id@3, Digest_type@1, Secret} end ). -file("src/ywt/verify_key.gleam", 163). -spec ec_decoder(gleam@option:option(binary())) -> gleam@dynamic@decode:decoder(verify_key()). ec_decoder(Id) -> gleam@dynamic@decode:field( <<"crv"/utf8>>, ywt@internal@core:curve_decoder(), fun(Crv) -> gleam@dynamic@decode:field( <<"x"/utf8>>, ywt@internal@core:bits_decoder(), fun(X) -> gleam@dynamic@decode:field( <<"y"/utf8>>, ywt@internal@core:bits_decoder(), fun(Y) -> Public_key = <<16#4, X/bitstring, Y/bitstring>>, Digest_type = case Crv of secp256r1 -> sha256; secp384r1 -> sha384; secp521r1 -> sha512 end, gleam@dynamic@decode:success( {verify_ecdsa, Id, Crv, Digest_type, Public_key} ) end ) end ) end ). -file("src/ywt/verify_key.gleam", 343). ?DOC( " Extracts the key identifier from a verification key, if present.\n" "\n" " This function retrieves the optional key identifier that was assigned to a key.\n" " Key identifiers are used for key selection during JWT verification.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec id(verify_key()) -> {ok, binary()} | {error, nil}. id(Key) -> gleam@option:to_result(erlang:element(2, Key), nil). -file("src/ywt/verify_key.gleam", 350). ?DOC(false). -spec match( verify_key(), fun((gleam@option:option(binary()), ywt@internal@core:named_curve(), ywt@internal@core:digest_type(), bitstring()) -> EYR), fun((gleam@option:option(binary()), ywt@internal@core:digest_type(), bigi:big_int(), bigi:big_int(), ywt@internal@core:padding()) -> EYR), fun((gleam@option:option(binary()), ywt@internal@core:digest_type(), bitstring()) -> EYR) ) -> EYR. match(Verify_key, On_ecdsa, On_rsa, On_hmac) -> case Verify_key of {verify_ecdsa, Id, Curve, Digest_type, Public_key} -> On_ecdsa(Id, Curve, Digest_type, Public_key); {verify_hmac, Id@1, Digest_type@1, Secret} -> On_hmac(Id@1, Digest_type@1, Secret); {verify_rsa, Id@2, Digest_type@2, Exponent, Modulus, Padding} -> On_rsa(Id@2, Digest_type@2, Exponent, Modulus, Padding) end. -file("src/ywt/verify_key.gleam", 361). ?DOC(false). -spec algorithm(verify_key()) -> ywt@algorithm:algorithm(). algorithm(Key) -> case Key of {verify_ecdsa, _, _, sha256, _} -> es256; {verify_ecdsa, _, _, sha384, _} -> es384; {verify_ecdsa, _, _, sha512, _} -> es512; {verify_hmac, _, sha256, _} -> hs256; {verify_hmac, _, sha384, _} -> hs384; {verify_hmac, _, sha512, _} -> hs512; {verify_rsa, _, sha256, _, _, rsa_pkcs1_padding} -> rs256; {verify_rsa, _, sha384, _, _, rsa_pkcs1_padding} -> rs384; {verify_rsa, _, sha512, _, _, rsa_pkcs1_padding} -> rs512; {verify_rsa, _, sha256, _, _, rsa_pkcs1_pss_padding} -> ps256; {verify_rsa, _, sha384, _, _, rsa_pkcs1_pss_padding} -> ps384; {verify_rsa, _, sha512, _, _, rsa_pkcs1_pss_padding} -> ps512 end. -file("src/ywt/verify_key.gleam", 196). -spec rsa_decoder(gleam@option:option(binary())) -> gleam@dynamic@decode:decoder(verify_key()). rsa_decoder(Id) -> gleam@dynamic@decode:field( <<"alg"/utf8>>, ywt@algorithm:decoder(), fun(Alg) -> gleam@dynamic@decode:field( <<"e"/utf8>>, ywt@internal@core:int_decoder(), fun(Exponent) -> gleam@dynamic@decode:field( <<"n"/utf8>>, ywt@internal@core:int_decoder(), fun(Modulus) -> Digest_type = ywt@algorithm:digest_type(Alg), gleam@dynamic@decode:then( case ywt@algorithm:padding(Alg) of {ok, Padding} -> gleam@dynamic@decode:success(Padding); {error, _} -> gleam@dynamic@decode:failure( rsa_pkcs1_padding, <<"alg"/utf8>> ) end, fun(Padding@1) -> gleam@dynamic@decode:success( {verify_rsa, Id, Digest_type, Exponent, Modulus, Padding@1} ) end ) end ) end ) end ). -file("src/ywt/verify_key.gleam", 114). ?DOC( " Decodes a JSON Web Key (JWK) containing a public key into a verification key.\n" "\n" " Use this when receiving public keys from external sources, key management services,\n" " or when implementing key rotation with public key distribution.\n" "\n" " This function only supports RSA or Elliptic Curve keys.\n" "\n" " ## Security Considerations\n" " - Only accept keys from trusted sources\n" " - Only accepts asymmetric public keys (EC, RSA) - never processes private key material\n" " - Does not implement key pinning or certificate validation\n" "\n" " ## Example\n" " ```gleam\n" " import gleam/json\n" "\n" " // Receive a public key from your identity provider\n" " let jwk_json = \"{\n" " \\\"kty\\\": \\\"EC\\\",\n" " \\\"crv\\\": \\\"P-256\\\",\n" " \\\"alg\\\": \\\"ES256\\\",\n" " \\\"x\\\": \\\"...\\\",\n" " \\\"y\\\": \\\"...\\\"\n" " }\"\n" "\n" " case json.parse(jwk_json, verify_key.decoder()) {\n" " Ok(verify_key) -> {\n" " // Use this key to verify tokens from the identity provider\n" " ywt.parse(token, using: decoder, claims: [], keys: [verify_key])\n" " }\n" " Error(_) -> // Handle invalid key format\n" " }\n" " ```\n" "\n" " 📝 **Note:** Always validate that keys come from trusted sources and consider\n" " implementing key rotation mechanisms.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec decoder() -> gleam@dynamic@decode:decoder(verify_key()). decoder() -> gleam@dynamic@decode:optional_field( <<"kid"/utf8>>, none, gleam@dynamic@decode:map( {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Field@0) -> {some, Field@0} end ), fun(Id) -> gleam@dynamic@decode:field( <<"kty"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Kty) -> case Kty of <<"EC"/utf8>> -> ec_decoder(Id); <<"RSA"/utf8>> -> rsa_decoder(Id); _ -> gleam@dynamic@decode:failure( {verify_hmac, Id, sha256, <<>>}, <<"kty"/utf8>> ) end end ) end ). -file("src/ywt/verify_key.gleam", 159). ?DOC( " Decodes a JSON Web Key Set (JWKS) into a list of verification keys.\n" "\n" " Use this when receiving public keys from external sources, key management services,\n" " or when implementing key rotation with public key distribution.\n" "\n" " ## Security Considerations\n" " - Only accept keys from trusted sources\n" " - Only accepts asymmetric public keys (EC, RSA) - never processes private key material\n" " - Does not implement key pinning or certificate validation\n" "\n" " ## Example\n" " ```gleam\n" " import gleam/json\n" "\n" " // Receive a public key from your identity provider\n" " let jwk_json = \"{\\\"keys\\\": [{\\\"kty\\\": \\\"EC\\\", ...}]}\"\n" "\n" " case json.parse(jwk_json, verify_key.set_decoder()) {\n" " Ok(verify_keys) -> {\n" " ywt.parse(token, using: decoder, claims: [], keys: verify_keys)\n" " }\n" " Error(_) -> // Handle invalid key format\n" " }\n" " ```\n" "\n" " 📝 **Note:** Always validate that keys come from trusted sources and consider\n" " implementing key rotation mechanisms.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec set_decoder() -> gleam@dynamic@decode:decoder(list(verify_key())). set_decoder() -> gleam@dynamic@decode:at( [<<"keys"/utf8>>], gleam@dynamic@decode:list(decoder()) ). -file("src/ywt/verify_key.gleam", 250). ?DOC( " Encodes a verification key into a JSON Web Key (JWK).\n" "\n" " This function serializes a `VerifyKey` into the standard JWK format defined in RFC 7517,\n" " making it suitable for key distribution, storage, or exchange with other JWT libraries\n" " and services.\n" "\n" " ## Security Considerations\n" " - Only converts verification keys (public keys and HMAC secrets for verification)\n" " - HMAC keys will include the secret in the JWK - distribute carefully\n" " - For asymmetric keys, only public information is included (safe to distribute)\n" " - Include key IDs (`kid`) to enable proper key rotation and selection\n" " - Consider key expiration and rotation policies when distributing JWKs\n" "\n" " ## Example\n" " ```gleam\n" " // Convert an ECDSA public key to JWK\n" " let verify_key = verify_key.derived(ecdsa_sign_key)\n" " let jwk = verify_key.to_jwk(verify_key)\n" "\n" " // Serve this JWK for other services to verify your tokens\n" " json.to_string(jwk)\n" " // Returns: {\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"...\",\"y\":\"...\",\"alg\":\"ES256\",\"kid\":\"key1\"}\n" " ```\n" "\n" " ⚠️ **Warning:** For HMAC keys, the resulting JWK contains the shared secret.\n" " Only distribute HMAC JWKs to trusted services that need to verify tokens.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec to_jwk(verify_key()) -> gleam@json:json(). to_jwk(Key) -> Fields = case Key of {verify_hmac, _, _, _} -> [{<<"kty"/utf8>>, gleam@json:string(<<"oct"/utf8>>)}, {<<"k"/utf8>>, ywt@internal@core:bits_to_json(erlang:element(4, Key))}]; {verify_ecdsa, _, _, _, _} -> Coord_size = ywt@internal@core:named_curve_size( erlang:element(3, Key) ), {X@1, Y@1} = case erlang:element(5, Key) of <<16#4, X:Coord_size/binary, Y:Coord_size/binary>> -> {X, Y}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"ywt/verify_key"/utf8>>, function => <<"to_jwk"/utf8>>, line => 259, value => _assert_fail, start => 9199, 'end' => 9294, pattern_start => 9210, pattern_end => 9269}) end, [{<<"kty"/utf8>>, gleam@json:string(<<"EC"/utf8>>)}, {<<"crv"/utf8>>, gleam@json:string( ywt@internal@core:named_curve(erlang:element(3, Key)) )}, {<<"x"/utf8>>, ywt@internal@core:bits_to_json(X@1)}, {<<"y"/utf8>>, ywt@internal@core:bits_to_json(Y@1)}]; {verify_rsa, _, _, _, _, _} -> [{<<"kty"/utf8>>, gleam@json:string(<<"RSA"/utf8>>)}, {<<"e"/utf8>>, ywt@internal@core:int_to_json(erlang:element(4, Key))}, {<<"n"/utf8>>, ywt@internal@core:int_to_json(erlang:element(5, Key))}] end, Fields@1 = [{<<"alg"/utf8>>, ywt@algorithm:to_json(algorithm(Key))} | Fields], Fields@2 = case erlang:element(2, Key) of {some, Id} -> [{<<"kid"/utf8>>, gleam@json:string(Id)} | Fields@1]; none -> Fields@1 end, gleam@json:object(Fields@2). -file("src/ywt/verify_key.gleam", 329). ?DOC( " Converts a list of verification keys to a JSON Web Key Set (JWKS) format.\n" "\n" " This creates a standard JWKS structure containing multiple keys, which is the\n" " standard way to publish multiple verification keys for key rotation, different\n" " algorithms, or multi-tenant scenarios.\n" "\n" " ## Usage\n" " Use this to:\n" " - Implement a JWKS endpoint (`/.well-known/jwks.json`)\n" " - Support key rotation with multiple active keys\n" " - Provide keys for different algorithms or use cases\n" " - Enable external services to verify your JWTs\n" "\n" " ## Security Considerations\n" " - JWKS endpoints are typically public - ensure only verification keys are included\n" " - Implement proper caching headers for JWKS endpoints (but allow for key rotation)\n" " - For HMAC keys, be extremely careful about distribution\n" " - Consider rate limiting your JWKS endpoint to prevent abuse\n" " - Include appropriate CORS headers if serving to web applications\n" "\n" " ## Example\n" " ```gleam\n" " // Create a JWKS with multiple keys for rotation\n" " let current_key = verify_key.derived(current_signing_key)\n" " let previous_key = verify_key.derived(previous_signing_key)\n" " let backup_key = verify_key.derived(backup_signing_key)\n" "\n" " let jwks = verify_key.to_jwks([current_key, previous_key, backup_key])\n" "\n" " // Serve this at /.well-known/jwks.json\n" " json.to_string(jwks)\n" " // Returns: {\"keys\":[{\"kty\":\"EC\",...},{\"kty\":\"RSA\",...},{\"kty\":\"EC\",...}]}\n" " ```\n" "\n" " 💡 **Best Practice:** Include multiple keys during rotation periods to ensure\n" " tokens signed with old keys remain valid during the transition period.\n" "\n" "
\n" " \n" " Back to top ↑\n" " \n" "
\n" ). -spec to_jwks(list(verify_key())) -> gleam@json:json(). to_jwks(Keys) -> gleam@json:object([{<<"keys"/utf8>>, gleam@json:array(Keys, fun to_jwk/1)}]).