mte/unikernel/duniverse/mirage-crypto/tests/test_ec_wycheproof.ml
2025-11-11 02:07:51 +01:00

314 lines
10 KiB
OCaml

open Wycheproof
open Mirage_crypto_ec
let ( let* ) = Result.bind
let hex = Alcotest.testable Wycheproof.pp_hex Wycheproof.equal_hex
module Asn = struct
let parse_point curve s =
let seq2 a b = Asn.S.(sequence2 (required a) (required b)) in
let term = Asn.S.(seq2 (seq2 oid oid) bit_string_octets) in
let ec_public_key = Asn.OID.(base 1 2 <|| [ 840; 10045; 2; 1 ]) in
let prime_oid = match curve with
| "secp256r1" -> Asn.OID.(base 1 2 <|| [ 840; 10045; 3; 1; 7 ])
| "secp384r1" -> Asn.OID.(base 1 3 <|| [ 132; 0; 34 ])
| "secp521r1" -> Asn.OID.(base 1 3 <|| [ 132; 0; 35 ])
| _ -> assert false
in
match Asn.decode (Asn.codec Asn.ber term) s with
| Error _ -> Error "ASN1 parse error"
| Ok (((oid1, oid2), data), rest) ->
if String.length rest <> 0 then Error "ASN1 leftover"
else if not (Asn.OID.equal oid1 ec_public_key) then
Error "ASN1: wrong oid 1"
else if not (Asn.OID.equal oid2 prime_oid) then Error "ASN1: wrong oid 2"
else Ok data
let parse_signature cs =
let asn = Asn.S.(sequence2 (required unsigned_integer) (required unsigned_integer)) in
match Asn.(decode (codec der asn) cs) with
| Error _ -> Error "ASN1 parse error"
| Ok (r_s, rest) ->
if String.length rest <> 0 then Error "ASN1 leftover"
else
Ok r_s
end
let to_string_result ~pp_error = function
| Ok _ as ok -> ok
| Error e ->
let msg = Format.asprintf "%a" pp_error e in
Error msg
let pad ~total_len buf =
match total_len - String.length buf with
| 0 -> Ok buf
| n when n < 0 ->
let is_zero = ref true in
for i = 0 to abs n - 1 do
if Bytes.(get_uint8 (Bytes.unsafe_of_string buf) i) <> 0 then
is_zero := false
done;
if !is_zero then
Ok (String.sub buf (abs n) total_len)
else
Error "input is too long"
| pad_len ->
Ok (String.make pad_len '\000' ^ buf)
let len = function
| "secp256r1" -> 32
| "secp384r1" -> 48
| "secp521r1" -> 66
| _ -> assert false
let parse_secret curve s =
let total_len = len curve in
pad ~total_len s
type test = {
public_key : string;
raw_private_key : string;
expected : string;
}
let perform_key_exchange curve ~public_key ~raw_private_key =
to_string_result ~pp_error
(match curve with
| "secp256r1" ->
begin match P256.Dh.secret_of_octets raw_private_key with
| Ok (p, _) -> P256.Dh.key_exchange p public_key
| Error _ -> assert false
end
| "secp384r1" ->
begin match P384.Dh.secret_of_octets raw_private_key with
| Ok (p, _) -> P384.Dh.key_exchange p public_key
| Error _ -> assert false
end
| "secp521r1" ->
begin match P521.Dh.secret_of_octets raw_private_key with
| Ok (p, _) -> P521.Dh.key_exchange p public_key
| Error _ -> assert false
end
| _ -> assert false)
let interpret_test ~tcId curve { public_key; raw_private_key; expected } () =
match perform_key_exchange curve ~public_key ~raw_private_key with
| Ok got -> Alcotest.check hex __LOC__ expected got
| Error err ->
Printf.ksprintf (fun s -> Alcotest.fail s) "While parsing %d: %s" tcId err
type invalid_test = { public : string; private_ : string }
let is_ok = function Ok _ -> true | Error _ -> false
let interpret_invalid_test curve { public; private_ } () =
let result =
let* public_key = Asn.parse_point curve public in
let* raw_private_key = parse_secret curve private_ in
perform_key_exchange curve ~public_key ~raw_private_key
in
Alcotest.check Alcotest.bool __LOC__ false (is_ok result)
type strategy = Test of test | Invalid_test of invalid_test | Skip
let make_ecdh_test curve (test : ecdh_test) =
let ignored_flags = ["UnnamedCurve"] in
let curve_compression_test curve =
let curves = ["secp256r1"; "secp384r1"; "secp521r1"] in
test.tcId = 2 && List.exists (fun x -> String.equal x curve) curves
in
match test.result with
| _ when has_ignored_flag test ~ignored_flags -> Ok Skip
| Invalid ->
Ok (Invalid_test { public = test.public; private_ = test.private_ })
| Acceptable when curve_compression_test curve ->
let* public_key = Asn.parse_point curve test.public in
let* raw_private_key = parse_secret curve test.private_ in
Ok (Test { public_key; raw_private_key; expected = test.shared })
| Acceptable -> Ok Skip
| Valid ->
let* public_key = Asn.parse_point curve test.public in
let* raw_private_key = parse_secret curve test.private_ in
Ok (Test { public_key; raw_private_key; expected = test.shared })
let to_ecdh_tests curve (x : ecdh_test) =
let name = Printf.sprintf "%d - %s" x.tcId x.comment in
match make_ecdh_test curve x with
| Ok (Test t) -> [ (name, `Quick, interpret_test ~tcId:x.tcId curve t) ]
| Ok (Invalid_test t) -> [ (name, `Quick, interpret_invalid_test curve t) ]
| Ok Skip -> []
| Error e -> Printf.ksprintf failwith "While parsing %d: %s" x.tcId e
let ecdh_tests file =
let data = load_file_exn file in
let groups : ecdh_test_group list =
List.map ecdh_test_group_exn data.testGroups
in
List.concat_map (fun (group : ecdh_test_group) ->
List.concat_map (to_ecdh_tests group.curve) group.tests)
groups
let make_ecdsa_test curve key hash (tst : dsa_test) =
let name = Printf.sprintf "%d - %s" tst.tcId tst.comment in
let size = len curve in
let msg =
let dgst =
match hash with
| "SHA-256" -> Digestif.SHA256.(digest_string tst.msg |> to_raw_string)
| "SHA-384" -> Digestif.SHA384.(digest_string tst.msg |> to_raw_string)
| "SHA-512" -> Digestif.SHA512.(digest_string tst.msg |> to_raw_string)
| "SHA-224" -> Digestif.SHA224.(digest_string tst.msg |> to_raw_string)
| _ -> assert false
in
String.sub dgst 0 (min size (String.length dgst))
in
let verified (r,s) =
match curve with
| "secp256r1" ->
begin match P256.Dsa.pub_of_octets key with
| Ok key -> P256.Dsa.verify ~key (r, s) msg
| Error _ -> assert false
end
| "secp384r1" ->
begin match P384.Dsa.pub_of_octets key with
| Ok key -> P384.Dsa.verify ~key (r, s) msg
| Error _ -> assert false
end
| "secp521r1" ->
begin match P521.Dsa.pub_of_octets key with
| Ok key -> P521.Dsa.verify ~key (r, s) msg
| Error _ -> assert false
end
| _ -> assert false
in
match tst.result with
| Acceptable
| Invalid ->
let f () =
match Asn.parse_signature tst.sig_ with
| Ok (r, s) -> Alcotest.(check bool __LOC__ false (verified (r, s)))
| Error _s -> ()
in
name, `Quick, f
| Valid ->
let f () =
match Asn.parse_signature tst.sig_ with
| Ok (r, s) -> Alcotest.(check bool __LOC__ true (verified (r, s)))
| Error s -> Alcotest.fail s
in
name, `Quick, f
let to_ecdsa_tests (x : ecdsa_test_group) =
List.map
(make_ecdsa_test x.key.curve x.key.uncompressed x.sha)
x.tests
let ecdsa_tests file =
let data = load_file_exn file in
let groups : ecdsa_test_group list =
List.map ecdsa_test_group_exn data.testGroups
in
List.concat_map to_ecdsa_tests groups
let to_x25519_test (x : ecdh_test) =
let name = Printf.sprintf "%d - %s" x.tcId x.comment
and priv =
match X25519.secret_of_octets x.private_ with
| Ok (p, _) -> p
| Error _ -> assert false
in
match x.result with
| Acceptable ->
let f () =
match
X25519.key_exchange priv x.public,
has_ignored_flag x ~ignored_flags:[ "LowOrderPublic" ]
with
| Ok _, true -> Alcotest.fail "acceptable should have errored"
| Ok r, false ->
Alcotest.(check bool __LOC__ true (String.equal r x.shared))
| Error _, true -> ()
| Error e, false -> Alcotest.failf "acceptable errored %a" pp_error e
in
name, `Quick, f
| Invalid ->
let f () =
match X25519.key_exchange priv x.public with
| Ok r -> Alcotest.(check bool __LOC__ false (String.equal r x.shared))
| Error e -> Alcotest.failf "invalid errored %a" pp_error e
in
name, `Quick, f
| Valid ->
let f () =
match X25519.key_exchange priv x.public with
| Ok r -> Alcotest.(check bool __LOC__ true (String.equal r x.shared))
| Error e -> Alcotest.failf "valid errored %a" pp_error e
in
name, `Quick, f
let x25519_tests =
let data = load_file_exn "x25519_test.json" in
let groups : ecdh_test_group list =
List.map ecdh_test_group_exn data.testGroups
in
List.concat_map (fun (group : ecdh_test_group) ->
List.map to_x25519_test group.tests)
groups
let to_ed25519_test (priv, pub) (x : dsa_test) =
let name = Printf.sprintf "%d - %s" x.tcId x.comment in
match x.result with
| Invalid ->
let f () =
Alcotest.(check bool __LOC__ false (Ed25519.verify ~key:pub x.sig_ ~msg:x.msg));
let s = Ed25519.sign ~key:priv x.msg in
Alcotest.(check bool __LOC__ false (String.equal s x.sig_))
in
name, `Quick, f
| Valid ->
let f () =
Alcotest.(check bool __LOC__ true (Ed25519.verify ~key:pub x.sig_ ~msg:x.msg));
let s = Ed25519.sign ~key:priv x.msg in
Alcotest.(check bool __LOC__ true (String.equal s x.sig_))
in
name, `Quick, f
| Acceptable -> assert false
let to_ed25519_keys (key : eddsa_key) =
match Ed25519.priv_of_octets key.sk, Ed25519.pub_of_octets key.pk with
| Ok priv, Ok pub ->
assert (String.equal Ed25519.(pub_to_octets (pub_of_priv priv)) key.pk);
priv, pub
| _ -> assert false
let ed25519_tests =
let data = load_file_exn "eddsa_test.json" in
let groups : eddsa_test_group list =
List.map eddsa_test_group_exn data.testGroups
in
List.concat_map (fun (group : eddsa_test_group) ->
let keys = to_ed25519_keys group.key in
List.map (to_ed25519_test keys) group.tests)
groups
let () =
Alcotest.run "Wycheproof NIST curves" [
("ECDH P256 test vectors", ecdh_tests "ecdh_secp256r1_test.json") ;
("ECDSA P256 test vectors (SHA256)",
ecdsa_tests "ecdsa_secp256r1_sha256_test.json") ;
("ECDSA P256 test vectors (SHA512)",
ecdsa_tests "ecdsa_secp256r1_sha512_test.json") ;
("ECDH P384 test vectors", ecdh_tests "ecdh_secp384r1_test.json") ;
("ECDSA P384 test vectors (SHA384)",
ecdsa_tests "ecdsa_secp384r1_sha384_test.json") ;
("ECDSA P384 test vectors (SHA512)",
ecdsa_tests "ecdsa_secp384r1_sha512_test.json") ;
("ECDH P521 test vectors", ecdh_tests "ecdh_secp521r1_test.json") ;
("ECDSA P521 test vectors (SHA512)",
ecdsa_tests "ecdsa_secp521r1_sha512_test.json") ;
("X25519 test vectors", x25519_tests) ;
("ED25519 test vectors", ed25519_tests) ;
]