diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ba74f3..45f4a97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: test-erlang: name: Erlang (OTP ${{ matrix.otp-version }}, Gleam ${{ matrix.gleam-version }}) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -43,7 +43,7 @@ jobs: test-javascript: name: JavaScript (Node ${{ matrix.node-version }}, Gleam ${{ matrix.gleam-version }}) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: diff --git a/README.md b/README.md index 4171341..5540f3b 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,10 @@ gleam add kryptos | [eddsa](https://hexdocs.pm/kryptos/kryptos/eddsa.html) | Ed25519, Ed448 | | [hash](https://hexdocs.pm/kryptos/kryptos/hash.html) | SHA-1, SHA-2, SHA-3, SHAKE, BLAKE2 | | [hmac](https://hexdocs.pm/kryptos/kryptos/hmac.html) | All hash algorithms | +| [mldsa](https://hexdocs.pm/kryptos/kryptos/mldsa.html) | ML-DSA-44, ML-DSA-65, ML-DSA-87 | | [rsa](https://hexdocs.pm/kryptos/kryptos/rsa.html) | OAEP, PKCS#1 v1.5, PSS, PKCS#1 v1.5 signatures | | [x509/certificate](https://hexdocs.pm/kryptos/kryptos/x509/certificate.html) | Certificate parsing and self-signed generation | -| [x509/csr](https://hexdocs.pm/kryptos/kryptos/x509/csr.html) | CSR generation with ECDSA/RSA/EdDSA, SANs | +| [x509/csr](https://hexdocs.pm/kryptos/kryptos/x509/csr.html) | CSR generation with ECDSA/RSA/EdDSA/ML-DSA, SANs | | [xdh](https://hexdocs.pm/kryptos/kryptos/xdh.html) | X25519, X448 | ## Getting Started diff --git a/src/kryptos/internal/mldsa.gleam b/src/kryptos/internal/mldsa.gleam new file mode 100644 index 0000000..6acd686 --- /dev/null +++ b/src/kryptos/internal/mldsa.gleam @@ -0,0 +1,688 @@ +//// Internal ML-DSA (FIPS 204) constants, types, and polynomial arithmetic. +//// +//// Erlang's `crypto:generate_key/2` and Node's `crypto.generateKeyPairSync` +//// do not accept a 32-byte seed; importing a seed-form PKCS#8 key therefore +//// requires deriving the public key from the seed. The JavaScript target +//// handles this natively, so the code here is only reachable on Erlang. + +import gleam/bit_array +import gleam/bool +import gleam/bytes_tree +import gleam/dict.{type Dict} +import gleam/int +import gleam/list +import gleam/pair +import gleam/result +import kryptos/hash +import kryptos/mldsa + +const q = 8_380_417 + +const n = 256 + +const inv_ntt_scale = 8_347_681 + +type Eta { + Eta2 + Eta4 +} + +type Config { + Config(k: Int, l: Int, eta: Eta) +} + +fn param_config(param: mldsa.ParameterSet) -> Config { + case param { + mldsa.Mldsa44 -> Config(k: 4, l: 4, eta: Eta2) + mldsa.Mldsa65 -> Config(k: 6, l: 5, eta: Eta4) + mldsa.Mldsa87 -> Config(k: 8, l: 7, eta: Eta2) + } +} + +// FIPS 204 NTT twiddle factors: zetas[i] = 1753^bitrev8(i) mod 8380417 +fn zetas() -> Dict(Int, Int) { + [ + 1, + 4_808_194, + 3_765_607, + 3_761_513, + 5_178_923, + 5_496_691, + 5_234_739, + 5_178_987, + 7_778_734, + 3_542_485, + 2_682_288, + 2_129_892, + 3_764_867, + 7_375_178, + 557_458, + 7_159_240, + 5_010_068, + 4_317_364, + 2_663_378, + 6_705_802, + 4_855_975, + 7_946_292, + 676_590, + 7_044_481, + 5_152_541, + 1_714_295, + 2_453_983, + 1_460_718, + 7_737_789, + 4_795_319, + 2_815_639, + 2_283_733, + 3_602_218, + 3_182_878, + 2_740_543, + 4_793_971, + 5_269_599, + 2_101_410, + 3_704_823, + 1_159_875, + 394_148, + 928_749, + 1_095_468, + 4_874_037, + 2_071_829, + 4_361_428, + 3_241_972, + 2_156_050, + 3_415_069, + 1_759_347, + 7_562_881, + 4_805_951, + 3_756_790, + 6_444_618, + 6_663_429, + 4_430_364, + 5_483_103, + 3_192_354, + 556_856, + 3_870_317, + 2_917_338, + 1_853_806, + 3_345_963, + 1_858_416, + 3_073_009, + 1_277_625, + 5_744_944, + 3_852_015, + 4_183_372, + 5_157_610, + 5_258_977, + 8_106_357, + 2_508_980, + 2_028_118, + 1_937_570, + 4_564_692, + 2_811_291, + 5_396_636, + 7_270_901, + 4_158_088, + 1_528_066, + 482_649, + 1_148_858, + 5_418_153, + 7_814_814, + 169_688, + 2_462_444, + 5_046_034, + 4_213_992, + 4_892_034, + 1_987_814, + 5_183_169, + 1_736_313, + 235_407, + 5_130_263, + 3_258_457, + 5_801_164, + 1_787_943, + 5_989_328, + 6_125_690, + 3_482_206, + 4_197_502, + 7_080_401, + 6_018_354, + 7_062_739, + 2_461_387, + 3_035_980, + 621_164, + 3_901_472, + 7_153_756, + 2_925_816, + 3_374_250, + 1_356_448, + 5_604_662, + 2_683_270, + 5_601_629, + 4_912_752, + 2_312_838, + 7_727_142, + 7_921_254, + 348_812, + 8_052_569, + 1_011_223, + 6_026_202, + 4_561_790, + 6_458_164, + 6_143_691, + 1_744_507, + 1753, + 6_444_997, + 5_720_892, + 6_924_527, + 2_660_408, + 6_600_190, + 8_321_269, + 2_772_600, + 1_182_243, + 87_208, + 636_927, + 4_415_111, + 4_423_672, + 6_084_020, + 5_095_502, + 4_663_471, + 8_352_605, + 822_541, + 1_009_365, + 5_926_272, + 6_400_920, + 1_596_822, + 4_423_473, + 4_620_952, + 6_695_264, + 4_969_849, + 2_678_278, + 4_611_469, + 4_829_411, + 635_956, + 8_129_971, + 5_925_040, + 4_234_153, + 6_607_829, + 2_192_938, + 6_653_329, + 2_387_513, + 4_768_667, + 8_111_961, + 5_199_961, + 3_747_250, + 2_296_099, + 1_239_911, + 4_541_938, + 3_195_676, + 2_642_980, + 1_254_190, + 8_368_000, + 2_998_219, + 141_835, + 8_291_116, + 2_513_018, + 7_025_525, + 613_238, + 7_070_156, + 6_161_950, + 7_921_677, + 6_458_423, + 4_040_196, + 4_908_348, + 2_039_144, + 6_500_539, + 7_561_656, + 6_201_452, + 6_757_063, + 2_105_286, + 6_006_015, + 6_346_610, + 586_241, + 7_200_804, + 527_981, + 5_637_006, + 6_903_432, + 1_994_046, + 2_491_325, + 6_987_258, + 507_927, + 7_192_532, + 7_655_613, + 6_545_891, + 5_346_675, + 8_041_997, + 2_647_994, + 3_009_748, + 5_767_564, + 4_148_469, + 749_577, + 4_357_667, + 3_980_599, + 2_569_011, + 6_764_887, + 1_723_229, + 1_665_318, + 2_028_038, + 1_163_598, + 5_011_144, + 3_994_671, + 8_368_538, + 7_009_900, + 3_020_393, + 3_363_542, + 214_880, + 545_376, + 7_609_976, + 3_105_558, + 7_277_073, + 508_145, + 7_826_699, + 860_144, + 3_430_436, + 140_244, + 6_866_265, + 6_195_333, + 3_123_762, + 2_358_373, + 6_187_330, + 5_365_997, + 6_663_603, + 2_926_054, + 7_987_710, + 8_077_412, + 3_531_229, + 4_405_932, + 4_606_686, + 1_900_052, + 7_598_542, + 1_054_478, + 7_648_983, + ] + |> list.index_map(fn(x, i) { #(i, x) }) + |> dict.from_list +} + +fn mod_q(x: Int) -> Int { + let r = x % q + case r < 0 { + True -> r + q + False -> r + } +} + +fn poly_add(a: List(Int), b: List(Int)) -> List(Int) { + list.map2(a, b, fn(ai, bi) { mod_q(ai + bi) }) +} + +fn poly_to_dict(poly: List(Int)) -> Dict(Int, Int) { + poly + |> list.index_map(fn(x, i) { #(i, x) }) + |> dict.from_list +} + +fn dict_to_poly(d: Dict(Int, Int)) -> List(Int) { + int.range(from: n - 1, to: -1, with: [], run: fn(acc, i) { + let assert Ok(v) = dict.get(d, i) + [v, ..acc] + }) +} + +fn ntt_outer( + coeffs: Dict(Int, Int), + z: Dict(Int, Int), + k: Int, + length: Int, +) -> #(Dict(Int, Int), Int) { + use <- bool.guard(when: length < 1, return: #(coeffs, k)) + let #(coeffs, k) = ntt_middle(coeffs, z, k, length, 0) + ntt_outer(coeffs, z, k, length / 2) +} + +fn ntt_middle( + coeffs: Dict(Int, Int), + z: Dict(Int, Int), + k: Int, + length: Int, + start: Int, +) -> #(Dict(Int, Int), Int) { + use <- bool.guard(when: start >= n, return: #(coeffs, k)) + let k = k + 1 + let assert Ok(zeta) = dict.get(z, k) + let coeffs = ntt_inner(coeffs, zeta, length, start, start) + ntt_middle(coeffs, z, k, length, start + 2 * length) +} + +fn ntt_inner( + coeffs: Dict(Int, Int), + zeta: Int, + length: Int, + end: Int, + j: Int, +) -> Dict(Int, Int) { + use <- bool.guard(when: j >= end + length, return: coeffs) + let assert Ok(cj) = dict.get(coeffs, j) + let assert Ok(cjl) = dict.get(coeffs, j + length) + let t = mod_q(zeta * cjl) + let coeffs = + coeffs + |> dict.insert(j + length, mod_q(cj - t)) + |> dict.insert(j, mod_q(cj + t)) + + ntt_inner(coeffs, zeta, length, end, j + 1) +} + +fn intt_outer( + coeffs: Dict(Int, Int), + z: Dict(Int, Int), + k: Int, + length: Int, +) -> #(Dict(Int, Int), Int) { + use <- bool.guard(when: length >= n, return: #(coeffs, k)) + let #(coeffs, k) = intt_middle(coeffs, z, k, length, 0) + intt_outer(coeffs, z, k, length * 2) +} + +fn intt_middle( + coeffs: Dict(Int, Int), + z: Dict(Int, Int), + k: Int, + length: Int, + start: Int, +) -> #(Dict(Int, Int), Int) { + use <- bool.guard(when: start >= n, return: #(coeffs, k)) + let k = k - 1 + let assert Ok(zeta) = dict.get(z, k) + let neg_zeta = q - zeta + let coeffs = intt_inner(coeffs, neg_zeta, length, start, start) + intt_middle(coeffs, z, k, length, start + 2 * length) +} + +fn intt_inner( + coeffs: Dict(Int, Int), + neg_zeta: Int, + length: Int, + end: Int, + j: Int, +) -> Dict(Int, Int) { + use <- bool.guard(when: j >= end + length, return: coeffs) + let assert Ok(cj) = dict.get(coeffs, j) + let assert Ok(cjl) = dict.get(coeffs, j + length) + let coeffs = + coeffs + |> dict.insert(j, mod_q(cj + cjl)) + |> dict.insert(j + length, mod_q(neg_zeta * { cj - cjl })) + intt_inner(coeffs, neg_zeta, length, end, j + 1) +} + +fn ntt_from_dict(coeffs: Dict(Int, Int), z: Dict(Int, Int)) -> Dict(Int, Int) { + let #(result, _k) = ntt_outer(coeffs, z, 0, 128) + result +} + +fn intt_to_list(coeffs: Dict(Int, Int), z: Dict(Int, Int)) -> List(Int) { + let #(result, _k) = intt_outer(coeffs, z, n, 1) + result + |> dict_to_poly + |> list.map(fn(v) { mod_q(v * inv_ntt_scale) }) +} + +fn poly_zero_dict() -> Dict(Int, Int) { + int.range(from: 0, to: n, with: dict.new(), run: fn(acc, i) { + dict.insert(acc, i, 0) + }) +} + +fn dict_zip_with( + a: Dict(Int, Int), + b: Dict(Int, Int), + op: fn(Int, Int) -> Int, +) -> Dict(Int, Int) { + dict.map_values(a, fn(i, ai) { + let assert Ok(bi) = dict.get(b, i) + mod_q(op(ai, bi)) + }) +} + +fn poly_add_dict(a: Dict(Int, Int), b: Dict(Int, Int)) -> Dict(Int, Int) { + dict_zip_with(a, b, int.add) +} + +fn poly_pointwise_mul_dict( + a: Dict(Int, Int), + b: Dict(Int, Int), +) -> Dict(Int, Int) { + dict_zip_with(a, b, int.multiply) +} + +fn expand_a( + rho: BitArray, + config: Config, +) -> Result(List(List(List(Int))), Nil) { + int.range(from: config.k - 1, to: -1, with: [], run: list.prepend) + |> list.try_map(fn(i) { + int.range(from: config.l - 1, to: -1, with: [], run: list.prepend) + |> list.try_map(fn(j) { rej_ntt_poly(rho, j, i) }) + }) +} + +fn rej_ntt_poly(rho: BitArray, j: Int, i: Int) -> Result(List(Int), Nil) { + let assert Ok(algo) = hash.shake_128(output_length: 1024) + let assert Ok(h) = hash.new(algo) + let output = + h + |> hash.update(rho) + |> hash.update(<>) + |> hash.final() + rejection_sample(output, [], 0) +} + +fn rejection_sample( + bytes: BitArray, + acc: List(Int), + count: Int, +) -> Result(List(Int), Nil) { + use <- bool.guard(when: count >= n, return: Ok(list.reverse(acc))) + case bytes { + <> -> { + let val = int.bitwise_and(val, 0x7FFFFF) + case val < q { + True -> rejection_sample(rest, [val, ..acc], count + 1) + False -> rejection_sample(rest, acc, count) + } + } + _ -> Error(Nil) + } +} + +fn expand_s( + rho_prime: BitArray, + config: Config, +) -> Result(#(List(List(Int)), List(List(Int))), Nil) { + use s1 <- result.try( + list.try_map( + int.range(from: config.l - 1, to: -1, with: [], run: list.prepend), + fn(nonce) { rej_bounded_poly(rho_prime, nonce, config.eta) }, + ), + ) + use s2 <- result.try( + list.try_map( + int.range(from: config.k - 1, to: -1, with: [], run: list.prepend), + fn(i) { rej_bounded_poly(rho_prime, config.l + i, config.eta) }, + ), + ) + Ok(#(s1, s2)) +} + +fn rej_bounded_poly( + rho_prime: BitArray, + nonce: Int, + eta: Eta, +) -> Result(List(Int), Nil) { + let assert Ok(algo) = hash.shake_256(output_length: 512) + let assert Ok(h) = hash.new(algo) + let output = + h + |> hash.update(rho_prime) + |> hash.update(<>) + |> hash.final() + half_byte_sample(output, eta, [], 0) +} + +fn half_byte_sample( + bytes: BitArray, + eta: Eta, + acc: List(Int), + count: Int, +) -> Result(List(Int), Nil) { + use <- bool.guard(when: count >= n, return: Ok(list.reverse(acc))) + case bytes { + <> -> { + let lo = byte % 16 + let hi = byte / 16 + let #(acc, count) = push_coeff(acc, count, lo, eta) + case count >= n { + True -> Ok(list.reverse(acc)) + False -> { + let #(acc, count) = push_coeff(acc, count, hi, eta) + half_byte_sample(rest, eta, acc, count) + } + } + } + _ -> Error(Nil) + } +} + +fn push_coeff( + acc: List(Int), + count: Int, + nibble: Int, + eta: Eta, +) -> #(List(Int), Int) { + case coeff_from_half_byte(nibble, eta) { + Ok(c) -> #([c, ..acc], count + 1) + Error(Nil) -> #(acc, count) + } +} + +fn coeff_from_half_byte(nibble: Int, eta: Eta) -> Result(Int, Nil) { + case eta { + Eta2 -> + case nibble < 15 { + True -> Ok(mod_q(2 - nibble % 5)) + False -> Error(Nil) + } + Eta4 -> + case nibble < 9 { + True -> Ok(mod_q(4 - nibble)) + False -> Error(Nil) + } + } +} + +fn power2round(r: Int) -> #(Int, Int) { + let r_pos = mod_q(r) + let two_d = 8192 + let r0 = r_pos % two_d + let r0_centered = case r0 > two_d / 2 { + True -> r0 - two_d + False -> r0 + } + let r1 = { r_pos - r0_centered } / two_d + #(r1, r0_centered) +} + +fn simple_bit_pack_t1(coeffs: List(Int)) -> BitArray { + simple_bit_pack_t1_loop(coeffs, 0, 0, <<>>) +} + +fn simple_bit_pack_t1_loop( + coeffs: List(Int), + bit_buffer: Int, + bits_in_buffer: Int, + acc: BitArray, +) -> BitArray { + case coeffs { + // t1 is 256 coeffs x 10 bits = 320 bytes exactly, so the buffer is always + // byte-aligned (empty) here — no partial byte to flush. + [] -> acc + [c, ..rest] -> { + let bit_buffer = + bit_buffer + c * int.bitwise_shift_left(1, bits_in_buffer) + let bits_in_buffer = bits_in_buffer + 10 + let #(bit_buffer, bits_in_buffer, acc) = + emit_full_bytes(bit_buffer, bits_in_buffer, acc) + simple_bit_pack_t1_loop(rest, bit_buffer, bits_in_buffer, acc) + } + } +} + +fn emit_full_bytes( + bit_buffer: Int, + bits_in_buffer: Int, + acc: BitArray, +) -> #(Int, Int, BitArray) { + case bits_in_buffer >= 8 { + True -> { + let byte = bit_buffer % 256 + emit_full_bytes(bit_buffer / 256, bits_in_buffer - 8, << + acc:bits, + byte:8, + >>) + } + False -> #(bit_buffer, bits_in_buffer, acc) + } +} + +pub fn public_key_from_seed( + param: mldsa.ParameterSet, + seed: BitArray, +) -> Result(BitArray, Nil) { + use <- bool.guard(when: bit_array.byte_size(seed) != 32, return: Error(Nil)) + let config = param_config(param) + + let assert Ok(algo) = hash.shake_256(output_length: 96) + let assert Ok(h) = hash.new(algo) + let expanded = + h + |> hash.update(seed) + |> hash.update(<>) + |> hash.final() + let assert <> = expanded + + use a_hat <- result.try(expand_a(rho, config)) + use #(s1, s2) <- result.try(expand_s(rho_prime, config)) + + let z = zetas() + let s1_hat = + list.map(s1, fn(p) { + p + |> poly_to_dict + |> ntt_from_dict(z) + }) + + let t = + list.map2(a_hat, s2, fn(a_row, s2_i) { + let product = + list.map2(a_row, s1_hat, fn(a_poly, s1_hat_i) { + a_poly + |> poly_to_dict + |> poly_pointwise_mul_dict(s1_hat_i) + }) + |> list.fold(poly_zero_dict(), poly_add_dict) + poly_add(intt_to_list(product, z), s2_i) + }) + + let t1 = list.map(t, list.map(_, fn(c) { pair.first(power2round(c)) })) + + let public_key = + bytes_tree.new() + |> bytes_tree.append(rho) + |> bytes_tree.append_tree( + list.map(t1, simple_bit_pack_t1) + |> bytes_tree.concat_bit_arrays, + ) + |> bytes_tree.to_bit_array + + Ok(public_key) +} diff --git a/src/kryptos/internal/x509.gleam b/src/kryptos/internal/x509.gleam index 9d75e7b..2a033e1 100644 --- a/src/kryptos/internal/x509.gleam +++ b/src/kryptos/internal/x509.gleam @@ -11,6 +11,7 @@ import kryptos/eddsa import kryptos/hash import kryptos/internal/der import kryptos/internal/utils +import kryptos/mldsa import kryptos/rsa import kryptos/x509 import kryptos/xdh @@ -35,6 +36,12 @@ const oid_ed25519 = x509.Oid([1, 3, 101, 112]) const oid_ed448 = x509.Oid([1, 3, 101, 113]) +const oid_mldsa44 = x509.Oid([2, 16, 840, 1, 101, 3, 4, 3, 17]) + +const oid_mldsa65 = x509.Oid([2, 16, 840, 1, 101, 3, 4, 3, 18]) + +const oid_mldsa87 = x509.Oid([2, 16, 840, 1, 101, 3, 4, 3, 19]) + type PemError { PemNotFound PemMalformed @@ -94,6 +101,15 @@ pub fn eddsa_sig_alg_info(curve: eddsa.Curve) -> SigAlgInfo { } } +/// Map an ML-DSA parameter set to signature algorithm information. +pub fn mldsa_sig_alg_info(param: mldsa.ParameterSet) -> SigAlgInfo { + case param { + mldsa.Mldsa44 -> SigAlgInfo(oid_mldsa44, False) + mldsa.Mldsa65 -> SigAlgInfo(oid_mldsa65, False) + mldsa.Mldsa87 -> SigAlgInfo(oid_mldsa87, False) + } +} + /// Verify a signature using the appropriate algorithm based on public key and signature algorithm. pub fn verify_signature( public_key: x509.PublicKey, @@ -112,8 +128,13 @@ pub fn verify_signature( } x509.EdPublicKey(key), x509.Ed25519 | x509.EdPublicKey(key), x509.Ed448 -> eddsa.verify(key, data, signature) + x509.MldsaPublicKey(key), x509.Mldsa44 + | x509.MldsaPublicKey(key), x509.Mldsa65 + | x509.MldsaPublicKey(key), x509.Mldsa87 + -> mldsa.verify(key, data, signature) x509.EdPublicKey(_), _ -> False x509.XdhPublicKey(_), _ -> False + x509.MldsaPublicKey(_), _ -> False } } @@ -126,7 +147,8 @@ fn verify_with_hash( x509.EcdsaSha256 | x509.RsaSha256 -> Ok(hash.Sha256) x509.EcdsaSha384 | x509.RsaSha384 -> Ok(hash.Sha384) x509.EcdsaSha512 | x509.RsaSha512 -> Ok(hash.Sha512) - x509.Ed25519 | x509.Ed448 -> Error(Nil) + x509.Ed25519 | x509.Ed448 | x509.Mldsa44 | x509.Mldsa65 | x509.Mldsa87 -> + Error(Nil) } hash_alg |> result.map(verify) |> result.unwrap(False) } @@ -175,6 +197,9 @@ pub fn parse_signature_algorithm( [1, 2, 840, 10_045, 4, 3, 4] -> Ok(x509.EcdsaSha512) [1, 3, 101, 112] -> Ok(x509.Ed25519) [1, 3, 101, 113] -> Ok(x509.Ed448) + [2, 16, 840, 1, 101, 3, 4, 3, 17] -> Ok(x509.Mldsa44) + [2, 16, 840, 1, 101, 3, 4, 3, 18] -> Ok(x509.Mldsa65) + [2, 16, 840, 1, 101, 3, 4, 3, 19] -> Ok(x509.Mldsa87) _ -> Error(x509.Oid(oid_components)) } Error(_) -> Error(x509.Oid([])) @@ -332,6 +357,15 @@ fn dispatch_public_key_parse( // id-Ed25519 / id-Ed448 (RFC 8410) [1, 3, 101, 112] | [1, 3, 101, 113] -> wrap_key(eddsa.public_key_from_der(spki_bytes), x509.EdPublicKey, alg_oid) + // id-ML-DSA-44 / id-ML-DSA-65 / id-ML-DSA-87 (FIPS 204) + [2, 16, 840, 1, 101, 3, 4, 3, 17] + | [2, 16, 840, 1, 101, 3, 4, 3, 18] + | [2, 16, 840, 1, 101, 3, 4, 3, 19] -> + wrap_key( + mldsa.public_key_from_der(spki_bytes), + x509.MldsaPublicKey, + alg_oid, + ) _ -> Error(x509.Oid(alg_oid)) } } diff --git a/src/kryptos/mldsa.gleam b/src/kryptos/mldsa.gleam new file mode 100644 index 0000000..6acb0d2 --- /dev/null +++ b/src/kryptos/mldsa.gleam @@ -0,0 +1,254 @@ +//// Module-Lattice-Based Digital Signature Algorithm (ML-DSA). +//// +//// ML-DSA (formerly CRYSTALS-Dilithium) provides post-quantum digital +//// signatures based on lattice problems believed to resist both classical and +//// quantum attacks. Conventional schemes like ECDSA and Ed25519 rely on +//// elliptic-curve discrete logarithms, which a sufficiently powerful quantum +//// computer could break via [Shor's algorithm](https://en.wikipedia.org/wiki/Shor's_algorithm). +//// ML-DSA is designed for systems that need long-term security guarantees. +//// +//// ## Trade-offs +//// +//// Post-quantum security comes at a cost. ML-DSA public keys and signatures +//// are significantly larger than their classical counterparts: +//// +//// | Algorithm | Public Key | Signature | +//// | ----------- | ---------- | --------- | +//// | Ed25519 | 32 B | 64 B | +//// | ECDSA P-256 | 64 B | 64 B | +//// | ML-DSA-44 | 1,312 B | 2,420 B | +//// | ML-DSA-65 | 1,952 B | 3,309 B | +//// | ML-DSA-87 | 2,592 B | 4,627 B | +//// +//// All of these use a 32-byte private key. For ML-DSA that is the seed this +//// module stores and exports; the expanded signing key held in memory is +//// larger (2,560 to 4,896 B) but is never serialized. +//// +//// These larger sizes affect wire overhead, certificate sizes, handshake +//// latency, and storage. ML-DSA-44 targets roughly NIST Security Level 2 +//// (comparable to AES-128), ML-DSA-65 targets Level 3, and ML-DSA-87 targets +//// Level 5. Choose the smallest parameter set that meets your security +//// requirements. ML-DSA-44 is appropriate for most applications. +//// +//// ## Key representations +//// +//// FIPS 204 defines two private key representations: a compact 32-byte seed +//// and the fully expanded key material. Following the +//// [CFRG security considerations](https://datatracker.ietf.org/doc/draft-connolly-cfrg-ml-dsa-security-considerations/), +//// this module treats the seed as the canonical representation and never +//// exposes expanded keys. Seed generation guarantees well-formed key material, +//// whereas an externally supplied expanded key could be malformed. The +//// [pyca/cryptography](https://cryptography.io/en/latest/hazmat/primitives/asymmetric/mldsa/) +//// library takes the same seed-only approach. +//// +//// ## Context strings +//// +//// FIPS 204 defines an optional context string parameter for signing and +//// verification. Neither Erlang/OTP nor Node.js expose this parameter in +//// their crypto APIs, so this module always uses the default empty context +//// string. +//// +//// ## Example +//// +//// ```gleam +//// import kryptos/mldsa +//// +//// let #(private_key, public_key) = mldsa.generate_key_pair(mldsa.Mldsa44) +//// let message = <<"hello world":utf8>> +//// let signature = mldsa.sign(private_key, message) +//// let valid = mldsa.verify(public_key, message, signature) +//// // valid == True +//// ``` + +import kryptos/internal/utils + +/// An ML-DSA private key. +pub type PrivateKey + +/// An ML-DSA public key. +pub type PublicKey + +/// ML-DSA parameter sets. +pub type ParameterSet { + /// ML-DSA-44. 1312-byte public keys, 2420-byte signatures. + Mldsa44 + /// ML-DSA-65. 1952-byte public keys, 3309-byte signatures. + Mldsa65 + /// ML-DSA-87. 2592-byte public keys, 4627-byte signatures. + Mldsa87 +} + +/// Returns the public key size in bytes for the given parameter set. +pub fn key_size(param: ParameterSet) -> Int { + case param { + Mldsa44 -> 1312 + Mldsa65 -> 1952 + Mldsa87 -> 2592 + } +} + +/// Returns the signature size in bytes for the given parameter set. +pub fn signature_size(param: ParameterSet) -> Int { + case param { + Mldsa44 -> 2420 + Mldsa65 -> 3309 + Mldsa87 -> 4627 + } +} + +/// Returns whether the current runtime supports ML-DSA operations. +/// +/// ML-DSA requires Erlang/OTP 28 or later, or Node.js 24 or later. On older +/// runtimes this returns `False` and ML-DSA operations are unavailable. +@external(erlang, "kryptos_ffi", "mldsa_supported") +@external(javascript, "../kryptos_ffi.mjs", "mldsaSupported") +pub fn supported() -> Bool + +/// Generates a new ML-DSA key pair. +@external(erlang, "kryptos_ffi", "mldsa_generate_key_pair") +@external(javascript, "../kryptos_ffi.mjs", "mldsaGenerateKeyPair") +pub fn generate_key_pair(param: ParameterSet) -> #(PrivateKey, PublicKey) + +/// Signs a message using ML-DSA. +/// +/// ML-DSA includes built-in hashing. No separate hash algorithm parameter +/// is needed. +@external(erlang, "kryptos_ffi", "mldsa_sign") +@external(javascript, "../kryptos_ffi.mjs", "mldsaSign") +pub fn sign(private_key: PrivateKey, message: BitArray) -> BitArray + +/// Verifies an ML-DSA signature against a message. +@external(erlang, "kryptos_ffi", "mldsa_verify") +@external(javascript, "../kryptos_ffi.mjs", "mldsaVerify") +pub fn verify( + public_key: PublicKey, + message: BitArray, + signature signature: BitArray, +) -> Bool + +/// Exports a public key to its FIPS 204 raw byte encoding (`rho || t1_packed`), +/// without any ASN.1 wrapping. Use `public_key_to_pem` or `public_key_to_der` +/// for SPKI-encoded output. +@external(erlang, "kryptos_ffi", "mldsa_public_key_to_bytes") +@external(javascript, "../kryptos_ffi.mjs", "mldsaPublicKeyToBytes") +pub fn public_key_to_bytes(key: PublicKey) -> BitArray + +/// Imports a public key from its FIPS 204 raw byte encoding (`rho || t1_packed`), +/// without any ASN.1 wrapping. Use `public_key_from_pem` or `public_key_from_der` +/// for SPKI-encoded input. +/// +/// Returns `Error(Nil)` if the bytes are the wrong size for the parameter set. +@external(erlang, "kryptos_ffi", "mldsa_public_key_from_bytes") +@external(javascript, "../kryptos_ffi.mjs", "mldsaPublicKeyFromBytes") +pub fn public_key_from_bytes( + param: ParameterSet, + public_bytes: BitArray, +) -> Result(PublicKey, Nil) + +/// Derives the public key from an ML-DSA private key. +@external(erlang, "kryptos_ffi", "mldsa_public_key_from_private") +@external(javascript, "../kryptos_ffi.mjs", "mldsaPublicKeyFromPrivate") +pub fn public_key_from_private_key(key: PrivateKey) -> PublicKey + +/// Returns the parameter set for an ML-DSA private key. +@external(erlang, "kryptos_ffi", "mldsa_private_key_parameter_set") +@external(javascript, "../kryptos_ffi.mjs", "mldsaPrivateKeyParameterSet") +pub fn parameter_set(key: PrivateKey) -> ParameterSet + +/// Returns the parameter set for an ML-DSA public key. +@external(erlang, "kryptos_ffi", "mldsa_public_key_parameter_set") +@external(javascript, "../kryptos_ffi.mjs", "mldsaPublicKeyParameterSet") +pub fn public_key_parameter_set(key: PublicKey) -> ParameterSet + +/// Creates an ML-DSA key pair from a 32-byte seed. +/// +/// Returns `Error(Nil)` if the seed is not exactly 32 bytes. +@external(erlang, "kryptos_ffi", "mldsa_from_seed") +@external(javascript, "../kryptos_ffi.mjs", "mldsaFromSeed") +pub fn from_seed( + param: ParameterSet, + seed: BitArray, +) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports the 32-byte seed from an ML-DSA private key. +@external(erlang, "kryptos_ffi", "mldsa_private_key_to_seed") +@external(javascript, "../kryptos_ffi.mjs", "mldsaPrivateKeyToSeed") +pub fn private_key_to_seed(key: PrivateKey) -> Result(BitArray, Nil) + +/// Imports an ML-DSA private key from PEM-encoded data. +/// +/// The key must carry the 32-byte seed. ExpandedKey-only encodings, which +/// omit the seed, are rejected with `Error(Nil)`. +@external(erlang, "kryptos_ffi", "mldsa_import_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "mldsaImportPrivateKeyPem") +pub fn from_pem(pem: String) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Imports an ML-DSA private key from DER-encoded data. +/// +/// The key must carry the 32-byte seed. ExpandedKey-only encodings, which +/// omit the seed, are rejected with `Error(Nil)`. +@external(erlang, "kryptos_ffi", "mldsa_import_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "mldsaImportPrivateKeyDer") +pub fn from_der(der: BitArray) -> Result(#(PrivateKey, PublicKey), Nil) + +/// Exports an ML-DSA private key to PEM format. +/// +/// The key is exported as seed-form PKCS#8. +pub fn to_pem(key: PrivateKey) -> String { + let assert Ok(pem) = do_to_pem(key) |> utils.normalize_pem + pem +} + +@external(erlang, "kryptos_ffi", "mldsa_export_private_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "exportPrivateKeyPem") +fn do_to_pem(key: PrivateKey) -> Result(String, Nil) + +/// Exports an ML-DSA private key to DER format. +/// +/// The key is exported as seed-form PKCS#8. +pub fn to_der(key: PrivateKey) -> BitArray { + let assert Ok(der) = do_to_der(key) + der +} + +@external(erlang, "kryptos_ffi", "mldsa_export_private_key_der") +@external(javascript, "../kryptos_ffi.mjs", "exportPrivateKeyDer") +fn do_to_der(key: PrivateKey) -> Result(BitArray, Nil) + +/// Imports an ML-DSA public key from PEM-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "mldsa_import_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "mldsaImportPublicKeyPem") +pub fn public_key_from_pem(pem: String) -> Result(PublicKey, Nil) + +/// Imports an ML-DSA public key from DER-encoded data. +/// +/// The key must be in SPKI format. +@external(erlang, "kryptos_ffi", "mldsa_import_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "mldsaImportPublicKeyDer") +pub fn public_key_from_der(der: BitArray) -> Result(PublicKey, Nil) + +/// Exports an ML-DSA public key to PEM format. +/// +/// The key is exported in SPKI format. +pub fn public_key_to_pem(key: PublicKey) -> String { + let assert Ok(pem) = do_public_key_to_pem(key) |> utils.normalize_pem + pem +} + +@external(erlang, "kryptos_ffi", "export_public_key_pem") +@external(javascript, "../kryptos_ffi.mjs", "exportPublicKeyPem") +fn do_public_key_to_pem(key: PublicKey) -> Result(String, Nil) + +/// Exports an ML-DSA public key to DER format. +/// +/// The key is exported in SPKI format. +pub fn public_key_to_der(key: PublicKey) -> BitArray { + let assert Ok(der) = do_public_key_to_der(key) + der +} + +@external(erlang, "kryptos_ffi", "export_public_key_der") +@external(javascript, "../kryptos_ffi.mjs", "exportPublicKeyDer") +fn do_public_key_to_der(key: PublicKey) -> Result(BitArray, Nil) diff --git a/src/kryptos/x509.gleam b/src/kryptos/x509.gleam index 0c6ba1d..cd72710 100644 --- a/src/kryptos/x509.gleam +++ b/src/kryptos/x509.gleam @@ -51,6 +51,7 @@ import gleam/time/timestamp.{type Timestamp} import kryptos/ec import kryptos/eddsa import kryptos/internal/der +import kryptos/mldsa import kryptos/rsa import kryptos/xdh @@ -126,6 +127,8 @@ pub type PublicKey { EdPublicKey(eddsa.PublicKey) /// An XDH public key (e.g., Curve25519 or Curve448). XdhPublicKey(xdh.PublicKey) + /// An ML-DSA public key (ML-DSA-44, ML-DSA-65, or ML-DSA-87). + MldsaPublicKey(mldsa.PublicKey) } /// Signature algorithm used in X.509 structures. @@ -150,6 +153,12 @@ pub type SignatureAlgorithm { Ed25519 /// Edwards-Curve Digital Signature Algorithm using Ed448 Ed448 + /// Module-Lattice Digital Signature Algorithm ML-DSA-44 (FIPS 204) + Mldsa44 + /// Module-Lattice Digital Signature Algorithm ML-DSA-65 (FIPS 204) + Mldsa65 + /// Module-Lattice Digital Signature Algorithm ML-DSA-87 (FIPS 204) + Mldsa87 } /// Basic Constraints extension - indicates if cert is a CA. diff --git a/src/kryptos/x509/certificate.gleam b/src/kryptos/x509/certificate.gleam index 1c8bc0b..d56fa45 100644 --- a/src/kryptos/x509/certificate.gleam +++ b/src/kryptos/x509/certificate.gleam @@ -75,7 +75,8 @@ import kryptos/eddsa import kryptos/hash.{type HashAlgorithm} import kryptos/internal/der import kryptos/internal/utils -import kryptos/internal/x509.{type SigAlgInfo} as x509_internal +import kryptos/internal/x509 as x509_internal +import kryptos/mldsa import kryptos/rsa import kryptos/x509 import kryptos/xdh @@ -211,6 +212,7 @@ pub opaque type Certificate(status) { /// - `self_signed_with_ecdsa()` for ECDSA keys /// - `self_signed_with_rsa()` for RSA keys /// - `self_signed_with_eddsa()` for Ed25519/Ed448 keys +/// - `self_signed_with_mldsa()` for ML-DSA keys pub opaque type Builder { Builder( subject: x509.Name, @@ -404,7 +406,7 @@ pub fn self_signed_with_eddsa( fn finish_self_signed( builder: Builder, - sig_alg: SigAlgInfo, + sig_alg: x509_internal.SigAlgInfo, spki: BitArray, sign: fn(BitArray) -> BitArray, ) -> Result(Certificate(Built), Nil) { @@ -426,6 +428,25 @@ fn finish_self_signed( Ok(BuiltCertificate(cert_der)) } +/// Signs a self-signed certificate with an ML-DSA private key. +/// +/// The public key is derived from the private key and used as both +/// the issuer and subject public key. ML-DSA has built-in hashing, so no +/// hash algorithm parameter is needed. +/// +/// ML-DSA is a post-quantum signature algorithm (FIPS 204); support may be +/// limited with browsers and certificate authorities. +pub fn self_signed_with_mldsa( + builder: Builder, + key: mldsa.PrivateKey, +) -> Result(Certificate(Built), Nil) { + let sig_alg = x509_internal.mldsa_sig_alg_info(mldsa.parameter_set(key)) + let public_key = mldsa.public_key_from_private_key(key) + let spki = mldsa.public_key_to_der(public_key) + use tbs <- finish_self_signed(builder, sig_alg, spki) + mldsa.sign(key, tbs) +} + /// Exports the certificate as DER-encoded bytes. pub fn to_der(cert: Certificate(a)) -> BitArray { case cert { @@ -698,7 +719,7 @@ pub fn extensions( /// Verify a certificate's signature against an issuer's public key. /// -/// The public key must be RSA, ECDSA, or EdDSA (XDH keys cannot sign). +/// The public key must be RSA, ECDSA, EdDSA, or ML-DSA (XDH keys cannot sign). pub fn verify( cert: Certificate(Parsed), issuer_public_key: x509.PublicKey, @@ -1092,7 +1113,7 @@ fn parse_eku_oids( fn encode_tbs_certificate( builder: Builder, serial: BitArray, - sig_alg: SigAlgInfo, + sig_alg: x509_internal.SigAlgInfo, spki: BitArray, validity: x509.Validity, ) -> Result(BitArray, Nil) { diff --git a/src/kryptos/x509/csr.gleam b/src/kryptos/x509/csr.gleam index 18319b8..add2938 100644 --- a/src/kryptos/x509/csr.gleam +++ b/src/kryptos/x509/csr.gleam @@ -42,6 +42,7 @@ import kryptos/hash.{type HashAlgorithm} import kryptos/internal/der import kryptos/internal/utils import kryptos/internal/x509 as x509_internal +import kryptos/mldsa import kryptos/rsa import kryptos/x509 @@ -64,7 +65,7 @@ pub type Parsed /// A Certificate Signing Request. /// /// The phantom type parameter tracks how the CSR was created: -/// - `Csr(Built)` - created via `sign_with_ecdsa` or `sign_with_rsa` +/// - `Csr(Built)` - created via `sign_with_ecdsa`, `sign_with_rsa`, `sign_with_eddsa`, or `sign_with_mldsa` /// - `Csr(Parsed)` - created via `from_pem` or `from_der` /// /// Export functions (`to_pem`, `to_der`) work on any `Csr(a)`. @@ -103,7 +104,8 @@ pub opaque type Builder { /// Creates a new CSR builder with an empty subject and no extensions. /// /// Use the `with_*` functions to configure the builder, then call -/// `sign_with_ecdsa` or `sign_with_rsa` to generate the signed CSR. +/// `sign_with_ecdsa`, `sign_with_rsa`, `sign_with_eddsa`, or `sign_with_mldsa` +/// to generate the signed CSR. pub fn new() -> Builder { Builder( subject: x509.name([]), @@ -220,6 +222,21 @@ fn finish_sign( Ok(BuiltCsr(csr_der)) } +/// Signs the CSR with an ML-DSA private key. +/// +/// ML-DSA is a post-quantum signature algorithm (FIPS 204); support may be +/// limited with browsers and certificate authorities. +pub fn sign_with_mldsa( + builder: Builder, + key: mldsa.PrivateKey, +) -> Result(Csr(Built), Nil) { + let sig_alg = x509_internal.mldsa_sig_alg_info(mldsa.parameter_set(key)) + let public_key = mldsa.public_key_from_private_key(key) + let spki = mldsa.public_key_to_der(public_key) + use request_info <- finish_sign(builder, sig_alg, spki) + mldsa.sign(key, request_info) +} + /// Exports the CSR as DER-encoded bytes. pub fn to_der(csr: Csr(a)) -> BitArray { case csr { diff --git a/src/kryptos_ffi.erl b/src/kryptos_ffi.erl index 654a877..944a834 100644 --- a/src/kryptos_ffi.erl +++ b/src/kryptos_ffi.erl @@ -108,7 +108,24 @@ xdh_public_key_from_private/1, xdh_public_key_to_bytes/1, xdh_private_key_curve/1, - xdh_public_key_curve/1 + xdh_public_key_curve/1, + mldsa_supported/0, + mldsa_generate_key_pair/1, + mldsa_sign/2, + mldsa_verify/3, + mldsa_public_key_from_bytes/2, + mldsa_public_key_to_bytes/1, + mldsa_public_key_from_private/1, + mldsa_private_key_parameter_set/1, + mldsa_public_key_parameter_set/1, + mldsa_import_private_key_pem/1, + mldsa_import_private_key_der/1, + mldsa_export_private_key_pem/1, + mldsa_export_private_key_der/1, + mldsa_import_public_key_pem/1, + mldsa_import_public_key_der/1, + mldsa_from_seed/2, + mldsa_private_key_to_seed/1 ]). %%------------------------------------------------------------------------------ @@ -951,12 +968,12 @@ xdh_private_key_to_der(KeyBytes, Curve) -> end, public_key:der_encode('PrivateKeyInfo', PrivKeyInfo). -export_public_key_pem({PubBytes, Curve}) -> +export_public_key_pem({PubBytes, Alg}) -> try Spki = #'SubjectPublicKeyInfo'{ algorithm = - #'AlgorithmIdentifier'{algorithm = curve_to_oid(Curve)}, + #'AlgorithmIdentifier'{algorithm = public_key_alg_oid(Alg)}, subjectPublicKey = PubBytes }, Der = public_key:der_encode('SubjectPublicKeyInfo', Spki), @@ -966,12 +983,12 @@ export_public_key_pem({PubBytes, Curve}) -> {error, nil} end. -export_public_key_der({PubBytes, Curve}) -> +export_public_key_der({PubBytes, Alg}) -> try Spki = #'SubjectPublicKeyInfo'{ algorithm = - #'AlgorithmIdentifier'{algorithm = curve_to_oid(Curve)}, + #'AlgorithmIdentifier'{algorithm = public_key_alg_oid(Alg)}, subjectPublicKey = PubBytes }, {ok, public_key:der_encode('SubjectPublicKeyInfo', Spki)} @@ -980,6 +997,11 @@ export_public_key_der({PubBytes, Curve}) -> {error, nil} end. +public_key_alg_oid(Alg) when Alg =:= mldsa44; Alg =:= mldsa65; Alg =:= mldsa87 -> + mldsa_param_to_oid(Alg); +public_key_alg_oid(Curve) -> + curve_to_oid(Curve). + %%------------------------------------------------------------------------------ %% RSA %%------------------------------------------------------------------------------ @@ -1507,3 +1529,206 @@ eddsa_private_key_to_der(KeyBytes, Curve) -> {'PrivateKeyInfo', v1, AlgId, WrappedKey, asn1_NOVALUE} end, public_key:der_encode('PrivateKeyInfo', PrivKeyInfo). + +%%------------------------------------------------------------------------------ +%% ML-DSA (FIPS 204) +%%------------------------------------------------------------------------------ + +mldsa_param_to_oid(mldsa44) -> {2, 16, 840, 1, 101, 3, 4, 3, 17}; +mldsa_param_to_oid(mldsa65) -> {2, 16, 840, 1, 101, 3, 4, 3, 18}; +mldsa_param_to_oid(mldsa87) -> {2, 16, 840, 1, 101, 3, 4, 3, 19}. + +mldsa_oid_to_param({2, 16, 840, 1, 101, 3, 4, 3, 17}) -> mldsa44; +mldsa_oid_to_param({2, 16, 840, 1, 101, 3, 4, 3, 18}) -> mldsa65; +mldsa_oid_to_param({2, 16, 840, 1, 101, 3, 4, 3, 19}) -> mldsa87. + +mldsa_asn1_type(mldsa44) -> 'ML-DSA-44-PrivateKey'; +mldsa_asn1_type(mldsa65) -> 'ML-DSA-65-PrivateKey'; +mldsa_asn1_type(mldsa87) -> 'ML-DSA-87-PrivateKey'. + +mldsa_supported() -> + lists:member(mldsa44, crypto:supports(public_keys)). + +-if(?OTP_RELEASE >= 28). +mldsa_generate_key_pair(Param) -> + %% Seed-only: generate a random seed and derive, so every key is + %% seed-backed. See the CFRG security considerations: + %% https://datatracker.ietf.org/doc/draft-connolly-cfrg-ml-dsa-security-considerations/ + Seed = crypto:strong_rand_bytes(32), + {ok, KeyPair} = mldsa_seed_key_pair(Param, Seed), + KeyPair. +-else. +mldsa_generate_key_pair(_Param) -> + erlang:error(mldsa_not_supported). +-endif. + +mldsa_sign({seed, Seed, _PubKey, Param}, Message) -> + crypto:sign(Param, none, Message, {seed, Seed}). + +mldsa_verify({PubKey, Param}, Message, Signature) -> + try + crypto:verify(Param, none, Message, Signature, PubKey) + catch + _:_ -> + false + end. + +mldsa_public_key_from_bytes(Param, PublicBytes) -> + ExpectedSize = kryptos@mldsa:key_size(Param), + case byte_size(PublicBytes) of + ExpectedSize -> + {ok, {PublicBytes, Param}}; + _ -> + {error, nil} + end. + +mldsa_public_key_to_bytes({PubBytes, _Param}) -> + PubBytes. + +mldsa_public_key_from_private({_, _, PubKeyBytes, Param}) -> + {PubKeyBytes, Param}. + +mldsa_private_key_parameter_set({_, _, _, Param}) -> + Param. + +mldsa_public_key_parameter_set({_PubBytes, Param}) -> + Param. + +%% PEM/DER Import + +mldsa_import_private_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'PrivateKeyInfo', DerBytes, not_encrypted}] -> + mldsa_import_private_key_from_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +mldsa_import_private_key_der(DerBytes) -> + try + mldsa_import_private_key_from_der(DerBytes) + catch + _:_ -> + {error, nil} + end. + +mldsa_import_private_key_from_der(DerBytes) -> + {ok, Dec} = 'PKCS-FRAME':decode('OneAsymmetricKey', DerBytes), + OID = element(2, element(3, Dec)), + Param = mldsa_oid_to_param(OID), + InnerDer = element(4, Dec), + Type = mldsa_asn1_type(Param), + %% Seed-only: reject expandedKey-only keys; derive from the seed carried + %% by the seed and both forms. + case public_key:der_decode(Type, InnerDer) of + {seed, Seed} -> + mldsa_seed_key_pair(Param, Seed); + {both, BothRec} -> + mldsa_seed_key_pair(Param, element(2, BothRec)); + _ -> + {error, nil} + end. + +mldsa_seed_key_pair(Param, Seed) -> + case kryptos@internal@mldsa:public_key_from_seed(Param, Seed) of + {ok, PubKeyBytes} -> + {ok, {{seed, Seed, PubKeyBytes, Param}, {PubKeyBytes, Param}}}; + {error, nil} -> + {error, nil} + end. + +%% PEM/DER Export + +mldsa_export_private_key_pem(PrivKey) -> + case mldsa_export_private_key_der(PrivKey) of + {ok, Der} -> + {ok, public_key:pem_encode([{'PrivateKeyInfo', Der, not_encrypted}])}; + Error -> + Error + end. + +mldsa_export_private_key_der({seed, Seed, _PubKey, Param}) -> + try + {ok, mldsa_priv_key_to_der({seed, Seed}, asn1_NOVALUE, Param)} + catch + _:_ -> + {error, nil} + end. + +mldsa_priv_key_to_der(Choice, PubKeySlot, Param) -> + OID = mldsa_param_to_oid(Param), + AlgId = {'AlgorithmIdentifier', OID, asn1_NOVALUE}, + InnerDer = mldsa_inner_choice_der(Choice, Param), + PrivKeyInfo = + case is_otp_28_or_later() of + true -> + %% OneAsymmetricKey version is v2 (1) when the public key is + %% present, v1 (0) otherwise (RFC 5958). + Version = + case PubKeySlot =/= asn1_NOVALUE of + true -> 1; + false -> 0 + end, + {'OneAsymmetricKey', Version, AlgId, InnerDer, asn1_NOVALUE, PubKeySlot}; + false -> + %% OTP 27: 5-tuple PrivateKeyInfo has no slot for the public key. + {'PrivateKeyInfo', v1, AlgId, InnerDer, asn1_NOVALUE} + end, + public_key:der_encode('PrivateKeyInfo', PrivKeyInfo). + +mldsa_inner_choice_der(Choice, Param) -> + case is_otp_28_or_later() of + true -> + public_key:der_encode(mldsa_asn1_type(Param), Choice); + false -> + %% OTP 27 lacks the 'ML-DSA-*-PrivateKey' ASN.1 types; hand-encode + %% the CHOICE. + mldsa_inner_choice_der_manual(Choice) + end. + +mldsa_inner_choice_der_manual({seed, Seed}) when byte_size(Seed) =:= 32 -> + %% seed [0] IMPLICIT OCTET STRING (SIZE (32)) + <<16#80, 32, Seed/binary>>. + +mldsa_from_seed(Param, Seed) when byte_size(Seed) =:= 32 -> + mldsa_seed_key_pair(Param, Seed); +mldsa_from_seed(_, _) -> + {error, nil}. + +mldsa_private_key_to_seed({seed, Seed, _PubKey, _Param}) -> + {ok, Seed}. + +mldsa_import_public_key_pem(PemData) -> + try + case public_key:pem_decode(PemData) of + [{'SubjectPublicKeyInfo', DerBytes, not_encrypted}] -> + mldsa_import_public_key_der(DerBytes); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. + +mldsa_import_public_key_der(DerBytes) -> + try + case public_key:der_decode('SubjectPublicKeyInfo', DerBytes) of + #'SubjectPublicKeyInfo'{ + algorithm = #'AlgorithmIdentifier'{algorithm = OID}, + subjectPublicKey = PublicKeyBits + } -> + Param = mldsa_oid_to_param(OID), + mldsa_public_key_from_bytes(Param, PublicKeyBits); + _ -> + {error, nil} + end + catch + _:_ -> + {error, nil} + end. diff --git a/src/kryptos_ffi.mjs b/src/kryptos_ffi.mjs index 04cbdc6..25e6a69 100644 --- a/src/kryptos_ffi.mjs +++ b/src/kryptos_ffi.mjs @@ -50,6 +50,15 @@ import { HashAlgorithm$Shake256$0, algorithm_name as hashAlgorithmName, } from "./kryptos/hash.mjs"; +import { + ParameterSet$isMldsa44, + ParameterSet$isMldsa65, + ParameterSet$isMldsa87, + ParameterSet$Mldsa44, + ParameterSet$Mldsa65, + ParameterSet$Mldsa87, + key_size as mldsaKeySize, +} from "./kryptos/mldsa.mjs"; import { EncryptPadding$isEncryptPkcs1v15, EncryptPadding$isOaep, @@ -879,61 +888,60 @@ function importPublicKeyDer(der, type, allowedTypes) { } } -// EC Import Functions - -export function ecImportPrivateKeyPem(pem) { - return importPrivateKeyPem(pem, "pkcs8", ["ec"]); -} - -export function ecImportPrivateKeyDer(der) { - return importPrivateKeyDer(der, "pkcs8", ["ec"]); -} - -export function ecImportPublicKeyDer(der) { - return importPublicKeyDer(der, "spki", ["ec"]); -} - -// Key Export Functions -// -// Key export is identical for EC, EdDSA, and XDH keys, so these are shared. -// RSA export differs (it takes a format) and stays separate. +// ============================================================================= +// Key Export (shared by EC, EdDSA, XDH, ML-DSA) +// ============================================================================= -export function exportPrivateKeyPem(key) { +export function exportPublicKeyPem(key) { try { - const exported = key.export({ format: "pem", type: "pkcs8" }); + const exported = key.export({ format: "pem", type: "spki" }); return Result$Ok(exported); } catch { return Result$Error(undefined); } } -export function exportPrivateKeyDer(key) { +export function exportPublicKeyDer(key) { try { - const exported = key.export({ format: "der", type: "pkcs8" }); + const exported = key.export({ format: "der", type: "spki" }); return Result$Ok(BitArray$BitArray(exported)); } catch { return Result$Error(undefined); } } -export function exportPublicKeyPem(key) { +export function exportPrivateKeyPem(key) { try { - const exported = key.export({ format: "pem", type: "spki" }); + const exported = key.export({ format: "pem", type: "pkcs8" }); return Result$Ok(exported); } catch { return Result$Error(undefined); } } -export function exportPublicKeyDer(key) { +export function exportPrivateKeyDer(key) { try { - const exported = key.export({ format: "der", type: "spki" }); + const exported = key.export({ format: "der", type: "pkcs8" }); return Result$Ok(BitArray$BitArray(exported)); } catch { return Result$Error(undefined); } } +// EC Import Functions + +export function ecImportPrivateKeyPem(pem) { + return importPrivateKeyPem(pem, "pkcs8", ["ec"]); +} + +export function ecImportPrivateKeyDer(der) { + return importPrivateKeyDer(der, "pkcs8", ["ec"]); +} + +export function ecImportPublicKeyDer(der) { + return importPublicKeyDer(der, "spki", ["ec"]); +} + // ============================================================================= // X25519/X448 Key Exchange (XDH) // ============================================================================= @@ -1421,3 +1429,237 @@ export function eddsaImportPublicKeyPem(pem) { export function eddsaImportPublicKeyDer(der) { return importPublicKeyDer(der, "spki", ["ed25519", "ed448"]); } + +// ============================================================================= +// ML-DSA (FIPS 204) +// ============================================================================= + +const MLDSA_KEY_TYPES = ["ml-dsa-44", "ml-dsa-65", "ml-dsa-87"]; + +const MLDSA_SEED_PKCS8_PREFIX = { + "ml-dsa-44": Buffer.from( + "3034020100300b060960864801650304031104228020", + "hex", + ), + "ml-dsa-65": Buffer.from( + "3034020100300b060960864801650304031204228020", + "hex", + ), + "ml-dsa-87": Buffer.from( + "3034020100300b060960864801650304031304228020", + "hex", + ), +}; + +const MLDSA_PUBLIC_DER_PREFIX = { + // ML-DSA-44: OID 2.16.840.1.101.3.4.3.17 + "ml-dsa-44": Buffer.from( + "30820532300b06096086480165030403110382052100", + "hex", + ), + // ML-DSA-65: OID 2.16.840.1.101.3.4.3.18 + "ml-dsa-65": Buffer.from( + "308207b2300b0609608648016503040312038207a100", + "hex", + ), + // ML-DSA-87: OID 2.16.840.1.101.3.4.3.19 + "ml-dsa-87": Buffer.from( + "30820a32300b060960864801650304031303820a2100", + "hex", + ), +}; + +function mldsaKeyType(param) { + if (ParameterSet$isMldsa44(param)) return "ml-dsa-44"; + if (ParameterSet$isMldsa65(param)) return "ml-dsa-65"; + if (ParameterSet$isMldsa87(param)) return "ml-dsa-87"; + throw new Error("Unsupported ML-DSA parameter set"); +} + +function mldsaParamFromKeyType(keyType) { + switch (keyType) { + case "ml-dsa-44": + return ParameterSet$Mldsa44(); + case "ml-dsa-65": + return ParameterSet$Mldsa65(); + case "ml-dsa-87": + return ParameterSet$Mldsa87(); + default: + throw new Error(`Unsupported ML-DSA key type: ${keyType}`); + } +} + +let mldsaSupportedCache; + +export function mldsaSupported() { + if (mldsaSupportedCache === undefined) { + try { + crypto.generateKeyPairSync("ml-dsa-44"); + mldsaSupportedCache = true; + } catch { + mldsaSupportedCache = false; + } + } + return mldsaSupportedCache; +} + +export function mldsaGenerateKeyPair(param) { + const keyType = mldsaKeyType(param); + const { privateKey, publicKey } = crypto.generateKeyPairSync(keyType); + return [privateKey, publicKey]; +} + +export function mldsaSign(privateKey, message) { + const signature = crypto.sign(null, BitArray$BitArray$data(message), privateKey); + return BitArray$BitArray(signature); +} + +export function mldsaVerify(publicKey, message, signature) { + try { + return crypto.verify( + null, + BitArray$BitArray$data(message), + publicKey, + BitArray$BitArray$data(signature), + ); + } catch { + return false; + } +} + +export function mldsaPublicKeyToBytes(publicKey) { + const der = publicKey.export({ format: "der", type: "spki" }); + const keyType = publicKey.asymmetricKeyType; + const prefixLen = MLDSA_PUBLIC_DER_PREFIX[keyType].length; + return BitArray$BitArray(der.subarray(prefixLen)); +} + +export function mldsaPublicKeyFromBytes(param, publicBytes) { + try { + const keyType = mldsaKeyType(param); + const expectedSize = mldsaKeySize(param); + if (publicBytes.byteSize !== expectedSize) { + return Result$Error(undefined); + } + const prefix = MLDSA_PUBLIC_DER_PREFIX[keyType]; + const der = BitArray$BitArray$data( + bitArrayAppend(BitArray$BitArray(prefix), publicBytes), + ); + const publicKey = crypto.createPublicKey({ + key: der, + format: "der", + type: "spki", + }); + return Result$Ok(publicKey); + } catch { + return Result$Error(undefined); + } +} + +export function mldsaPublicKeyFromPrivate(privateKey) { + return crypto.createPublicKey(privateKey); +} + +export function mldsaPrivateKeyParameterSet(privateKey) { + return mldsaParamFromKeyType(privateKey.asymmetricKeyType); +} + +export function mldsaPublicKeyParameterSet(publicKey) { + return mldsaParamFromKeyType(publicKey.asymmetricKeyType); +} + +export function mldsaFromSeed(param, seed) { + try { + if (seed.byteSize !== 32) { + return Result$Error(undefined); + } + const keyType = mldsaKeyType(param); + const prefix = MLDSA_SEED_PKCS8_PREFIX[keyType]; + const der = BitArray$BitArray$data( + bitArrayAppend(BitArray$BitArray(prefix), seed), + ); + const privateKey = crypto.createPrivateKey({ + key: der, + format: "der", + type: "pkcs8", + }); + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +function mldsaSeedBytes(privateKey) { + // Node exports the seed-format PKCS#8 on some versions (22, 26+) and the + // expanded key on others (24), so try the seed prefix first and fall back + // to the JWK `priv` field, which carries the 32-byte seed either way. + const prefix = MLDSA_SEED_PKCS8_PREFIX[privateKey.asymmetricKeyType]; + const der = privateKey.export({ format: "der", type: "pkcs8" }); + if ( + prefix && + der.length === prefix.length + 32 && + der.subarray(0, prefix.length).equals(prefix) + ) { + return der.subarray(prefix.length); + } + try { + const jwk = privateKey.export({ format: "jwk" }); + if (jwk && jwk.priv) { + const seed = Buffer.from(jwk.priv, "base64url"); + if (seed.length === 32) { + return seed; + } + } + } catch { + // JWK export unsupported for ML-DSA on this Node version. + } + return null; +} + +export function mldsaPrivateKeyToSeed(privateKey) { + try { + const seed = mldsaSeedBytes(privateKey); + return seed === null + ? Result$Error(undefined) + : Result$Ok(BitArray$BitArray(seed)); + } catch { + return Result$Error(undefined); + } +} + +export function mldsaImportPrivateKeyPem(pem) { + return mldsaImportPrivateKey({ key: pem, format: "pem", type: "pkcs8" }); +} + +export function mldsaImportPrivateKeyDer(der) { + return mldsaImportPrivateKey({ + key: BitArray$BitArray$data(der), + format: "der", + type: "pkcs8", + }); +} + +function mldsaImportPrivateKey(options) { + try { + const privateKey = crypto.createPrivateKey(options); + if ( + !MLDSA_KEY_TYPES.includes(privateKey.asymmetricKeyType) || + mldsaSeedBytes(privateKey) === null + ) { + return Result$Error(undefined); + } + const publicKey = crypto.createPublicKey(privateKey); + return Result$Ok([privateKey, publicKey]); + } catch { + return Result$Error(undefined); + } +} + +export function mldsaImportPublicKeyPem(pem) { + return importPublicKeyPem(pem, "spki", MLDSA_KEY_TYPES); +} + +export function mldsaImportPublicKeyDer(der) { + return importPublicKeyDer(der, "spki", MLDSA_KEY_TYPES); +} diff --git a/test/birdie_snapshots/csr_mldsa_dns_san_openssl_output.accepted b/test/birdie_snapshots/csr_mldsa_dns_san_openssl_output.accepted new file mode 100644 index 0000000..c690307 --- /dev/null +++ b/test/birdie_snapshots/csr_mldsa_dns_san_openssl_output.accepted @@ -0,0 +1,36 @@ +--- +version: 2.0.1 +title: csr mldsa dns san openssl output +--- + N:d=0 hl=N l= NNN cons: SEQUENCE + N:d=1 hl=N l= NNN cons: SEQUENCE + N:d=2 hl=N l= NNN prim: INTEGER :00 + N:d=2 hl=N l= NNN cons: SEQUENCE + N:d=3 hl=N l= NNN cons: SET + N:d=4 hl=N l= NNN cons: SEQUENCE + N:d=5 hl=N l= NNN prim: OBJECT :commonName + N:d=5 hl=N l= NNN prim: UTF8STRING :mldsa.example.com + N:d=3 hl=N l= NNN cons: SET + N:d=4 hl=N l= NNN cons: SEQUENCE + N:d=5 hl=N l= NNN prim: OBJECT :organizationName + N:d=5 hl=N l= NNN prim: UTF8STRING :MLDSA Corp + N:d=3 hl=N l= NNN cons: SET + N:d=4 hl=N l= NNN cons: SEQUENCE + N:d=5 hl=N l= NNN prim: OBJECT :countryName + N:d=5 hl=N l= NNN prim: PRINTABLESTRING :US + N:d=2 hl=N l= NNN cons: SEQUENCE + N:d=3 hl=N l= NNN cons: SEQUENCE + N:d=4 hl=N l= NNN prim: OBJECT :ML-DSA-44 + N:d=3 hl=N l= NNN prim: BIT STRING + N:d=2 hl=N l= NNN cons: cont [ 0 ] + N:d=3 hl=N l= NNN cons: SEQUENCE + N:d=4 hl=N l= NNN prim: OBJECT :Extension Request + N:d=4 hl=N l= NNN cons: SET + N:d=5 hl=N l= NNN cons: SEQUENCE + N:d=6 hl=N l= NNN cons: SEQUENCE + N:d=7 hl=N l= NNN prim: OBJECT :X509v3 Subject Alternative Name + N:d=7 hl=N l= NNN prim: OCTET STRING [HEX DUMP]:301382116D6C6473612E6578616D706C652E636F6D + N:d=1 hl=N l= NNN cons: SEQUENCE + N:d=2 hl=N l= NNN prim: OBJECT :ML-DSA-44 + N:d=1 hl=N l= NNN prim: BIT STRING + diff --git a/test/birdie_snapshots/csr_mldsa_dns_san_text.accepted b/test/birdie_snapshots/csr_mldsa_dns_san_text.accepted new file mode 100644 index 0000000..102d1b0 --- /dev/null +++ b/test/birdie_snapshots/csr_mldsa_dns_san_text.accepted @@ -0,0 +1,108 @@ +--- +version: 2.0.1 +title: csr mldsa dns san text +--- +Certificate Request: + Data: + Version: 1 (0x0) + Subject: CN=mldsa.example.com, O=MLDSA Corp, C=US + Subject Public Key Info: + Public Key Algorithm: ML-DSA-44 + ML-DSA-44 Public-Key: + pub: + 3d:7a:ff:96:3a:06:29:b7:2e:69:e9:a9:f2:09:be: + 05:16:ad:d9:ba:8a:a7:08:36:87:7a:98:a0:01:03: + 99:a9:11:86:e6:66:bb:30:79:d1:32:b5:c9:b8:5a: + b2:fd:61:57:5e:01:5d:53:24:2a:3e:02:6a:51:56: + 55:41:a4:56:81:c1:5f:39:87:4d:62:f4:d1:a5:2a: + e9:89:28:23:ef:92:48:bf:4c:e1:32:fe:8c:b2:c2: + 2d:79:46:42:fa:26:da:48:f5:49:1a:f2:d3:c0:ef: + 8b:b5:ff:0e:87:11:aa:69:54:7c:96:b4:b1:e5:d8: + 99:29:c6:63:cb:14:17:a3:30:50:05:55:05:95:af: + 31:70:f6:34:3e:ca:6e:e8:43:22:79:cd:7a:88:01: + 89:4e:1d:32:d2:da:fa:e9:1d:86:95:1d:c5:ea:66: + 03:7b:28:c6:9e:35:2d:cc:8f:63:1b:a2:8a:a1:19: + 68:7f:90:00:cb:37:3f:11:30:7d:9a:99:7d:0d:f3: + dd:af:6d:34:d1:9a:d2:19:52:cc:c9:f9:7e:ec:2f: + 22:7f:0e:f0:27:3b:d2:fa:52:31:0a:bb:ef:b7:32: + 01:f3:58:13:4f:c2:cb:02:2c:13:de:97:04:dd:97: + bd:90:f7:44:04:ba:f3:03:76:e9:da:80:88:fe:e4: + e3:77:53:56:09:dc:b5:eb:4c:8c:b5:e8:7e:7d:a0: + 5b:00:42:7d:02:09:04:b8:74:58:56:36:f5:6e:bc: + 85:14:c1:3c:8b:ad:c9:b5:c5:fb:ef:51:06:d4:0a: + f3:91:60:af:03:8f:9f:76:70:94:4c:50:3e:60:2a: + 03:4b:32:fc:db:58:66:bc:bf:a4:a2:0a:77:28:2c: + 47:c3:b3:cc:ac:d8:8e:60:29:79:db:22:27:9c:3c: + fb:45:02:d2:f7:a4:a8:60:0f:9a:bc:47:c8:0a:51: + 44:75:3c:aa:f9:88:25:01:f9:3b:36:5b:de:90:a1: + 18:e1:73:ef:f0:d2:0a:de:31:6e:cc:38:44:c6:40: + ec:8a:7c:7b:60:59:cb:2e:0b:70:a5:dd:1a:75:a7: + 42:d0:fc:d4:e5:04:b5:c9:12:27:80:ea:26:64:20: + 11:30:c5:e5:db:db:30:43:2e:6b:ce:ed:af:7a:20: + 50:9d:a7:c4:01:25:74:3f:1c:a5:d2:83:16:30:8a: + c4:4c:85:8c:d7:99:10:8d:56:e3:ae:9d:5b:4d:4c: + 1c:9f:97:3f:6a:e3:b7:25:96:73:ff:fe:0a:a8:cb: + cf:55:23:f6:f5:51:bb:62:fa:ef:15:04:d2:b8:b6: + 3e:29:ee:4e:e7:0d:93:95:45:13:72:cd:95:6f:50: + 97:61:aa:5f:b8:06:b4:a4:46:fe:11:df:bf:ac:a5: + d5:5d:d7:2c:c1:f0:97:3b:13:c8:fb:0c:51:10:63: + 5c:c3:ec:92:4c:78:64:69:8b:44:af:aa:73:e3:24: + cc:30:84:4c:3e:70:16:85:9d:86:d6:64:c5:c9:91: + a1:e6:61:be:c3:50:18:96:65:52:80:7e:98:a4:d8: + 1e:8d:a3:ff:09:4e:ae:86:8f:5d:93:1c:f8:9d:c6: + 2a:e5:38:11:b2:c1:c7:8b:1a:9e:62:f6:c8:a5:c1: + 9e:bf:70:8b:6f:c2:47:96:15:62:4a:4d:3e:72:5b: + f3:66:db:cc:07:10:37:74:8b:85:42:eb:94:9f:6b: + 00:cc:af:18:9f:ef:1b:c5:90:7e:3d:59:23:6b:f9: + 87:9a:4a:f5:13:91:55:b5:9d:ea:54:95:8f:3c:1a: + 43:39:4e:f7:e2:d6:d4:a5:38:ff:f8:5d:41:dd:d2: + 74:64:b0:4e:65:ed:fe:49:c0:be:91:52:13:03:c3: + 51:0a:29:68:4a:34:fb:7b:98:fc:4d:fa:fa:fe:b3: + 48:ec:07:4f:95:29:b7:d6:4b:ef:92:81:31:34:23: + 0b:74:25:b3:97:65:c9:c5:46:13:bb:c9:e4:29:b1: + 15:e0:ba:a7:3c:7c:ed:21:2c:81:c6:aa:a9:ba:96: + cf:29:53:ea:60:7a:78:98:2a:d3:49:0b:a6:4a:ef: + 88:be:2c:ca:44:c4:cd:fb:b0:06:a9:d6:1f:82:a6: + a4:76:59:5f:9a:ae:b2:fc:2e:eb:69:02:10:5d:b8: + 0d:43:e4:d9:d3:af:c2:3b:ab:b8:28:bc:bf:46:d7: + ad:c8:c3:a1:bb:62:a6:2a:de:fc:bb:1a:b0:8b:f5: + d5:46:5e:31:5a:84:0a:bb:6c:41:3d:b9:7b:cd:72: + 98:f3:3a:4f:79:2a:84:20:53:18:e9:c3:45:3f:a8: + 3b:71:09:7b:6b:46:56:68:06:db:2d:6e:9e:ef:89: + 70:a1:3a:82:8c:be:55:87:be:5d:35:8b:df:7b:60: + cf:34:5f:d9:6d:13:6f:42:68:87:1b:47:c4:9c:3c: + d2:64:63:ad:06:68:69:d6:0a:e9:8c:fa:38:01:56: + 65:ac:dd:7e:45:1e:ad:3a:58:40:1b:f1:af:99:7b: + 17:c1:07:46:34:3c:56:8a:9d:84:17:c8:42:a1:08: + 47:40:26:0e:23:34:0c:93:9e:5d:74:77:55:78:d4: + d6:50:22:55:ab:f1:4b:24:85:d8:bc:a8:ba:b4:a9: + 5b:4b:d1:24:9e:d6:ff:9e:73:72:a6:e9:42:eb:03: + 63:3b:5b:e9:9d:55:54:64:de:eb:8d:bf:a9:8d:37: + 51:a1:0b:de:26:f1:85:6d:25:3e:7b:1d:ff:4d:a2: + ba:41:ee:59:3b:d5:b0:5d:35:2d:ad:8a:33:ee:a0: + 4d:88:37:c4:29:9b:23:44:9b:fe:58:31:f4:cb:53: + d2:3d:b0:1e:f1:f9:5b:2f:69:8e:b1:80:8d:39:f5: + e1:29:8e:8d:3b:3c:be:2f:7c:6b:5b:61:aa:a8:26: + 9f:f7:e2:ef:14:6c:f5:9d:23:ac:f8:bf:45:bb:23: + ac:bf:67:02:df:4d:30:8e:b8:16:40:89:d9:55:e7: + 24:45:07:3c:d5:af:bc:d6:1a:2b:f0:4b:0d:95:ab: + a3:55:00:2e:88:ca:3b:4d:0c:1f:d2:61:3d:5e:ea: + af:82:43:d7:84:b6:0a:e1:31:53:ed:2c:bb:d4:16: + 6d:9a:6c:b6:ac:bf:56:19:86:ed:d9:8d:5a:80:41: + 17:10:e1:fb:0b:e7:d6:0a:bf:0c:34:d6:a4:08:23: + cb:30:16:f5:f3:49:25:d4:5c:b7:15:d2:5c:fc:e9: + a5:a8:69:ec:21:a6:e1:51:95:7c:b0:1b:9f:98:5b: + 95:f9:fd:cf:86:6d:9e:ab:99:c6:3c:4e:6c:9b:b3: + 50:17:32:94:0d:e1:d2:32:66:04:6d:8c:8c:10:95: + a5:87:ef:20:99:4e:7e:4d:ea:fd:5f:67:3a:9a:dc: + bb:ff:cf:a3:e7:dc:a0:c8:8d:5a:27:81:5f:04:9f: + 7f:79:3a:26:c2:92:54:0d:98:8e:f1:ef:4f:80:60: + 11:cb:05:96:91:3a:8f + Attributes: + Requested Extensions: + X509v3 Subject Alternative Name: + DNS:mldsa.example.com + Signature Algorithm: ML-DSA-44 + Signature Value: + [MASKED] + diff --git a/test/birdie_snapshots/mldsa44_public_key_der.accepted b/test/birdie_snapshots/mldsa44_public_key_der.accepted new file mode 100644 index 0000000..4f95e60 --- /dev/null +++ b/test/birdie_snapshots/mldsa44_public_key_der.accepted @@ -0,0 +1,7 @@ +--- +version: 1.5.4 +title: mldsa44 public key der +file: ./test/kryptos/mldsa_test.gleam +test_name: mldsa44_export_public_key_der_test +--- +30820532300B060960864801650304031103820521003E5CFEA56BA30C9FB122C8E47A1C694271367F818CF8B79139ECAA8213A21E6F7A840129954C8F92D0179AEEAA6D0C631D392858EC150109850E51D9CA8E30ED80EA8C00C2C3B1C4719AA9F931B1DE571967AF1E4DD42DCF19672EF895A4FAD1BC79C70AB4D18ADB5FE17DC72F0DFE69B3CB21D77296A60172C2E89A2A62E0701BDB5C3D974B73A16034448847A5D6174C71D597268E5A04B262871D886804F7A6D8A455EDC2D50D44565F05694F4A5B7EAF358A397562F5478517DB10E807F8E2C285DEB09EBCAC6B2440D4ADC5A9B5E8F6D38B27BB2E16BC37D02BA62452CE9F1E60836DAA1E0A9190CC97DC6571465B497ACF1A94811B52F6CCE000890ECB2C4170BB21C9537236AFEE189C61C94AB76C71A76BA9AC67CE02F8A40A5B69C71397A2E26FE2405E4A6F79C154FE68853A613838FA602035CDEB54C7349E7BF7CA300ED9CBBCE022EDF797E353CC37FEEC53BD941EFC9B5661E4C3C0A9154133AD0F543EE03EA7B5DF91D6014C38695A67C35A4748191E58FE55CE89A8DADDE7B7975587A93EF5F90B10092541C4B324410DDDA3906E9DB5FE7CB8EF24D897A242B16D752FE569607316AF144E16B5EF4F50C8D554CD98BCB0CDB48D1EB91671148EEBED123FE9CB3392AAA3852AD157FC39E3D7BF063373C9E80247CB0C43405A963573BF9C304D2F32E81CFA00FFDF7A88D695D753F3A6911AC6BB27B42A683B5B77BD4512C62BBB0E2B0ED06F24B7A88FBFBF9E4480DAC499A04B2A2357FA55049F05F9879DB0AF1F7C7C0B7D7E9761F7945EE68CD080E7B1288E56174DFA7219F2840E92A827E362E93DDE6C686ED686CE97141199C730C92A031BD05FCED197142F7A43AAB909E30A5B2815A4C7EAEFC99259489F0F0C0851CC7CB63B9BFF1000D6712B3C0D46B2798634FFFA9FF99BC7502116E0FF9EB7AA5854AF9C341B0EFEC3191ADBAAFEBD43403451C3A8AF1A93D3BE2089CE054AE60B99CC8B51DA86A7D6E5C4E13921B95DB9B4F8C6905FB45FF33A7302ECEC664EFBCC7A44E724AB9BC3706B2F1B84D24C89EB9306E1F593D30284D4479654E92D203A86B69E7805A1460A799048A69796E9BE4811FED95F7D3B67D59DA01252F1E8D20CFAED2BBA5603BBE4A0B003DE07578B74DA6FE97D55267336F9134844086C5B0F32EBFA68D7B079124DFF0A9DF688FBA0017FF9740DA6E767524224AFCA5A0F4873A36211092BDE77DB83BE007E593FDD36D5BD2763CDED52ED8F7648CE108AAB8BF95732BC4EACA08E70851D8A0F89BB3D6FCED02C5446BA52EE4F5509F30382CBF536CDDBB467DEF6E3D13982548DD263A5ED8D54B660C58E0BD4181942B7339E85D3687BC48A94597E4E1F2F2A3E54DFD23D9BAB31B4C55F436D1C587FB98B211BDD9EE12449CC5BBE0D94DC50B7E1F8DC119FF9216AB40E1DEEE776B99E26970A2382AF15113DBC0A31CA6FBCAA513F4A2A255158BE3D24AD6F06E253173A78A593BF7B045CE3FEE533A948DDD4CAB5D2A3A8F9DD4C774D092E94F330A6452AFD55798435A8BA509A2C172039322FC92DB9D7A47715A046797C57DCB79BB98AD284993BA908999B2CE45DD9DF368C1C18281E619782CFC0EEA38C6F80CFC557C01D60F62B3AAC453906F57573E53EC480D2A983461E01A51AAAB06AA099447693E9981DC46CC6BA78659020E035998A2B2519AF9127DF46A7ED8CA8D3804DBAAB5568B0CB95261B9BAF575BAE576B98CB353D4D592B49D3A81A75A679C52C88B9D1952692CE22D3A2B55190B7A2B30464F9E05CAD87CE47748FCFF8E35E7EC8211D7173516D25CF956FB955FC8ABE119AAF53E9064B4C5D3D9829C64A80381057 diff --git a/test/birdie_snapshots/mldsa44_public_key_pem.accepted b/test/birdie_snapshots/mldsa44_public_key_pem.accepted new file mode 100644 index 0000000..5fd193c --- /dev/null +++ b/test/birdie_snapshots/mldsa44_public_key_pem.accepted @@ -0,0 +1,37 @@ +--- +version: 1.5.4 +title: mldsa44 public key pem +file: ./test/kryptos/mldsa_test.gleam +test_name: mldsa44_export_public_key_pem_test +--- +-----BEGIN PUBLIC KEY----- +MIIFMjALBglghkgBZQMEAxEDggUhAD5c/qVrowyfsSLI5HocaUJxNn+BjPi3kTns +qoIToh5veoQBKZVMj5LQF5ruqm0MYx05KFjsFQEJhQ5R2cqOMO2A6owAwsOxxHGa +qfkxsd5XGWevHk3ULc8ZZy74laT60bx5xwq00YrbX+F9xy8N/mmzyyHXcpamAXLC +6JoqYuBwG9tcPZdLc6FgNESIR6XWF0xx1ZcmjloEsmKHHYhoBPem2KRV7cLVDURW +XwVpT0pbfq81ijl1YvVHhRfbEOgH+OLChd6wnrysayRA1K3FqbXo9tOLJ7suFrw3 +0CumJFLOnx5gg22qHgqRkMyX3GVxRltJes8alIEbUvbM4ACJDsssQXC7IclTcjav +7hicYclKt2xxp2uprGfOAvikCltpxxOXouJv4kBeSm95wVT+aIU6YTg4+mAgNc3r +VMc0nnv3yjAO2cu84CLt95fjU8w3/uxTvZQe/JtWYeTDwKkVQTOtD1Q+4D6ntd+R +1gFMOGlaZ8NaR0gZHlj+Vc6JqNrd57eXVYepPvX5CxAJJUHEsyRBDd2jkG6dtf58 +uO8k2JeiQrFtdS/laWBzFq8UTha1709QyNVUzZi8sM20jR65FnEUjuvtEj/pyzOS +qqOFKtFX/Dnj178GM3PJ6AJHywxDQFqWNXO/nDBNLzLoHPoA/996iNaV11PzppEa +xrsntCpoO1t3vUUSxiu7DisO0G8kt6iPv7+eRIDaxJmgSyojV/pVBJ8F+YedsK8f +fHwLfX6XYfeUXuaM0IDnsSiOVhdN+nIZ8oQOkqgn42LpPd5saG7Whs6XFBGZxzDJ +KgMb0F/O0ZcUL3pDqrkJ4wpbKBWkx+rvyZJZSJ8PDAhRzHy2O5v/EADWcSs8DUay +eYY0//qf+ZvHUCEW4P+et6pYVK+cNBsO/sMZGtuq/r1DQDRRw6ivGpPTviCJzgVK +5guZzItR2oan1uXE4TkhuV25tPjGkF+0X/M6cwLs7GZO+8x6ROckq5vDcGsvG4TS +TInrkwbh9ZPTAoTUR5ZU6S0gOoa2nngFoUYKeZBIppeW6b5IEf7ZX307Z9WdoBJS +8ejSDPrtK7pWA7vkoLAD3gdXi3Tab+l9VSZzNvkTSEQIbFsPMuv6aNeweRJN/wqd +9oj7oAF/+XQNpudnUkIkr8paD0hzo2IRCSved9uDvgB+WT/dNtW9J2PN7VLtj3ZI +zhCKq4v5VzK8TqygjnCFHYoPibs9b87QLFRGulLuT1UJ8wOCy/U2zdu0Z97249E5 +glSN0mOl7Y1UtmDFjgvUGBlCtzOehdNoe8SKlFl+Th8vKj5U39I9m6sxtMVfQ20c +WH+5iyEb3Z7hJEnMW74NlNxQt+H43BGf+SFqtA4d7ud2uZ4mlwojgq8VET28CjHK +b7yqUT9KKiVRWL49JK1vBuJTFzp4pZO/ewRc4/7lM6lI3dTKtdKjqPndTHdNCS6U +8zCmRSr9VXmENai6UJosFyA5Mi/JLbnXpHcVoEZ5fFfct5u5itKEmTupCJmbLORd +2d82jBwYKB5hl4LPwO6jjG+Az8VXwB1g9is6rEU5BvV1c+U+xIDSqYNGHgGlGqqw +aqCZRHaT6ZgdxGzGunhlkCDgNZmKKyUZr5En30an7Yyo04BNuqtVaLDLlSYbm69X +W65Xa5jLNT1NWStJ06gadaZ5xSyIudGVJpLOItOitVGQt6KzBGT54Fyth85HdI/P ++ONefsghHXFzUW0lz5VvuVX8ir4Rmq9T6QZLTF09mCnGSoA4EFc= +-----END PUBLIC KEY----- + diff --git a/test/birdie_snapshots/mldsa65_public_key_der.accepted b/test/birdie_snapshots/mldsa65_public_key_der.accepted new file mode 100644 index 0000000..de1db29 --- /dev/null +++ b/test/birdie_snapshots/mldsa65_public_key_der.accepted @@ -0,0 +1,5 @@ +--- +version: 1.5.4 +title: mldsa65 public key der +--- +308207B2300B0609608648016503040312038207A1002291017A6459AF5E006567F8D394E618CD8599291F42D4B84949F3062120BF53F23B33B5CA8E24A6A5DFF3EC57B02DE7165239E3A48D48B1BE6AEEE8E1377A81B0BFE79CF2FCB7AC1F824D7D95E718CABE2B6F3DB33ED371D636C882F1C6E771BDD5490D638DB118198C80ED2C8CC9183D7ABE414FBA70D694E43C2177A07DB863ADE3C86860CDC5AB2EA89B62D11EF592E69467E3F29ED55F9D882DB72481061E8B8CA3C39EBA56250BB4CDDCD7D1C3D4DD4895DCFA0AE5F9F5E3E50F1CB88632AAD1C586651D09F677A4F19CF6F6FB354A4A1D9AE39AA48C14DFE4164947DA29CCC68C3FA743F9096ACEB4EF44176C46518AAE1E084962E73E9453584E3BC621C1A4C4E0D0ED35066BF4D4A06065C6D3D5B090F33413E73719314A9D9F1C32935ACAB885A3E7E4C3184F2ABDD2C3DCBBD3E867040C654FB4493A44C4C375EF9CAD977DB3BEE33E92B1C36769BB77A36CD745481A2DAF0FC4385A1C7AB3E848576C42C4767A79517E02D405975888B56251291342367D2228E21834ADE2AF1B4C5CAB12106158C8C84BBE2298FB30B20376053849A76CA01DE892F92C5CA66A180B90B5F7FD655688C904B62B6783B702A706332858353EC98ED6A352D2A46FE0BC3EC3B72BD1AE3C48EB8E49E5F160B28425EB10EF08C321094ED1F7F190F957AE3740C1A5030F949ECA8D78CBC40EB24261DE85DF05330C63C4586EA7ECB8E246715D784FEC82B7C7D6E7F2D46B79713D9E803F4A6DC0866E8E023F0DEF4D70F91A8BC677EE83518DCC546DB79979B6658AB4A7013DDF74E2072264571437113D0FBA4ABE442BFB0EA4BD841D75F8D6F29F927224C77EE4A5AF15D1306D6CC2A416BAC04D462C7BA40F2EECF977D9CAA9C6750F0DE2F075D99BECF5FED570E148DA86936351ED86753A934D58FE46C894EEEBA2204948CF24D5CC4F635868F74DE3B2A79CFA07CDB712984EDF0F4089E09130F2E04EF5934D7A52C93E5B676AD61B45FEFB98CF933FB933E7F0D39BD946291475B57C2D13CE80AC665E58CA3284CE307C4FFF653A5196543151D76D6F33F1AACC06E3D5BB6F89BC3484B181670CB1AD6988C511CD85517A87259FEA354D442FFF196E722A2DA953B20FEDF2AE7FD12997039FCB947A723923CCC8EC901E0FDA65EF0971931A9A615CB28D39B7D5ACF1E87E1745F111BBC7723DC9294756E2363F248EC01B7F1DC1733DC2B56179BF0A9875C2319A6791AC1C4910E5354932FE0183AA3633BC710DBFC2C01C7B2177ED6EE066C19E2A12077CD3EE17E3BAA2C832B7926A6F38F5B64D4DF6D8B74E16AE328EAAEE5326D25CCF540F2A52CE032ABDF4001F994A2E42CF6F53DB233AE6E239F9B6AA7B52DD3AB3D84151298E9DE2D5FDD2F845748941796FA4C5A049787ACA416076774EA71DB92949EC733445FA68ECE39C30B41744DDA3F1237B4A349788FEE9F63EC0C47AEB7C51A62A501AAEBEB61D4851758FE57AEB9944D459AFA815F8C23D818976659FDC33166CD27AA1ADD034552E7C2FCD98EF10EE3C0A327B00A8DD3D34686BE7FA9F3856B095132AF5B6232CEB9C92A3A30F6B936C1D2A6E27377120A0BEBF5ACDF95DAD34BE55B2E9124006677A023548C9A62F8A79C74247E55D20D114B504F4C374912DB1AFD3889E1281E3958A771DB039F783810851A5625C8D8F57E558E7DCD901010FD9BB35E566616FEF11747B0A34F91815536AE127ACB9C0BA4095404B42A6E3309C065C835405791EEE8DB6215172DF539950465C7109D61256A324B95F0D64B1F0064A5284DB38488FB74209C5361180B17DDE6897EF6825D399B28093CFF4085009EA15BB41A020FC2790E3263E99E83E18D03965DF61CFC9EE8F71AF75E1AC38F1B170F9EE7820168D5F524C68A44B657CE88C509ADC971D5362B2A6312FD1C778F690630C6C841AD3385F98E4880B7A578B73E24959E69EF9490005D8E88E0A5D47E0DF4221721B9D28C9BCABDC4C73CE616C9725F5AA3179AE02C8A4E46E07E57B7AA07965E2A07C7D4E0E130BCFA4F1A8D143F3CC51D391768D2BD51E9F186E6B1B094999F47962209ED25CE07AC4EAC9A5E25516D384542A29700ED5A281B5B96C635C132BB0A82A60FB8C35EA695A55F094F3EC14BA9DFDEDE0BADD3BC81E37E782D4877B2A8A953451BEB728A4D68E9C23B15C9AEF31FF693456898F2B042D51EBC1D4B690306F5A4760F5DDE291675BC561B8B9D781629CBCF4C69906D544BA5BFB698845BF652BC4F8F454DDF9631ED88D66CEF5D768547777730B05D9D4D5EE1781EDA91D02553198C62EA545B1D5AD3F3F1894F2C17545C7C07B8E1A79AE29FC5256508D7D1AB65985ABB4888EA4D514582C60AB257AB78683C9221A397C6B94745B6F948D586C2FF758567AD92EFC3BCC7EDACAAC78A3824A349EE7FE684562769542C27F6463A8660857541179398895522E5E72E8F69C728DF55AE1B72777C8119A1C1D930D1653C4E4C1F1D9E370D35C4C4AAEA780D285AEB22C962236D4C45E6705E59078479546475189EADE57F793D8428EA4D021E4C3699EDE5CF1B33C324FAEB71B69BE7D28554952F2418CBD7F1239C205CB19BBB2DF3CCA1C86A18C97C6871DA8B78BA174015FDB8CBF6B5D463865CEBFF2752781458B5FEF1EAFAA21A114B89475E060D971F3710EF4E9CD984EABC0D1E5571712CAEF7354DFB837722022F6F64BE9934E3E5E650A1E8F9D8F6441CC36E62DBF217CBAEE0168035E5BC23B32789DEB diff --git a/test/birdie_snapshots/mldsa65_public_key_pem.accepted b/test/birdie_snapshots/mldsa65_public_key_pem.accepted new file mode 100644 index 0000000..5278ddb --- /dev/null +++ b/test/birdie_snapshots/mldsa65_public_key_pem.accepted @@ -0,0 +1,51 @@ +--- +version: 1.5.4 +title: mldsa65 public key pem +file: ./test/kryptos/mldsa_test.gleam +test_name: mldsa65_export_public_key_pem_test +--- +-----BEGIN PUBLIC KEY----- +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 +wjsyeJ3r +-----END PUBLIC KEY----- + diff --git a/test/birdie_snapshots/mldsa87_public_key_der.accepted b/test/birdie_snapshots/mldsa87_public_key_der.accepted new file mode 100644 index 0000000..08fd9c5 --- /dev/null +++ b/test/birdie_snapshots/mldsa87_public_key_der.accepted @@ -0,0 +1,5 @@ +--- +version: 1.5.4 +title: mldsa87 public key der +--- +30820A32300B060960864801650304031303820A2100D113614E8990D450384B8F9FE12F6785F3C92F1BD5857A8545D2F93A450F8303F38757DF7B3941A6DB1A0C3FFEB1A2A1B0B5FADD2FC5623FDFCEF28B91702BD0CA9757B7CFD8BE8560143CB96919D7EC060D4162282E9441D9F0F4E6B6AFD5ED395A29B4C5A7427C1954D9C24AB1F4D7E21B42698DBE2D5F3A9F8CD4F303F1B71AD5A9854B60EFB92089849D3A9B90F8D07D062E4E8BF0953406FF23C44A1E245E2BF0DCEA6DC5EA2F6A9B18D7C431333F5F5E0CFD25BD0C647AE97608ED05DB1109AFA2ADA9AE26E2265ABAB66AFA7CEC1F8A9E8814DE9C1EC3046902844AC3F7B6765CCBD6579B97659900C767BA966DC5594486C0C6D7CB183DAD4D40368A33A940ACB34E489EECB7BB62BB2DC8C787939073B61E1D98BA27C133CBAF45F491D09EC64F532E4F88DF6B045ECA686A4832D4826C48CD94C39CCFAF1F217078E76D196C4B766CA7A551C0D3BD140E6CE7B3BDA821BB69B0FCE945E22F0946BB6955C1DE6067AB634BDF7D55F997093BD6447BD8C0454E577468257FDDE37C929915080A4D8BA96A57119F33B69C2A021A775652CCF35B15027171BA8D971B110B67D3331C1A7EDCD64355B396C9BCE8ADA6F662A1C87FB24F416D9324EB3BACF18F6C35C913D4889157CB50E9815264B99F0DF5A698B9892B12F6863CC38045A4A1C47E18C620FA2D1DA227BC274551B39359F76CDEBB30959C0C9BD4604D4746D21A9561A7A85FEEFD2913EDBB8F9A5D4BAE0666CA484AEF1E2DF2D7848DFF9B4FC5712BD03B4EEB67DD7F53F771B8AC319FCECBB71F9D66B3ABB6D08245FFDB91E6C2B76659F8F29303D519861B06CEDE760999C7587811999436F388D34B17AF0F1DE95D069DE213622400B55198DFB5621338929CEBD72FC793764726B2E4E5618A5AEA162D86B3E9C055198BDBF5DA0B594FD9C12D1009EEA4023FE8106DCD11A38647D870F3DC2BF8950BD868793477BF81646EDFB8E48748B87DCF9B9612B83DEE390DDC2C4CB5C75456827EF0A38099F8549F59C67C5902D0B6582A7677C0388FDF22CD8B53155DB87EB556164F4090E4684110252CD196ED7C700FF1802B29F9C9EB345F485026EA3F43B190DA7223E2C4D8F67F0AEB26327EB10E7823ACA8D487C81D226CA603C243473FCE9E060E30CA833D0D2A3E52BDD9373731C272B3EEBA377245D7B0694EF123656E90C21ADC862CD84DE360FB76D2EDBDE8BCA2738A439B115AFD99B63353B9E174C44EDC9EB25D43141E696F89E45D200DE3DF226CACD8FC87CC77F3056D6FCC2691A52747C53F7EAD2C16655C08A42AA558693EAF3F311FB139BF1E3ACC36EC4837B2B3F3D9CA4420BA3014789AE444E1E7293D53317B196A7DF2A1509402FF987D8648995AD24D2CF514CB8AF8BAEA7704FD62AEE0714D52B3BD1CDE6885395B7D829737280B53706401A960768836DEDE8790E19A50F648557AA4B5CB08C144D6E963BA847FE4ABBC043E0E3F6B8F86F05BD1070E7AC343396B7B09FAEC0D45421B8278E59410129CF10F263AFEFAAD92952D863945B5F62B96E52FEE1277C181986A36931922E0F56AC40E8721B9715134EFC507D4B128765C4A6A2E7FCE5378F2F54C1654375B9AFB1FF18FCEA5F17ECACEF992D7B9DE30B1A30F926A632804B7EC77C43173DA8E8490A12AB6470EDDAD176911F1A534F8E7702F5E7B71412FBD7199613F1341344C30BF662136B6BFD693C14B22CBD813B004EC15A9445475E746EF28B319D29C32BD3399D3A9128C29998D561C81C91510F1CE696877D867F99688ECFF98C15C512425A9E141C32EF1E845EB6FE44D0E3F3B09E25D00DF9170E35D3CBB33E8C69DD9B30C4221C3E062B04112B9B1963310C0607041D83DFC4821E795BDBC75420A13762A3183D891C65123DF6875E97A5A5D651812C03883D6A030DC8168A5D51B571C501EDF3EA4ACBC553E9CE87813D12A7F318ACC69B46537D69036D8A80EE681201FD74413E609B818EFB7711EF8882D0559CAD9D3155FF1BBE745989DDBAE3D975586A157031618EB6038FB0BFA387AC4DD2BFEF4D74AE192D33B1CE8A18671088FB195DD218B5DD916DCC38EAAF3F28DB9B9F57EE1DDAAEBA7810527B527A8D7EA8E5290B91B8AE81D3B5C13EFA62C0341DF56C7C8A42FE89928518B4A9237CBE75CD60B98E1FE4E3891597E970FECE008248ACC2DCEAE4B111CFA1B23D1C14789CDB67E3D06934FA9158BFD1CE559214AC2B9848B901EC157CB5483E10E69EE85ACBA9272EC16A46C75207A71F7BAF8E6004A276F66AA26315DED713111938E4984D026CE7B9B11BCE51752EB19C428D2D1B7706CA04329955D794360FF2D753425EA674E7E89B7344B20E61E6A5DB8A795541342A379A4A5D9E7167D18D7732FB15BB590DF0E3B1E9CDEBA782238601F86857E83D45628854DEA1D6B2B3453CCD6BD8DBE8A47BC688B1EF82E5AD4583538FACDDA4D8406656931899DEEBE1DDEC92B45AC60B2EB23D57A8F6C658F9EFDC80336F6B54642C75BDFFD55A51F5B66C3923D4A0E83EE0EA53B8A28EC6292247A43DF3743A8AABD79FB8E65132FBCFEE5041A3BC1F1AC6061BC7589104764876AF3F344537184DE3015A4F2B7A8852BFB6DA3AF86702A49643EB3587D4A930D5C5F9EC3A19916DBA99243B48C04F5E2BE78D8946203919868BDD49F7D2E0134DF1E103EE6C70A5D5F63551F852DB9C5C3C0DEF20F268898ABC4D4D94AAEDEEB60B0151F21D4E5CA672B78C0D93587A443808700C0076228C4E2A11F69DFF87798B9B3B80E14C7FFAE478F4D968E96745FE19C3D4608A9F09A5269A820A5309C1DEEB65759394CACE5DF0B86C640F3CC0EFBBD70E66244199F3BEDDE77C0A7D556D191B4B14E3AE9ECA9BBA01D66EBB9357A4181375776B7969507FA15B51CAD006D37D1BCEFE6BE8447FDDB07336FC9AF6FF66E74931F526526F0C471D6BDBE14DB4918ABBA7999B48EE487DBAAF51AA514C0CE431BEDFFC8A8B4727B8631947BDA6FC9A3F45C7104F091A48271870C816BD1848BCA6D9F2745BABFA33F746389EA085D8DD0873B35DAD7EA5F4620885E208F996A059FFC0ECF34B7B952A3C3911AFCD9A3EC804290475A6489D35DCF5B3A24C35A9A2AB5026167B9F20CDA9810800F06C8C42EF22E928810538F1A257A0BF842996F5C0C6A3225E847357ACF6CD5A3677DABEEE436301B22E6B6CFF6E2497B68DBB0FE86CC1F2F56135525652A99D42B3DC7B2EAB72A7B0055ACFEC031E640D29C2EDC976F1F6D53160743EFECE90A0FD6252D0D9F655E493C21FD09F5EF2A5821E4943DD76C938ECF193F84EA1B4510E7C4BFD10F8A491A246F5FDB250E01153B10A64430379D527F750D57DD948B52C0D3A78E53F08C6644F093669324466A557E809C8FF52D96D41BCC8FDBE8BB00032833FBE9D75BFD5E5EE251184F52DBDA2A6E5FDBD8D603A5EAD1B47A7DD41B866E9CAAA065C692F8B762E3C5F65560F6CC2223D6C4139463391819AD60486081F1BD0778CD84EC94A97BA6C26DBD54B6B95765266AA19A513F6F07A4AB7E5325238C473522E4DA3BAA0999B553C087F8D86D1DF052BBE79398EE8E5961D82240F0BB0F300EEDB851798D0919A3E9BF02D4B1F2E7AE291BB68DFA2C961C3B07217087B8A47E285101692D5D79E7 diff --git a/test/birdie_snapshots/mldsa87_public_key_pem.accepted b/test/birdie_snapshots/mldsa87_public_key_pem.accepted new file mode 100644 index 0000000..1f6123a --- /dev/null +++ b/test/birdie_snapshots/mldsa87_public_key_pem.accepted @@ -0,0 +1,64 @@ +--- +version: 1.5.4 +title: mldsa87 public key pem +file: ./test/kryptos/mldsa_test.gleam +test_name: mldsa87_export_public_key_pem_test +--- +-----BEGIN PUBLIC KEY----- +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 +to36LJYcOwchcIe4pH4oUQFpLV155w== +-----END PUBLIC KEY----- + diff --git a/test/fixtures/mldsa44_pkcs8.der b/test/fixtures/mldsa44_pkcs8.der new file mode 100644 index 0000000..d056e60 Binary files /dev/null and b/test/fixtures/mldsa44_pkcs8.der differ diff --git a/test/fixtures/mldsa44_pkcs8.pem b/test/fixtures/mldsa44_pkcs8.pem new file mode 100644 index 0000000..d999c25 --- /dev/null +++ b/test/fixtures/mldsa44_pkcs8.pem @@ -0,0 +1,84 @@ +-----BEGIN PRIVATE KEY----- +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 +mq9T6QZLTF09mCnGSoA4EFc= +-----END PRIVATE KEY----- diff --git a/test/fixtures/mldsa44_seed_pkcs8.der b/test/fixtures/mldsa44_seed_pkcs8.der new file mode 100644 index 0000000..0074eaf Binary files /dev/null and b/test/fixtures/mldsa44_seed_pkcs8.der differ diff --git a/test/fixtures/mldsa44_seed_pkcs8.pem b/test/fixtures/mldsa44_seed_pkcs8.pem new file mode 100644 index 0000000..6e6b741 --- /dev/null +++ b/test/fixtures/mldsa44_seed_pkcs8.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMRBCKAIKbCDxGWUl7odWKiRfBE8cszZ0Ei1jdVvXtY +2rIBk7X5 +-----END PRIVATE KEY----- diff --git a/test/fixtures/mldsa44_spki_pub.der b/test/fixtures/mldsa44_spki_pub.der new file mode 100644 index 0000000..803ed2d Binary files /dev/null and b/test/fixtures/mldsa44_spki_pub.der differ diff --git a/test/fixtures/mldsa44_spki_pub.pem b/test/fixtures/mldsa44_spki_pub.pem new file mode 100644 index 0000000..bd68fd2 --- /dev/null +++ b/test/fixtures/mldsa44_spki_pub.pem @@ -0,0 +1,30 @@ +-----BEGIN PUBLIC KEY----- +MIIFMjALBglghkgBZQMEAxEDggUhAD5c/qVrowyfsSLI5HocaUJxNn+BjPi3kTns +qoIToh5veoQBKZVMj5LQF5ruqm0MYx05KFjsFQEJhQ5R2cqOMO2A6owAwsOxxHGa +qfkxsd5XGWevHk3ULc8ZZy74laT60bx5xwq00YrbX+F9xy8N/mmzyyHXcpamAXLC +6JoqYuBwG9tcPZdLc6FgNESIR6XWF0xx1ZcmjloEsmKHHYhoBPem2KRV7cLVDURW +XwVpT0pbfq81ijl1YvVHhRfbEOgH+OLChd6wnrysayRA1K3FqbXo9tOLJ7suFrw3 +0CumJFLOnx5gg22qHgqRkMyX3GVxRltJes8alIEbUvbM4ACJDsssQXC7IclTcjav +7hicYclKt2xxp2uprGfOAvikCltpxxOXouJv4kBeSm95wVT+aIU6YTg4+mAgNc3r +VMc0nnv3yjAO2cu84CLt95fjU8w3/uxTvZQe/JtWYeTDwKkVQTOtD1Q+4D6ntd+R +1gFMOGlaZ8NaR0gZHlj+Vc6JqNrd57eXVYepPvX5CxAJJUHEsyRBDd2jkG6dtf58 +uO8k2JeiQrFtdS/laWBzFq8UTha1709QyNVUzZi8sM20jR65FnEUjuvtEj/pyzOS +qqOFKtFX/Dnj178GM3PJ6AJHywxDQFqWNXO/nDBNLzLoHPoA/996iNaV11PzppEa +xrsntCpoO1t3vUUSxiu7DisO0G8kt6iPv7+eRIDaxJmgSyojV/pVBJ8F+YedsK8f +fHwLfX6XYfeUXuaM0IDnsSiOVhdN+nIZ8oQOkqgn42LpPd5saG7Whs6XFBGZxzDJ +KgMb0F/O0ZcUL3pDqrkJ4wpbKBWkx+rvyZJZSJ8PDAhRzHy2O5v/EADWcSs8DUay +eYY0//qf+ZvHUCEW4P+et6pYVK+cNBsO/sMZGtuq/r1DQDRRw6ivGpPTviCJzgVK +5guZzItR2oan1uXE4TkhuV25tPjGkF+0X/M6cwLs7GZO+8x6ROckq5vDcGsvG4TS +TInrkwbh9ZPTAoTUR5ZU6S0gOoa2nngFoUYKeZBIppeW6b5IEf7ZX307Z9WdoBJS +8ejSDPrtK7pWA7vkoLAD3gdXi3Tab+l9VSZzNvkTSEQIbFsPMuv6aNeweRJN/wqd +9oj7oAF/+XQNpudnUkIkr8paD0hzo2IRCSved9uDvgB+WT/dNtW9J2PN7VLtj3ZI +zhCKq4v5VzK8TqygjnCFHYoPibs9b87QLFRGulLuT1UJ8wOCy/U2zdu0Z97249E5 +glSN0mOl7Y1UtmDFjgvUGBlCtzOehdNoe8SKlFl+Th8vKj5U39I9m6sxtMVfQ20c +WH+5iyEb3Z7hJEnMW74NlNxQt+H43BGf+SFqtA4d7ud2uZ4mlwojgq8VET28CjHK +b7yqUT9KKiVRWL49JK1vBuJTFzp4pZO/ewRc4/7lM6lI3dTKtdKjqPndTHdNCS6U +8zCmRSr9VXmENai6UJosFyA5Mi/JLbnXpHcVoEZ5fFfct5u5itKEmTupCJmbLORd +2d82jBwYKB5hl4LPwO6jjG+Az8VXwB1g9is6rEU5BvV1c+U+xIDSqYNGHgGlGqqw +aqCZRHaT6ZgdxGzGunhlkCDgNZmKKyUZr5En30an7Yyo04BNuqtVaLDLlSYbm69X +W65Xa5jLNT1NWStJ06gadaZ5xSyIudGVJpLOItOitVGQt6KzBGT54Fyth85HdI/P ++ONefsghHXFzUW0lz5VvuVX8ir4Rmq9T6QZLTF09mCnGSoA4EFc= +-----END PUBLIC KEY----- diff --git a/test/fixtures/mldsa65_pkcs8.der b/test/fixtures/mldsa65_pkcs8.der new file mode 100644 index 0000000..dc1a23f Binary files /dev/null and b/test/fixtures/mldsa65_pkcs8.der differ diff --git a/test/fixtures/mldsa65_pkcs8.pem b/test/fixtures/mldsa65_pkcs8.pem new file mode 100644 index 0000000..76aa86c --- /dev/null +++ b/test/fixtures/mldsa65_pkcs8.pem @@ -0,0 +1,128 @@ +-----BEGIN PRIVATE KEY----- +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 +Lb8hfLruAWgDXlvCOzJ4nes= +-----END PRIVATE KEY----- diff --git a/test/fixtures/mldsa65_seed_pkcs8.der b/test/fixtures/mldsa65_seed_pkcs8.der new file mode 100644 index 0000000..1ebf825 Binary files /dev/null and b/test/fixtures/mldsa65_seed_pkcs8.der differ diff --git a/test/fixtures/mldsa65_seed_pkcs8.pem b/test/fixtures/mldsa65_seed_pkcs8.pem new file mode 100644 index 0000000..06fbff1 --- /dev/null +++ b/test/fixtures/mldsa65_seed_pkcs8.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMSBCKAICx7pD8z9P8h0Zp8R1uq8fIOJ7O+SG2q+40e +hgMnldre +-----END PRIVATE KEY----- diff --git a/test/fixtures/mldsa65_spki_pub.der b/test/fixtures/mldsa65_spki_pub.der new file mode 100644 index 0000000..6fa3bc4 Binary files /dev/null and b/test/fixtures/mldsa65_spki_pub.der differ diff --git a/test/fixtures/mldsa65_spki_pub.pem b/test/fixtures/mldsa65_spki_pub.pem new file mode 100644 index 0000000..53ed81c --- /dev/null +++ b/test/fixtures/mldsa65_spki_pub.pem @@ -0,0 +1,44 @@ +-----BEGIN PUBLIC KEY----- +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 +wjsyeJ3r +-----END PUBLIC KEY----- diff --git a/test/fixtures/mldsa87_pkcs8.der b/test/fixtures/mldsa87_pkcs8.der new file mode 100644 index 0000000..c98d79a Binary files /dev/null and b/test/fixtures/mldsa87_pkcs8.der differ diff --git a/test/fixtures/mldsa87_pkcs8.pem b/test/fixtures/mldsa87_pkcs8.pem new file mode 100644 index 0000000..597a509 --- /dev/null +++ b/test/fixtures/mldsa87_pkcs8.pem @@ -0,0 +1,159 @@ +-----BEGIN PRIVATE KEY----- +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 +-----END PRIVATE KEY----- diff --git a/test/fixtures/mldsa87_seed_pkcs8.der b/test/fixtures/mldsa87_seed_pkcs8.der new file mode 100644 index 0000000..81792c9 Binary files /dev/null and b/test/fixtures/mldsa87_seed_pkcs8.der differ diff --git a/test/fixtures/mldsa87_seed_pkcs8.pem b/test/fixtures/mldsa87_seed_pkcs8.pem new file mode 100644 index 0000000..9ee455c --- /dev/null +++ b/test/fixtures/mldsa87_seed_pkcs8.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMTBCKAIDJz1Xy/MFbvi22+key+cGeZeaEPksoOS1Qh +oA0yfbS1 +-----END PRIVATE KEY----- diff --git a/test/fixtures/mldsa87_spki_pub.der b/test/fixtures/mldsa87_spki_pub.der new file mode 100644 index 0000000..b059baa Binary files /dev/null and b/test/fixtures/mldsa87_spki_pub.der differ diff --git a/test/fixtures/mldsa87_spki_pub.pem b/test/fixtures/mldsa87_spki_pub.pem new file mode 100644 index 0000000..96b4c79 --- /dev/null +++ b/test/fixtures/mldsa87_spki_pub.pem @@ -0,0 +1,57 @@ +-----BEGIN PUBLIC KEY----- +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 +to36LJYcOwchcIe4pH4oUQFpLV155w== +-----END PUBLIC KEY----- diff --git a/test/kryptos/mldsa_test.gleam b/test/kryptos/mldsa_test.gleam new file mode 100644 index 0000000..b36bc35 --- /dev/null +++ b/test/kryptos/mldsa_test.gleam @@ -0,0 +1,430 @@ +import birdie +import gleam/bit_array +import gleam/int +import gleam/list +import kryptos/crypto +import kryptos/internal/der +import kryptos/mldsa +import qcheck +import simplifile +import unitest + +const test_count = 10 + +const all_params = [mldsa.Mldsa44, mldsa.Mldsa65, mldsa.Mldsa87] + +fn config() -> qcheck.Config { + qcheck.default_config() |> qcheck.with_test_count(test_count) +} + +fn param_gen() -> qcheck.Generator(mldsa.ParameterSet) { + qcheck.from_generators(qcheck.return(mldsa.Mldsa44), [ + qcheck.return(mldsa.Mldsa65), + qcheck.return(mldsa.Mldsa87), + ]) +} + +fn param_name(param: mldsa.ParameterSet) -> String { + case param { + mldsa.Mldsa44 -> "mldsa44" + mldsa.Mldsa65 -> "mldsa65" + mldsa.Mldsa87 -> "mldsa87" + } +} + +fn load_expanded_pem(param: mldsa.ParameterSet) -> String { + let assert Ok(pem) = + simplifile.read("test/fixtures/" <> param_name(param) <> "_pkcs8.pem") + pem +} + +fn load_expanded_der(param: mldsa.ParameterSet) -> BitArray { + let assert Ok(der) = + simplifile.read_bits("test/fixtures/" <> param_name(param) <> "_pkcs8.der") + der +} + +fn load_public_key(param: mldsa.ParameterSet) -> mldsa.PublicKey { + let assert Ok(pem) = + simplifile.read("test/fixtures/" <> param_name(param) <> "_spki_pub.pem") + let assert Ok(public_key) = mldsa.public_key_from_pem(pem) + public_key +} + +pub fn mldsa_sign_verify_roundtrip_property_test() { + use <- unitest.guard(mldsa.supported()) + + let gen = qcheck.tuple2(param_gen(), qcheck.byte_aligned_bit_array()) + + qcheck.run(config(), gen, fn(input) { + let #(param, message) = input + let #(private_key, public_key) = mldsa.generate_key_pair(param) + let signature = mldsa.sign(private_key, message) + assert mldsa.verify(public_key, message, signature:) + }) +} + +pub fn mldsa_wrong_key_fails_property_test() { + use <- unitest.guard(mldsa.supported()) + + let gen = qcheck.tuple2(param_gen(), qcheck.byte_aligned_bit_array()) + + qcheck.run(config(), gen, fn(input) { + let #(param, message) = input + let #(private_key, _) = mldsa.generate_key_pair(param) + let #(_, other_public_key) = mldsa.generate_key_pair(param) + let signature = mldsa.sign(private_key, message) + assert !mldsa.verify(other_public_key, message, signature:) + }) +} + +pub fn mldsa_tampered_message_fails_property_test() { + use <- unitest.guard(mldsa.supported()) + + let gen = + qcheck.tuple2(param_gen(), qcheck.non_empty_byte_aligned_bit_array()) + + qcheck.run(config(), gen, fn(input) { + let #(param, message) = input + let #(private_key, public_key) = mldsa.generate_key_pair(param) + let signature = mldsa.sign(private_key, message) + + let assert <> = message + let tampered = <<{ int.bitwise_exclusive_or(first_byte, 1) }:8, rest:bits>> + + assert !mldsa.verify(public_key, tampered, signature:) + }) +} + +pub fn mldsa_tampered_signature_fails_test() { + use <- unitest.guard(mldsa.supported()) + + let #(private_key, public_key) = mldsa.generate_key_pair(mldsa.Mldsa44) + let message = <<"message":utf8>> + let signature = mldsa.sign(private_key, message) + + let assert <> = signature + let tampered = <<{ int.bitwise_exclusive_or(first_byte, 1) }:8, rest:bits>> + + assert !mldsa.verify(public_key, message, signature: tampered) +} + +pub fn mldsa_cross_parameter_set_verify_fails_test() { + use <- unitest.guard(mldsa.supported()) + + let #(priv44, _) = mldsa.generate_key_pair(mldsa.Mldsa44) + let #(_, pub65) = mldsa.generate_key_pair(mldsa.Mldsa65) + let message = <<"cross":utf8>> + let sig = mldsa.sign(priv44, message) + assert !mldsa.verify(pub65, message, signature: sig) +} + +pub fn mldsa_empty_message_sign_verify_test() { + use <- unitest.guard(mldsa.supported()) + + let #(private, public) = mldsa.generate_key_pair(mldsa.Mldsa44) + let sig = mldsa.sign(private, <<>>) + assert mldsa.verify(public, <<>>, signature: sig) +} + +pub fn mldsa_parameter_set_introspection_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let #(private, public) = mldsa.generate_key_pair(param) + assert mldsa.parameter_set(private) == param + assert mldsa.public_key_parameter_set(public) == param + }) +} + +pub fn mldsa_public_key_from_private_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let #(private_key, public_key) = mldsa.generate_key_pair(param) + let derived = mldsa.public_key_from_private_key(private_key) + assert mldsa.public_key_to_bytes(derived) + == mldsa.public_key_to_bytes(public_key) + }) +} + +pub fn mldsa_signature_sizes_test() { + use <- unitest.guard(mldsa.supported()) + + let msg = <<"test":utf8>> + list.each(all_params, fn(param) { + let #(private, _) = mldsa.generate_key_pair(param) + assert bit_array.byte_size(mldsa.sign(private, msg)) + == mldsa.signature_size(param) + }) +} + +pub fn mldsa_public_key_sizes_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let #(_, public) = mldsa.generate_key_pair(param) + assert bit_array.byte_size(mldsa.public_key_to_bytes(public)) + == mldsa.key_size(param) + }) +} + +pub fn mldsa_public_key_bytes_roundtrip_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let #(_, public_key) = mldsa.generate_key_pair(param) + let bytes = mldsa.public_key_to_bytes(public_key) + let assert Ok(restored) = mldsa.public_key_from_bytes(param, bytes) + assert mldsa.public_key_to_bytes(restored) == bytes + }) +} + +pub fn mldsa_public_key_from_bytes_wrong_size_test() { + use <- unitest.guard(mldsa.supported()) + + assert mldsa.public_key_from_bytes(mldsa.Mldsa44, <<"too short":utf8>>) + == Error(Nil) +} + +pub fn mldsa_expanded_pkcs8_rejected_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + assert mldsa.from_pem(load_expanded_pem(param)) == Error(Nil) + assert mldsa.from_der(load_expanded_der(param)) == Error(Nil) + }) +} + +pub fn mldsa_from_pem_invalid_test() { + use <- unitest.guard(mldsa.supported()) + + assert mldsa.from_pem("garbage") == Error(Nil) + assert mldsa.from_pem( + "-----BEGIN PRIVATE KEY-----\nnotbase64\n-----END PRIVATE KEY-----", + ) + == Error(Nil) +} + +pub fn mldsa_from_der_invalid_test() { + use <- unitest.guard(mldsa.supported()) + + assert mldsa.from_der(<<0, 1, 2, 3>>) == Error(Nil) + assert mldsa.from_der(<<>>) == Error(Nil) +} + +pub fn mldsa_export_public_key_pem_snapshot_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let pem = mldsa.public_key_to_pem(load_public_key(param)) + birdie.snap(pem, title: param_name(param) <> " public key pem") + }) +} + +pub fn mldsa_export_public_key_der_snapshot_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let der = mldsa.public_key_to_der(load_public_key(param)) + birdie.snap( + bit_array.base16_encode(der), + title: param_name(param) <> " public key der", + ) + }) +} + +pub fn mldsa_public_key_pem_roundtrip_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let public_key = load_public_key(param) + let pem = mldsa.public_key_to_pem(public_key) + let assert Ok(imported_public) = mldsa.public_key_from_pem(pem) + assert mldsa.public_key_to_bytes(imported_public) + == mldsa.public_key_to_bytes(public_key) + }) +} + +pub fn mldsa_public_key_der_roundtrip_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let public_key = load_public_key(param) + let der = mldsa.public_key_to_der(public_key) + let assert Ok(imported_public) = mldsa.public_key_from_der(der) + assert mldsa.public_key_to_bytes(imported_public) + == mldsa.public_key_to_bytes(public_key) + }) +} + +fn build_spki_der(oid_components: List(Int), key_bytes: BitArray) -> BitArray { + let assert Ok(oid_der) = der.encode_oid(oid_components) + let assert Ok(alg_id) = der.encode_sequence(oid_der) + let assert Ok(bit_string) = der.encode_bit_string(key_bytes) + let assert Ok(spki) = + der.encode_sequence(bit_array.concat([alg_id, bit_string])) + spki +} + +fn param_oid(param: mldsa.ParameterSet) -> List(Int) { + case param { + mldsa.Mldsa44 -> [2, 16, 840, 1, 101, 3, 4, 3, 17] + mldsa.Mldsa65 -> [2, 16, 840, 1, 101, 3, 4, 3, 18] + mldsa.Mldsa87 -> [2, 16, 840, 1, 101, 3, 4, 3, 19] + } +} + +pub fn mldsa_public_key_from_der_wrong_key_length_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let too_short = build_spki_der(param_oid(param), <<0:64>>) + assert mldsa.public_key_from_der(too_short) == Error(Nil) + }) +} + +pub fn mldsa_public_key_from_pem_wrong_key_length_test() { + use <- unitest.guard(mldsa.supported()) + + let spki_der = build_spki_der(param_oid(mldsa.Mldsa44), <<0x42>>) + let base64 = bit_array.base64_encode(spki_der, True) + let pem = + "-----BEGIN PUBLIC KEY-----\n" <> base64 <> "\n-----END PUBLIC KEY-----\n" + assert mldsa.public_key_from_pem(pem) == Error(Nil) +} + +pub fn mldsa_public_key_from_der_empty_key_test() { + use <- unitest.guard(mldsa.supported()) + + let empty_key = build_spki_der(param_oid(mldsa.Mldsa44), <<>>) + assert mldsa.public_key_from_der(empty_key) == Error(Nil) +} + +pub fn mldsa_from_seed_sign_verify_test() { + use <- unitest.guard(mldsa.supported()) + + let seed = crypto.random_bytes(32) + let assert Ok(#(private, public)) = mldsa.from_seed(mldsa.Mldsa44, seed) + let message = <<"from seed test":utf8>> + let signature = mldsa.sign(private, message) + assert mldsa.verify(public, message, signature:) +} + +pub fn mldsa_from_seed_wrong_size_test() { + use <- unitest.guard(mldsa.supported()) + + assert mldsa.from_seed(mldsa.Mldsa44, <<1, 2, 3>>) == Error(Nil) + assert mldsa.from_seed(mldsa.Mldsa44, <<>>) == Error(Nil) +} + +pub fn mldsa_private_key_to_seed_roundtrip_property_test() { + use <- unitest.guard(mldsa.supported()) + + qcheck.run(config(), param_gen(), fn(param) { + let seed = crypto.random_bytes(32) + let assert Ok(#(private, _)) = mldsa.from_seed(param, seed) + assert mldsa.private_key_to_seed(private) == Ok(seed) + }) +} + +pub fn mldsa_generated_key_to_seed_test() { + use <- unitest.guard(mldsa.supported()) + + let #(private, public) = mldsa.generate_key_pair(mldsa.Mldsa44) + let assert Ok(seed) = mldsa.private_key_to_seed(private) + let assert Ok(#(_, regenerated_public)) = mldsa.from_seed(mldsa.Mldsa44, seed) + assert mldsa.public_key_to_bytes(regenerated_public) + == mldsa.public_key_to_bytes(public) +} + +pub fn mldsa_from_seed_public_key_from_private_test() { + use <- unitest.guard(mldsa.supported()) + + let seed = crypto.random_bytes(32) + let assert Ok(#(private, public)) = mldsa.from_seed(mldsa.Mldsa44, seed) + let derived = mldsa.public_key_from_private_key(private) + assert mldsa.public_key_to_bytes(derived) == mldsa.public_key_to_bytes(public) +} + +pub fn mldsa_from_seed_deterministic_property_test() { + use <- unitest.guard(mldsa.supported()) + + qcheck.run(config(), param_gen(), fn(param) { + let seed = crypto.random_bytes(32) + let assert Ok(#(_, pub1)) = mldsa.from_seed(param, seed) + let assert Ok(#(_, pub2)) = mldsa.from_seed(param, seed) + assert mldsa.public_key_to_bytes(pub1) == mldsa.public_key_to_bytes(pub2) + }) +} + +pub fn mldsa_from_seed_parameter_set_property_test() { + use <- unitest.guard(mldsa.supported()) + + qcheck.run(config(), param_gen(), fn(param) { + let seed = crypto.random_bytes(32) + let assert Ok(#(private, _)) = mldsa.from_seed(param, seed) + assert mldsa.parameter_set(private) == param + }) +} + +pub fn mldsa_import_seed_pem_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let assert Ok(pem) = + simplifile.read( + "test/fixtures/" <> param_name(param) <> "_seed_pkcs8.pem", + ) + let assert Ok(#(private, public)) = mldsa.from_pem(pem) + let message = <<"seed key test":utf8>> + let signature = mldsa.sign(private, message) + assert mldsa.verify(public, message, signature:) + }) +} + +pub fn mldsa_import_seed_der_test() { + use <- unitest.guard(mldsa.supported()) + + list.each(all_params, fn(param) { + let assert Ok(der) = + simplifile.read_bits( + "test/fixtures/" <> param_name(param) <> "_seed_pkcs8.der", + ) + let assert Ok(#(private, public)) = mldsa.from_der(der) + let message = <<"seed key test":utf8>> + let signature = mldsa.sign(private, message) + assert mldsa.verify(public, message, signature:) + }) +} + +pub fn mldsa_from_seed_der_roundtrip_property_test() { + use <- unitest.guard(mldsa.supported()) + + qcheck.run(config(), param_gen(), fn(param) { + let seed = crypto.random_bytes(32) + let assert Ok(#(private, public)) = mldsa.from_seed(param, seed) + let der = mldsa.to_der(private) + let assert Ok(#(reimported_private, reimported_public)) = + mldsa.from_der(der) + assert mldsa.public_key_to_bytes(public) + == mldsa.public_key_to_bytes(reimported_public) + let message = <<"roundtrip":utf8>> + let sig = mldsa.sign(reimported_private, message) + assert mldsa.verify(public, message, signature: sig) + }) +} + +pub fn mldsa_from_seed_pem_roundtrip_property_test() { + use <- unitest.guard(mldsa.supported()) + + qcheck.run(config(), param_gen(), fn(param) { + let seed = crypto.random_bytes(32) + let assert Ok(#(private, public)) = mldsa.from_seed(param, seed) + let pem = mldsa.to_pem(private) + let assert Ok(#(reimported_private, _)) = mldsa.from_pem(pem) + let message = <<"pem roundtrip":utf8>> + let sig = mldsa.sign(reimported_private, message) + assert mldsa.verify(public, message, signature: sig) + }) +} diff --git a/test/kryptos/x509/certificate_test.gleam b/test/kryptos/x509/certificate_test.gleam index 4d7e12f..3a16b50 100644 --- a/test/kryptos/x509/certificate_test.gleam +++ b/test/kryptos/x509/certificate_test.gleam @@ -9,6 +9,7 @@ import kryptos/crypto import kryptos/ec import kryptos/eddsa import kryptos/hash +import kryptos/mldsa import kryptos/rsa import kryptos/x509 import kryptos/x509/certificate @@ -17,6 +18,7 @@ import kryptos/xdh import qcheck import shellout import simplifile +import unitest fn make_builder(domain: String) -> Result(certificate.Builder, Nil) { let subject = x509.name([x509.cn(domain)]) @@ -785,3 +787,41 @@ pub fn san_not_critical_when_subject_present_test() { assert !is_critical } + +pub fn verify_mldsa_fails_with_wrong_key_test() { + use <- unitest.guard(mldsa.supported()) + + let #(private_key, _) = mldsa.generate_key_pair(mldsa.Mldsa44) + let #(_, wrong_public_key) = mldsa.generate_key_pair(mldsa.Mldsa44) + let assert Ok(builder) = make_builder("mldsa-verify.example.com") + + let assert Ok(cert) = certificate.self_signed_with_mldsa(builder, private_key) + + let assert Ok(parsed) = certificate.from_der(certificate.to_der(cert)) + assert certificate.verify(parsed, x509.MldsaPublicKey(wrong_public_key)) + == Error(certificate.SignatureVerificationFailed) +} + +pub fn self_signed_mldsa_roundtrip_test() { + use <- unitest.guard(mldsa.supported()) + + let cases = [ + #(mldsa.Mldsa44, x509.Mldsa44, "mldsa44.example.com"), + #(mldsa.Mldsa65, x509.Mldsa65, "mldsa65.example.com"), + #(mldsa.Mldsa87, x509.Mldsa87, "mldsa87.example.com"), + ] + + list.each(cases, fn(tc) { + let #(param, expected_alg, domain) = tc + let #(private_key, _) = mldsa.generate_key_pair(param) + let assert Ok(builder) = make_builder(domain) + + let assert Ok(cert) = + certificate.self_signed_with_mldsa(builder, private_key) + let assert Ok(parsed) = certificate.from_der(certificate.to_der(cert)) + assert certificate.version(parsed) == 2 + let assert x509.MldsaPublicKey(_) = certificate.public_key(parsed) + assert certificate.signature_algorithm(parsed) == expected_alg + let assert Ok(_) = certificate.verify_self_signed(parsed) + }) +} diff --git a/test/kryptos/x509/csr_test.gleam b/test/kryptos/x509/csr_test.gleam index f0ad7a7..f468e12 100644 --- a/test/kryptos/x509/csr_test.gleam +++ b/test/kryptos/x509/csr_test.gleam @@ -7,6 +7,7 @@ import gleam/string import kryptos/ec import kryptos/eddsa import kryptos/hash +import kryptos/mldsa import kryptos/rsa import kryptos/x509 import kryptos/x509/csr @@ -14,6 +15,7 @@ import kryptos/x509/test_helpers import qcheck import shellout import simplifile +import unitest fn contains_subsequence(haystack: BitArray, needle: BitArray) -> Bool { let needle_size = bit_array.byte_size(needle) @@ -419,6 +421,46 @@ pub fn csr_eddsa_ed448_dns_san_verified_by_openssl_test() { |> birdie.snap(title: "csr eddsa ed448 dns san text") } +pub fn csr_mldsa_dns_san_verified_by_openssl_test() { + use <- unitest.guard(mldsa.supported()) + + let assert Ok(pem) = simplifile.read("test/fixtures/mldsa44_seed_pkcs8.pem") + let assert Ok(#(private_key, _)) = mldsa.from_pem(pem) + + let subject = + x509.name([ + x509.cn("mldsa.example.com"), + x509.organization("MLDSA Corp"), + x509.country("US"), + ]) + + let assert Ok(builder) = + csr.new() + |> csr.with_subject(subject) + |> csr.with_dns_name("mldsa.example.com") + + let assert Ok(my_csr) = csr.sign_with_mldsa(builder, private_key) + let csr_pem = csr.to_pem(my_csr) + + let cmd = "echo '" <> csr_pem <> "' | openssl asn1parse -i" + let assert Ok(output) = + shellout.command(run: "sh", with: ["-c", cmd], in: ".", opt: []) + + birdie.snap( + test_helpers.mask_dynamic_values(output), + title: "csr mldsa dns san openssl output", + ) + + let text_cmd = "echo '" <> csr_pem <> "' | openssl req -text -noout" + let assert Ok(text_output) = + shellout.command(run: "sh", with: ["-c", text_cmd], in: ".", opt: []) + + text_output + |> test_helpers.mask_signature + |> test_helpers.normalize_subject + |> birdie.snap(title: "csr mldsa dns san text") +} + pub fn csr_ecdsa_email_san_verified_by_openssl_test() { let assert Ok(pem) = simplifile.read("test/fixtures/p256_pkcs8.pem") let assert Ok(#(private_key, _)) = ec.from_pem(pem) @@ -784,3 +826,101 @@ pub fn parse_openssl_generated_csr_test() { x509.IpAddress(<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>), ) } + +pub fn csr_mldsa_produces_valid_der_test() { + use <- unitest.guard(mldsa.supported()) + + list.each([mldsa.Mldsa44, mldsa.Mldsa65, mldsa.Mldsa87], fn(param) { + let #(private_key, _) = mldsa.generate_key_pair(param) + let subject = x509.name([x509.cn("test.example.com")]) + + let assert Ok(builder) = + csr.new() + |> csr.with_subject(subject) + |> csr.with_dns_name("test.example.com") + + let assert Ok(my_csr) = csr.sign_with_mldsa(builder, private_key) + + let pem = csr.to_pem(my_csr) + let assert Ok(_) = csr.from_pem(pem) + + let der = csr.to_der(my_csr) + let assert Ok(_) = csr.from_der(der) + }) +} + +pub fn mldsa_csr_roundtrip_test() { + use <- unitest.guard(mldsa.supported()) + + let cases = [ + #(mldsa.Mldsa44, x509.Mldsa44, "mldsa44.example.com"), + #(mldsa.Mldsa65, x509.Mldsa65, "mldsa65.example.com"), + #(mldsa.Mldsa87, x509.Mldsa87, "mldsa87.example.com"), + ] + + list.each(cases, fn(tc) { + let #(param, expected_alg, domain) = tc + let #(private_key, _) = mldsa.generate_key_pair(param) + let subject = x509.name([x509.cn(domain)]) + + let assert Ok(builder) = + csr.new() + |> csr.with_subject(subject) + |> csr.with_dns_name(domain) + + let assert Ok(built_csr) = csr.sign_with_mldsa(builder, private_key) + + let der = csr.to_der(built_csr) + let assert Ok(parsed) = csr.from_der(der) + + assert csr.version(parsed) == 0 + let assert x509.MldsaPublicKey(_) = csr.public_key(parsed) + assert csr.signature_algorithm(parsed) == expected_alg + assert csr.subject_alt_names(parsed) == [x509.DnsName(domain)] + }) +} + +pub fn mldsa_csr_bad_signature_test() { + use <- unitest.guard(mldsa.supported()) + + let #(private_key, _) = mldsa.generate_key_pair(mldsa.Mldsa44) + let subject = x509.name([x509.cn("test.example.com")]) + + let assert Ok(built_csr) = + csr.new() + |> csr.with_subject(subject) + |> csr.sign_with_mldsa(private_key) + + let der = csr.to_der(built_csr) + let size = bit_array.byte_size(der) + + let assert Ok(prefix) = bit_array.slice(der, 0, size - 4) + let corrupted = bit_array.concat([prefix, <<0xFF, 0xFF, 0xFF, 0xFF>>]) + + assert csr.from_der(corrupted) == Error(csr.SignatureVerificationFailed) +} + +pub fn mldsa_csr_omits_null_params_test() { + use <- unitest.guard(mldsa.supported()) + + let #(private_key, _) = mldsa.generate_key_pair(mldsa.Mldsa44) + let subject = x509.name([x509.cn("test.example.com")]) + + let assert Ok(builder) = + csr.new() + |> csr.with_subject(subject) + |> csr.with_dns_name("test.example.com") + + let assert Ok(my_csr) = csr.sign_with_mldsa(builder, private_key) + let der = csr.to_der(my_csr) + + let mldsa44_oid_with_null = << + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x05, 0x00, + >> + assert !contains_subsequence(der, mldsa44_oid_with_null) + + let mldsa44_oid_correct = << + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, + >> + assert contains_subsequence(der, mldsa44_oid_correct) +} diff --git a/test/wycheproof/mldsa_test.gleam b/test/wycheproof/mldsa_test.gleam new file mode 100644 index 0000000..b48bb45 --- /dev/null +++ b/test/wycheproof/mldsa_test.gleam @@ -0,0 +1,387 @@ +import gleam/bit_array +import gleam/bool +import gleam/dynamic/decode +import gleam/list +import gleam/option.{type Option} +import kryptos/internal/mldsa as mldsa_internal +import kryptos/mldsa +import unitest +import wycheproof/utils + +type TestCase { + TestCase( + tc_id: Int, + comment: String, + msg: String, + sig: String, + ctx: Option(String), + result: utils.TestResult, + ) +} + +type TestGroup { + TestGroup(public_key: String, tests: List(TestCase)) +} + +type TestFile { + TestFile(test_groups: List(TestGroup)) +} + +fn test_case_decoder() -> decode.Decoder(TestCase) { + use tc_id <- decode.field("tcId", decode.int) + use comment <- decode.field("comment", decode.string) + use msg <- decode.optional_field("msg", "", decode.string) + use sig <- decode.field("sig", decode.string) + use ctx <- decode.optional_field( + "ctx", + option.None, + decode.map(decode.string, option.Some), + ) + use result <- decode.field("result", utils.test_result_decoder()) + decode.success(TestCase(tc_id:, comment:, msg:, sig:, ctx:, result:)) +} + +fn test_group_decoder() -> decode.Decoder(TestGroup) { + use public_key <- decode.field("publicKey", decode.string) + use tests <- decode.field("tests", decode.list(test_case_decoder())) + decode.success(TestGroup(public_key:, tests:)) +} + +fn test_file_decoder() -> decode.Decoder(TestFile) { + use test_groups <- decode.field( + "testGroups", + decode.list(test_group_decoder()), + ) + decode.success(TestFile(test_groups:)) +} + +fn has_non_empty_context(ctx: Option(String)) -> Bool { + option.map(ctx, fn(value) { value != "" }) |> option.unwrap(False) +} + +fn run_single_test( + param: mldsa.ParameterSet, + group: TestGroup, + tc: TestCase, +) -> Nil { + let context = utils.test_context(tc.tc_id, tc.comment) + use <- bool.guard(when: has_non_empty_context(tc.ctx), return: Nil) + + let assert Ok(pk_bytes) = bit_array.base16_decode(group.public_key) + let assert Ok(msg_bytes) = bit_array.base16_decode(tc.msg) + let assert Ok(sig_bytes) = bit_array.base16_decode(tc.sig) + + case tc.result { + utils.Invalid -> { + case mldsa.public_key_from_bytes(param, pk_bytes) { + Ok(pub_key) -> { + assert !mldsa.verify(pub_key, msg_bytes, signature: sig_bytes) + as { "ML-DSA verification succeeded for invalid test: " <> context } + } + Error(Nil) -> Nil + } + } + utils.Valid -> { + let assert Ok(pub_key) = mldsa.public_key_from_bytes(param, pk_bytes) + as { "Public key import failed: " <> context } + assert mldsa.verify(pub_key, msg_bytes, signature: sig_bytes) + as { "ML-DSA verification failed for valid test: " <> context } + } + utils.Acceptable -> { + case mldsa.public_key_from_bytes(param, pk_bytes) { + Ok(pub_key) -> { + let _ = mldsa.verify(pub_key, msg_bytes, signature: sig_bytes) + Nil + } + Error(Nil) -> Nil + } + } + } +} + +fn run_verify_tests(filename: String, param: mldsa.ParameterSet) -> Nil { + let assert Ok(test_file) = utils.load_test_file(filename, test_file_decoder()) + utils.run_tests(test_file.test_groups, fn(g) { g.tests }, fn(group, tc) { + run_single_test(param, group, tc) + }) +} + +pub fn wycheproof_mldsa_44_verify_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_verify_tests("mldsa_44_verify_test.json", mldsa.Mldsa44) +} + +pub fn wycheproof_mldsa_65_verify_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_verify_tests("mldsa_65_verify_test.json", mldsa.Mldsa65) +} + +pub fn wycheproof_mldsa_87_verify_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_verify_tests("mldsa_87_verify_test.json", mldsa.Mldsa87) +} + +type SeedTestGroup { + SeedTestGroup( + private_seed: String, + public_key: Option(String), + tests: List(TestCase), + ) +} + +type SeedTestFile { + SeedTestFile(test_groups: List(SeedTestGroup)) +} + +fn seed_test_group_decoder() -> decode.Decoder(SeedTestGroup) { + use private_seed <- decode.field("privateSeed", decode.string) + use public_key <- decode.field("publicKey", decode.optional(decode.string)) + use tests <- decode.field("tests", decode.list(test_case_decoder())) + decode.success(SeedTestGroup(private_seed:, public_key:, tests:)) +} + +fn seed_test_file_decoder() -> decode.Decoder(SeedTestFile) { + use test_groups <- decode.field( + "testGroups", + decode.list(seed_test_group_decoder()), + ) + decode.success(SeedTestFile(test_groups:)) +} + +fn check_verification( + label: String, + result: utils.TestResult, + pub_key: mldsa.PublicKey, + msg_bytes: BitArray, + sig_bytes: BitArray, + context: String, +) -> Nil { + case result { + utils.Valid -> { + assert mldsa.verify(pub_key, msg_bytes, signature: sig_bytes) + as { label <> " verification failed: " <> context } + } + utils.Invalid -> { + assert !mldsa.verify(pub_key, msg_bytes, signature: sig_bytes) + as { label <> " verification succeeded for invalid: " <> context } + } + utils.Acceptable -> { + let _ = mldsa.verify(pub_key, msg_bytes, signature: sig_bytes) + Nil + } + } +} + +fn run_seed_sign_test( + param: mldsa.ParameterSet, + group: SeedTestGroup, + tc: TestCase, +) -> Nil { + let context = utils.test_context(tc.tc_id, tc.comment) + use <- bool.guard(when: has_non_empty_context(tc.ctx), return: Nil) + use <- bool.guard(when: tc.msg == "", return: Nil) + use <- bool.guard(when: option.is_none(group.public_key), return: Nil) + + let assert option.Some(pk_hex) = group.public_key + let assert Ok(pk_bytes) = bit_array.base16_decode(pk_hex) + let assert Ok(msg_bytes) = bit_array.base16_decode(tc.msg) + let assert Ok(sig_bytes) = bit_array.base16_decode(tc.sig) + let assert Ok(pub_key) = mldsa.public_key_from_bytes(param, pk_bytes) + + check_verification( + "Seed sign", + tc.result, + pub_key, + msg_bytes, + sig_bytes, + context, + ) +} + +fn run_seed_sign_tests(filename: String, param: mldsa.ParameterSet) -> Nil { + let assert Ok(file) = utils.load_test_file(filename, seed_test_file_decoder()) + utils.run_tests(file.test_groups, fn(g) { g.tests }, fn(group, tc) { + run_seed_sign_test(param, group, tc) + }) +} + +pub fn wycheproof_mldsa_44_sign_seed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_seed_sign_tests("mldsa_44_sign_seed_test.json", mldsa.Mldsa44) +} + +pub fn wycheproof_mldsa_65_sign_seed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_seed_sign_tests("mldsa_65_sign_seed_test.json", mldsa.Mldsa65) +} + +pub fn wycheproof_mldsa_87_sign_seed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_seed_sign_tests("mldsa_87_sign_seed_test.json", mldsa.Mldsa87) +} + +fn run_keygen_test(filename: String, param: mldsa.ParameterSet) -> Nil { + let assert Ok(file) = utils.load_test_file(filename, seed_test_file_decoder()) + list.each(file.test_groups, fn(group) { + let assert Ok(seed) = bit_array.base16_decode(group.private_seed) + case group.public_key { + option.None -> { + assert mldsa_internal.public_key_from_seed(param, seed) == Error(Nil) + } + option.Some(pk_hex) -> { + let assert Ok(expected_pk) = bit_array.base16_decode(pk_hex) + let assert Ok(derived_pk) = + mldsa_internal.public_key_from_seed(param, seed) + assert derived_pk == expected_pk + } + } + }) +} + +pub fn wycheproof_mldsa_44_keygen_from_seed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_keygen_test("mldsa_44_sign_seed_test.json", mldsa.Mldsa44) +} + +pub fn wycheproof_mldsa_65_keygen_from_seed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_keygen_test("mldsa_65_sign_seed_test.json", mldsa.Mldsa65) +} + +pub fn wycheproof_mldsa_87_keygen_from_seed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_keygen_test("mldsa_87_sign_seed_test.json", mldsa.Mldsa87) +} + +type NoseedTestCase { + NoseedTestCase( + tc_id: Int, + comment: String, + msg: Option(String), + ctx: Option(String), + sig: String, + result: utils.TestResult, + ) +} + +type NoseedTestGroup { + NoseedTestGroup( + private_key: String, + public_key: Option(String), + tests: List(NoseedTestCase), + ) +} + +type NoseedTestFile { + NoseedTestFile(test_groups: List(NoseedTestGroup)) +} + +fn noseed_test_case_decoder() -> decode.Decoder(NoseedTestCase) { + use tc_id <- decode.field("tcId", decode.int) + use comment <- decode.field("comment", decode.string) + use msg <- decode.optional_field( + "msg", + option.None, + decode.map(decode.string, option.Some), + ) + use ctx <- decode.optional_field( + "ctx", + option.None, + decode.map(decode.string, option.Some), + ) + use sig <- decode.field("sig", decode.string) + use result <- decode.field("result", utils.test_result_decoder()) + decode.success(NoseedTestCase(tc_id:, comment:, msg:, ctx:, sig:, result:)) +} + +fn noseed_test_group_decoder() -> decode.Decoder(NoseedTestGroup) { + use private_key <- decode.field("privateKey", decode.string) + use public_key <- decode.field("publicKey", decode.optional(decode.string)) + use tests <- decode.field("tests", decode.list(noseed_test_case_decoder())) + decode.success(NoseedTestGroup(private_key:, public_key:, tests:)) +} + +fn noseed_test_file_decoder() -> decode.Decoder(NoseedTestFile) { + use test_groups <- decode.field( + "testGroups", + decode.list(noseed_test_group_decoder()), + ) + decode.success(NoseedTestFile(test_groups:)) +} + +fn run_noseed_sign_test( + param: mldsa.ParameterSet, + group: NoseedTestGroup, + tc: NoseedTestCase, +) -> Nil { + case group.public_key, tc.msg, has_non_empty_context(tc.ctx) { + option.Some(pk_hex), option.Some(msg_hex), False -> + run_noseed_sign_check(param, tc, pk_hex, msg_hex) + _, _, _ -> Nil + } +} + +fn run_noseed_sign_check( + param: mldsa.ParameterSet, + tc: NoseedTestCase, + pk_hex: String, + msg_hex: String, +) -> Nil { + let context = utils.test_context(tc.tc_id, tc.comment) + let assert Ok(pk_bytes) = bit_array.base16_decode(pk_hex) + let assert Ok(msg_bytes) = bit_array.base16_decode(msg_hex) + let assert Ok(sig_bytes) = bit_array.base16_decode(tc.sig) + + case mldsa.public_key_from_bytes(param, pk_bytes) { + Ok(pub_key) -> + check_verification( + "Noseed sign", + tc.result, + pub_key, + msg_bytes, + sig_bytes, + context, + ) + Error(Nil) -> + case tc.result { + utils.Valid -> + panic as { "Public key import failed for valid test: " <> context } + _ -> Nil + } + } +} + +fn run_noseed_sign_tests(filename: String, param: mldsa.ParameterSet) -> Nil { + let assert Ok(file) = + utils.load_test_file(filename, noseed_test_file_decoder()) + utils.run_tests(file.test_groups, fn(g) { g.tests }, fn(group, tc) { + run_noseed_sign_test(param, group, tc) + }) +} + +pub fn wycheproof_mldsa_44_sign_noseed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_noseed_sign_tests("mldsa_44_sign_noseed_test.json", mldsa.Mldsa44) +} + +pub fn wycheproof_mldsa_65_sign_noseed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_noseed_sign_tests("mldsa_65_sign_noseed_test.json", mldsa.Mldsa65) +} + +pub fn wycheproof_mldsa_87_sign_noseed_test() { + use <- unitest.guard(mldsa.supported()) + use <- unitest.tag("wycheproof") + run_noseed_sign_tests("mldsa_87_sign_noseed_test.json", mldsa.Mldsa87) +}