import bigi.{type BigInt} import gleam/bit_array import gleam/dynamic/decode.{type Decoder} import gleam/json.{type Json} import gleam/order import gleam/time/timestamp import ywt/internal/base64url pub type Error { MalformedToken InvalidHeaderEncoding InvalidPayloadEncoding InvalidSignatureEncoding InvalidHeaderJson(json.DecodeError) HeaderDecodingError(List(decode.DecodeError)) UnsupportedCriticalHeader UnsupportedUnencodedPayload InvalidPayloadJson(json.DecodeError) NoMatchingKey InvalidSignature TokenExpired(expired_at: timestamp.Timestamp) TokenNotYetValid(not_before: timestamp.Timestamp) InvalidIssuer(expected: List(String), actual: String) InvalidAudience(expected: List(String), actual: List(String)) InvalidType(expected: String, actual: String) InvalidSubject(expected: List(String), actual: String) InvalidId(expected: List(String), actual: String) MissingClaim(claim_name: String) ClaimDecodingError(claim_name: String, error: List(decode.DecodeError)) InvalidCustomClaim(claim_name: String) PayloadDecodingError(List(decode.DecodeError)) } // -- INTEGERS ----------------------------------------------------------------- pub fn int_decoder() -> Decoder(BigInt) { use bits <- decode.then(bits_decoder()) case bigi.from_bytes(bits, bigi.BigEndian, bigi.Unsigned) { Ok(int) -> decode.success(int) Error(_) -> decode.failure(bigi.zero(), "BigInt") } } pub fn bits_to_json(bits: BitArray) -> Json { json.string(bit_array.base64_url_encode(bits, False)) } pub fn int_to_json(int: BigInt) -> Json { let size = int_byte_size(int, 0) let assert Ok(bytes) = bigi.to_bytes(int, bigi.BigEndian, bigi.Unsigned, size) json.string(bit_array.base64_url_encode(bytes, False)) } fn int_byte_size(int: BigInt, bytes: Int) -> Int { case bigi.compare(int, bigi.zero()) { order.Gt -> int_byte_size(bigi.bitwise_shift_right(int, 8), bytes + 1) _ -> bytes } } pub fn rsa_modulus_is_large_enough(modulus: BigInt) -> Bool { let minimum_bits = 2048 let minimum_modulus = bigi.bitwise_shift_left(bigi.one(), by: minimum_bits - 1) case bigi.compare(modulus, minimum_modulus) { order.Lt -> False order.Eq | order.Gt -> True } } pub fn bits_decoder() { use str <- decode.then(decode.string) case base64url.decode(str) { Ok(bits) -> decode.success(bits) Error(_) -> decode.failure(<<>>, "Base64Url") } } pub fn bits_of_length_decoder(byte_count: Int) -> Decoder(BitArray) { use bits <- decode.then(bits_decoder()) case bit_array.bit_size(bits) == byte_count * 8 { True -> decode.success(bits) False -> decode.failure(<<>>, "Base64UrlLength") } } // -- INTERNAL KEY TYPES ------------------------------------------------------- /// The names the Erlang public_key application uses for Ecdsa curves. pub type NamedCurve { Secp256r1 Secp384r1 Secp521r1 } pub fn curve_decoder() -> Decoder(NamedCurve) { // The "crv" (curve) parameter identifies the cryptographic curve used // with the key. Curve values from [DSS] used by this specification // are: // o "P-256" // o "P-384" // o "P-521" use crv <- decode.then(decode.string) case crv { "P-256" -> decode.success(Secp256r1) "P-384" -> decode.success(Secp384r1) "P-521" -> decode.success(Secp521r1) _ -> decode.failure(Secp256r1, "crv") } } pub fn named_curve(curve: NamedCurve) -> String { case curve { Secp256r1 -> "P-256" Secp384r1 -> "P-384" Secp521r1 -> "P-521" } } pub fn named_curve_size(curve: NamedCurve) -> Int { case curve { Secp256r1 -> 32 Secp384r1 -> 48 Secp521r1 -> 66 } } /// The RSA Padding pub type Padding { RsaPkcs1Padding RsaPkcs1PssPadding } pub fn rsa_padding(padding: Padding) { case padding { RsaPkcs1Padding -> "RSASSA-PKCS1-v1_5" RsaPkcs1PssPadding -> "RSA-PSS" } } /// We use our own type here to not add a dependency to gleam_crypto pub type DigestType { Sha256 Sha384 Sha512 } pub fn digest_type(digest_type: DigestType) { case digest_type { Sha256 -> "SHA-256" Sha384 -> "SHA-384" Sha512 -> "SHA-512" } } pub fn digest_size(digest_type: DigestType) -> Int { case digest_type { Sha256 -> 32 Sha384 -> 48 Sha512 -> 64 } }