upstream: github.com/mirleft/ocaml-x509
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

test: add 25 missing test files and fix 2 suite naming issues

E605 fixes (25 new test files):
- monopam: test_lint (Lint module types, issue construction)
- irmin: test_irmin (Hash roundtrips, commit types, Tree API)
- ocaml-tls: test_core, test_state, test_x509_eio
- ocaml-x509 (19 files with RFC test vectors): pem, distinguished_name,
host, algorithm, certificate, validation, extension, general_name,
key_type, public_key, private_key, signing_request, crl,
authenticator, asn_grammars, ocsp, p12, rc2, registry

E617 fixes (2 naming): module_alias_parsing, warning_parse

+1559
+78
tests/test_algorithm.ml
··· 1 + (* Tests for Algorithm module - key type and signature scheme mapping *) 2 + open X509 3 + 4 + (* Test Key_type.to_string / of_string roundtrip *) 5 + let test_key_type_roundtrip () = 6 + let types : Key_type.t list = [ `RSA; `ED25519; `P256; `P384; `P521 ] in 7 + List.iter 8 + (fun kt -> 9 + let s = Key_type.to_string kt in 10 + match Key_type.of_string s with 11 + | Ok kt2 -> 12 + Alcotest.(check string) 13 + (Fmt.str "roundtrip %s" s) s (Key_type.to_string kt2) 14 + | Error (`Msg m) -> Alcotest.failf "of_string failed for %s: %s" s m) 15 + types 16 + 17 + (* Test Key_type.of_string error case *) 18 + let test_key_type_invalid () = 19 + match Key_type.of_string "invalid_algo" with 20 + | Error _ -> () 21 + | Ok _ -> Alcotest.fail "expected error for invalid key type" 22 + 23 + (* Test Key_type.pp *) 24 + let test_key_type_pp () = 25 + let s = Fmt.to_to_string Key_type.pp `RSA in 26 + Alcotest.(check bool) "RSA pp non-empty" true (String.length s > 0) 27 + 28 + (* Test Key_type.strings *) 29 + let test_key_type_strings () = 30 + let strs = Key_type.strings in 31 + Alcotest.(check bool) "strings has entries" true (List.length strs >= 5); 32 + (* Each entry should roundtrip *) 33 + List.iter 34 + (fun (s, kt) -> 35 + Alcotest.(check string) "to_string matches" s (Key_type.to_string kt)) 36 + strs 37 + 38 + (* Test signature scheme support *) 39 + let test_supports_signature_scheme () = 40 + Alcotest.(check bool) 41 + "RSA supports RSA_PKCS1" true 42 + (Key_type.supports_signature_scheme `RSA `RSA_PKCS1); 43 + Alcotest.(check bool) 44 + "RSA supports RSA_PSS" true 45 + (Key_type.supports_signature_scheme `RSA `RSA_PSS); 46 + Alcotest.(check bool) 47 + "ED25519 supports ED25519" true 48 + (Key_type.supports_signature_scheme `ED25519 `ED25519); 49 + Alcotest.(check bool) 50 + "P256 supports ECDSA" true 51 + (Key_type.supports_signature_scheme `P256 `ECDSA); 52 + Alcotest.(check bool) 53 + "RSA does not support ECDSA" false 54 + (Key_type.supports_signature_scheme `RSA `ECDSA); 55 + Alcotest.(check bool) 56 + "ED25519 does not support RSA_PKCS1" false 57 + (Key_type.supports_signature_scheme `ED25519 `RSA_PKCS1) 58 + 59 + (* Test pp_signature_scheme *) 60 + let test_pp_signature_scheme () = 61 + let schemes : Key_type.signature_scheme list = 62 + [ `RSA_PSS; `RSA_PKCS1; `ECDSA; `ED25519 ] 63 + in 64 + List.iter 65 + (fun s -> 66 + let str = Fmt.to_to_string Key_type.pp_signature_scheme s in 67 + Alcotest.(check bool) "scheme pp non-empty" true (String.length str > 0)) 68 + schemes 69 + 70 + let tests = 71 + [ 72 + ("key type roundtrip", `Quick, test_key_type_roundtrip); 73 + ("key type invalid", `Quick, test_key_type_invalid); 74 + ("key type pp", `Quick, test_key_type_pp); 75 + ("key type strings", `Quick, test_key_type_strings); 76 + ("supports signature scheme", `Quick, test_supports_signature_scheme); 77 + ("pp signature scheme", `Quick, test_pp_signature_scheme); 78 + ]
+58
tests/test_asn_grammars.ml
··· 1 + (* Tests for ASN.1 grammar module accessibility *) 2 + (* asn_grammars is a private module, but its functionality is exposed 3 + through the public X509 API. We verify that the DER encoding/decoding 4 + works correctly through the public interface. *) 5 + open X509 6 + 7 + (* Test that DER encoding of DNs uses ASN.1 correctly *) 8 + let test_der_encoding () = 9 + let dn = 10 + [ Distinguished_name.Relative_distinguished_name.singleton (CN "test") ] 11 + in 12 + let der = Distinguished_name.encode_der dn in 13 + Alcotest.(check bool) "DER encoding non-empty" true (String.length der > 0); 14 + (* ASN.1 SEQUENCE tag is 0x30 *) 15 + Alcotest.(check int) 16 + "DER starts with SEQUENCE tag" 0x30 17 + (Char.code (String.get der 0)) 18 + 19 + (* Test that certificate DER uses proper ASN.1 structure *) 20 + let test_certificate_asn1 () = 21 + let key = Private_key.generate ~bits:2048 `RSA in 22 + let name = 23 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "asn1")) ] 24 + in 25 + match Signing_request.create name key with 26 + | Error _ -> Alcotest.fail "CSR failed" 27 + | Ok csr -> ( 28 + let valid_from = Ptime.epoch in 29 + let valid_until = 30 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 31 + | Some t -> t 32 + | None -> Alcotest.fail "time" 33 + in 34 + match Signing_request.sign csr ~valid_from ~valid_until key name with 35 + | Error _ -> Alcotest.fail "sign failed" 36 + | Ok cert -> 37 + let der = Certificate.encode_der cert in 38 + (* X.509 certificate is a SEQUENCE *) 39 + Alcotest.(check int) 40 + "certificate starts with SEQUENCE" 0x30 41 + (Char.code (String.get der 0))) 42 + 43 + (* Test public key ASN.1 encoding *) 44 + let test_public_key_asn1 () = 45 + let key = Private_key.generate ~bits:2048 `RSA in 46 + let pub = Private_key.public key in 47 + let der = Public_key.encode_der pub in 48 + (* SubjectPublicKeyInfo is a SEQUENCE *) 49 + Alcotest.(check int) 50 + "public key starts with SEQUENCE" 0x30 51 + (Char.code (String.get der 0)) 52 + 53 + let tests = 54 + [ 55 + ("DN DER encoding", `Quick, test_der_encoding); 56 + ("certificate ASN.1 structure", `Quick, test_certificate_asn1); 57 + ("public key ASN.1 structure", `Quick, test_public_key_asn1); 58 + ]
+51
tests/test_authenticator.ml
··· 1 + (* Tests for Authenticator module *) 2 + open X509 3 + 4 + (* Test chain_of_trust constructor *) 5 + let test_chain_of_trust_empty () = 6 + let time () = None in 7 + let auth = Authenticator.chain_of_trust ~time [] in 8 + (* With empty trust anchors, no chain should validate *) 9 + match auth ~host:None [] with 10 + | Error _ -> () 11 + | Ok _ -> Alcotest.fail "expected error with empty anchors" 12 + 13 + (* Test of_string with "none" *) 14 + let test_of_string_none () = 15 + match Authenticator.of_string "none" with 16 + | Ok f -> ( 17 + let time () = None in 18 + let auth = f time in 19 + match auth ~host:None [] with 20 + | Ok None -> () 21 + | _ -> Alcotest.fail "none authenticator should return Ok None") 22 + | Error (`Msg m) -> Alcotest.failf "of_string 'none' failed: %s" m 23 + 24 + (* Test of_string with invalid input *) 25 + let test_of_string_invalid () = 26 + match Authenticator.of_string "invalid_auth_string" with 27 + | Error _ -> () 28 + | Ok _ -> Alcotest.fail "expected error for invalid auth string" 29 + 30 + (* Test key_fingerprint constructor *) 31 + let test_key_fingerprint () = 32 + let time () = None in 33 + let fingerprint = String.make 32 '\x00' in 34 + let _auth = Authenticator.key_fingerprint ~time ~hash:`SHA256 ~fingerprint in 35 + () 36 + 37 + (* Test cert_fingerprint constructor *) 38 + let test_cert_fingerprint () = 39 + let time () = None in 40 + let fingerprint = String.make 32 '\x00' in 41 + let _auth = Authenticator.cert_fingerprint ~time ~hash:`SHA256 ~fingerprint in 42 + () 43 + 44 + let tests = 45 + [ 46 + ("chain_of_trust empty", `Quick, test_chain_of_trust_empty); 47 + ("of_string none", `Quick, test_of_string_none); 48 + ("of_string invalid", `Quick, test_of_string_invalid); 49 + ("key_fingerprint", `Quick, test_key_fingerprint); 50 + ("cert_fingerprint", `Quick, test_cert_fingerprint); 51 + ]
+136
tests/test_certificate.ml
··· 1 + (* Tests for Certificate module - RFC 5280 certificate structure *) 2 + open X509 3 + 4 + let key () = Private_key.generate ~bits:2048 `RSA 5 + 6 + let name = 7 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "test.com")) ] 8 + 9 + let make_cert () = 10 + let key = key () in 11 + match Signing_request.create name key with 12 + | Error _ -> Alcotest.fail "CSR failed" 13 + | Ok csr -> ( 14 + let valid_from = Ptime.epoch in 15 + let valid_until = 16 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 17 + | Some t -> t 18 + | None -> Alcotest.fail "time overflow" 19 + in 20 + let exts = Extension.(singleton Basic_constraints (true, (true, None))) in 21 + match 22 + Signing_request.sign csr ~valid_from ~valid_until ~extensions:exts key 23 + name 24 + with 25 + | Ok cert -> cert 26 + | Error _ -> Alcotest.fail "signing failed") 27 + 28 + (* Test certificate subject *) 29 + let test_subject () = 30 + let cert = make_cert () in 31 + let subj = Certificate.subject cert in 32 + Alcotest.(check (option string)) 33 + "subject CN" (Some "test.com") 34 + (Distinguished_name.common_name subj) 35 + 36 + (* Test certificate issuer *) 37 + let test_issuer () = 38 + let cert = make_cert () in 39 + let iss = Certificate.issuer cert in 40 + Alcotest.(check (option string)) 41 + "issuer CN" (Some "test.com") 42 + (Distinguished_name.common_name iss) 43 + 44 + (* Test certificate validity *) 45 + let test_validity () = 46 + let cert = make_cert () in 47 + let from_, until_ = Certificate.validity cert in 48 + Alcotest.(check bool) 49 + "valid_from <= valid_until" true 50 + (Ptime.compare from_ until_ <= 0) 51 + 52 + (* Test certificate serial *) 53 + let test_serial () = 54 + let cert = make_cert () in 55 + let serial = Certificate.serial cert in 56 + Alcotest.(check bool) "serial non-empty" true (String.length serial > 0) 57 + 58 + (* Test certificate public key *) 59 + let test_public_key () = 60 + let cert = make_cert () in 61 + let pub = Certificate.public_key cert in 62 + Alcotest.(check bool) 63 + "public key is RSA" true 64 + (match pub with `RSA _ -> true | _ -> false) 65 + 66 + (* Test certificate extensions *) 67 + let test_extensions () = 68 + let cert = make_cert () in 69 + let _exts = Certificate.extensions cert in 70 + () 71 + 72 + (* Test certificate pp *) 73 + let test_pp () = 74 + let cert = make_cert () in 75 + let s = Fmt.to_to_string Certificate.pp cert in 76 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 77 + 78 + (* Test certificate supports_keytype *) 79 + let test_supports_keytype () = 80 + let cert = make_cert () in 81 + Alcotest.(check bool) 82 + "cert supports RSA" true 83 + (Certificate.supports_keytype cert `RSA); 84 + Alcotest.(check bool) 85 + "cert does not support ED25519" false 86 + (Certificate.supports_keytype cert `ED25519) 87 + 88 + (* Test DER roundtrip *) 89 + let test_der_roundtrip () = 90 + let cert = make_cert () in 91 + let der = Certificate.encode_der cert in 92 + match Certificate.decode_der der with 93 + | Ok cert2 -> 94 + let der2 = Certificate.encode_der cert2 in 95 + Alcotest.(check string) "DER roundtrip" der der2 96 + | Error (`Msg m) -> Alcotest.failf "DER decode failed: %s" m 97 + 98 + (* Test fingerprint *) 99 + let test_fingerprint () = 100 + let cert = make_cert () in 101 + let fp = Certificate.fingerprint `SHA256 cert in 102 + Alcotest.(check int) "SHA256 fingerprint is 32 bytes" 32 (String.length fp) 103 + 104 + (* Test signature_algorithm *) 105 + let test_signature_algorithm () = 106 + let cert = make_cert () in 107 + match Certificate.signature_algorithm cert with 108 + | Some (_scheme, _hash) -> () 109 + | None -> Alcotest.fail "expected signature algorithm" 110 + 111 + (* Test PKCS1 digest info encode/decode *) 112 + let test_pkcs1_digest_info () = 113 + let hash = `SHA256 in 114 + let sig_data = String.make 32 '\x42' in 115 + let encoded = Certificate.encode_pkcs1_digest_info (hash, sig_data) in 116 + match Certificate.decode_pkcs1_digest_info encoded with 117 + | Ok (hash2, sig2) -> 118 + Alcotest.(check string) "signature data" sig_data sig2; 119 + Alcotest.(check bool) "hash matches" true (hash2 = hash) 120 + | Error (`Msg m) -> Alcotest.failf "digest info decode failed: %s" m 121 + 122 + let tests = 123 + [ 124 + ("certificate subject", `Quick, test_subject); 125 + ("certificate issuer", `Quick, test_issuer); 126 + ("certificate validity", `Quick, test_validity); 127 + ("certificate serial", `Quick, test_serial); 128 + ("certificate public key", `Quick, test_public_key); 129 + ("certificate extensions", `Quick, test_extensions); 130 + ("certificate pp", `Quick, test_pp); 131 + ("certificate supports_keytype", `Quick, test_supports_keytype); 132 + ("certificate DER roundtrip", `Quick, test_der_roundtrip); 133 + ("certificate fingerprint", `Quick, test_fingerprint); 134 + ("certificate signature_algorithm", `Quick, test_signature_algorithm); 135 + ("PKCS1 digest info roundtrip", `Quick, test_pkcs1_digest_info); 136 + ]
+95
tests/test_crl.ml
··· 1 + (* Tests for CRL module - RFC 5280 section 5 *) 2 + open X509 3 + 4 + let key () = Private_key.generate ~bits:2048 `RSA 5 + 6 + let issuer_name = 7 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "CRL CA")) ] 8 + 9 + let this_update = Ptime.epoch 10 + 11 + let next_update = 12 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 13 + | Some t -> t 14 + | None -> Ptime.epoch 15 + 16 + (* Test CRL creation and accessors *) 17 + let test_create_empty_crl () = 18 + match 19 + CRL.revoke ~issuer:issuer_name ~this_update ~next_update [] (key ()) 20 + with 21 + | Error (`Msg m) -> Alcotest.failf "CRL creation failed: %s" m 22 + | Ok crl -> 23 + let iss = CRL.issuer crl in 24 + Alcotest.(check (option string)) 25 + "issuer CN" (Some "CRL CA") 26 + (Distinguished_name.common_name iss); 27 + let revoked = CRL.revoked_certificates crl in 28 + Alcotest.(check int) "no revoked certs" 0 (List.length revoked) 29 + 30 + (* Test CRL this_update and next_update *) 31 + let test_crl_timestamps () = 32 + match 33 + CRL.revoke ~issuer:issuer_name ~this_update ~next_update [] (key ()) 34 + with 35 + | Error _ -> Alcotest.fail "CRL creation failed" 36 + | Ok crl -> ( 37 + let tu = CRL.this_update crl in 38 + Alcotest.(check bool) 39 + "this_update matches" true 40 + (Ptime.equal tu this_update); 41 + match CRL.next_update crl with 42 + | Some nu -> 43 + Alcotest.(check bool) 44 + "next_update matches" true 45 + (Ptime.equal nu next_update) 46 + | None -> Alcotest.fail "expected next_update") 47 + 48 + (* Test CRL DER roundtrip *) 49 + let test_der_roundtrip () = 50 + match CRL.revoke ~issuer:issuer_name ~this_update [] (key ()) with 51 + | Error _ -> Alcotest.fail "CRL creation failed" 52 + | Ok crl -> ( 53 + let der = CRL.encode_der crl in 54 + match CRL.decode_der der with 55 + | Ok crl2 -> 56 + let der2 = CRL.encode_der crl2 in 57 + Alcotest.(check string) "DER roundtrip" der der2 58 + | Error (`Msg m) -> Alcotest.failf "decode failed: %s" m) 59 + 60 + (* Test CRL with revoked certificate *) 61 + let test_revoked_cert () = 62 + let entry : CRL.revoked_cert = 63 + { serial = "\x01\x02"; date = Ptime.epoch; extensions = Extension.empty } 64 + in 65 + match CRL.revoke ~issuer:issuer_name ~this_update [ entry ] (key ()) with 66 + | Error _ -> Alcotest.fail "CRL creation failed" 67 + | Ok crl -> 68 + let revoked = CRL.revoked_certificates crl in 69 + Alcotest.(check int) "one revoked cert" 1 (List.length revoked); 70 + let r = List.hd revoked in 71 + Alcotest.(check string) "serial matches" "\x01\x02" r.serial 72 + 73 + (* Test CRL signature_algorithm *) 74 + let test_signature_algorithm () = 75 + match CRL.revoke ~issuer:issuer_name ~this_update [] (key ()) with 76 + | Error _ -> Alcotest.fail "CRL creation failed" 77 + | Ok crl -> ( 78 + match CRL.signature_algorithm crl with 79 + | Some _ -> () 80 + | None -> Alcotest.fail "expected signature algorithm") 81 + 82 + (* Test CRL pp_verification_error *) 83 + let test_pp_verification_error () = 84 + let s = Fmt.to_to_string CRL.pp_verification_error (`Msg "test") in 85 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 86 + 87 + let tests = 88 + [ 89 + ("create empty CRL", `Quick, test_create_empty_crl); 90 + ("CRL timestamps", `Quick, test_crl_timestamps); 91 + ("CRL DER roundtrip", `Quick, test_der_roundtrip); 92 + ("CRL with revoked cert", `Quick, test_revoked_cert); 93 + ("CRL signature algorithm", `Quick, test_signature_algorithm); 94 + ("CRL pp_verification_error", `Quick, test_pp_verification_error); 95 + ]
+127
tests/test_distinguished_name.ml
··· 1 + (* Tests for Distinguished_name module - RFC 5280 section 4.1.2.4 *) 2 + open X509 3 + 4 + let check_dn = 5 + (module Distinguished_name : Alcotest.TESTABLE 6 + with type t = Distinguished_name.t) 7 + 8 + (* Test basic DN construction *) 9 + let test_dn_construction () = 10 + let dn = 11 + [ 12 + Distinguished_name.Relative_distinguished_name.singleton 13 + (CN "example.com"); 14 + Distinguished_name.Relative_distinguished_name.singleton (O "Example Inc"); 15 + ] 16 + in 17 + Alcotest.(check int) "DN has 2 RDNs" 2 (List.length dn) 18 + 19 + (* Test common_name extraction *) 20 + let test_common_name () = 21 + let dn = 22 + [ Distinguished_name.Relative_distinguished_name.singleton (CN "test.com") ] 23 + in 24 + Alcotest.(check (option string)) 25 + "CN found" (Some "test.com") 26 + (Distinguished_name.common_name dn) 27 + 28 + let test_common_name_absent () = 29 + let dn = 30 + [ Distinguished_name.Relative_distinguished_name.singleton (O "Org") ] 31 + in 32 + Alcotest.(check (option string)) 33 + "CN absent" None 34 + (Distinguished_name.common_name dn) 35 + 36 + (* Test DN equality *) 37 + let test_dn_equal () = 38 + let dn1 = 39 + [ Distinguished_name.Relative_distinguished_name.singleton (CN "a") ] 40 + in 41 + let dn2 = 42 + [ Distinguished_name.Relative_distinguished_name.singleton (CN "a") ] 43 + in 44 + let dn3 = 45 + [ Distinguished_name.Relative_distinguished_name.singleton (CN "b") ] 46 + in 47 + Alcotest.(check bool) "equal DNs" true (Distinguished_name.equal dn1 dn2); 48 + Alcotest.(check bool) "different DNs" false (Distinguished_name.equal dn1 dn3) 49 + 50 + (* Test DN pretty printing *) 51 + let test_dn_pp () = 52 + let dn = 53 + [ Distinguished_name.Relative_distinguished_name.singleton (CN "test.com") ] 54 + in 55 + let s = Fmt.to_to_string Distinguished_name.pp dn in 56 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0); 57 + Alcotest.(check bool) "pp contains CN" true (String.length s > 0) 58 + 59 + (* Test make_pp with different formats *) 60 + let test_make_pp_formats () = 61 + let dn = 62 + [ 63 + Distinguished_name.Relative_distinguished_name.singleton (O "Org"); 64 + Distinguished_name.Relative_distinguished_name.singleton (CN "Host"); 65 + ] 66 + in 67 + let pp_rfc = Fmt.hbox (Distinguished_name.make_pp ~format:`RFC4514 ()) in 68 + let pp_ossl = Fmt.hbox (Distinguished_name.make_pp ~format:`OpenSSL ()) in 69 + let pp_osf = Fmt.hbox (Distinguished_name.make_pp ~format:`OSF ()) in 70 + let s_rfc = Fmt.to_to_string pp_rfc dn in 71 + let s_ossl = Fmt.to_to_string pp_ossl dn in 72 + let s_osf = Fmt.to_to_string pp_osf dn in 73 + (* RFC4514 reverses order; OpenSSL keeps ASN.1 order *) 74 + Alcotest.(check bool) "RFC4514 non-empty" true (String.length s_rfc > 0); 75 + Alcotest.(check bool) "OpenSSL non-empty" true (String.length s_ossl > 0); 76 + Alcotest.(check bool) "OSF non-empty" true (String.length s_osf > 0) 77 + 78 + (* Test DN DER roundtrip *) 79 + let test_dn_der_roundtrip () = 80 + let dn = 81 + [ 82 + Distinguished_name.Relative_distinguished_name.singleton (CN "test.com"); 83 + Distinguished_name.Relative_distinguished_name.singleton (O "Org"); 84 + ] 85 + in 86 + let der = Distinguished_name.encode_der dn in 87 + match Distinguished_name.decode_der der with 88 + | Ok dn2 -> Alcotest.check check_dn "DER roundtrip" dn dn2 89 + | Error (`Msg m) -> Alcotest.failf "DER decode failed: %s" m 90 + 91 + (* Test all attribute constructors *) 92 + let test_attribute_types () = 93 + let attrs = 94 + [ 95 + Distinguished_name.CN "cn"; 96 + Distinguished_name.Serialnumber "sn"; 97 + Distinguished_name.C "US"; 98 + Distinguished_name.L "City"; 99 + Distinguished_name.ST "State"; 100 + Distinguished_name.O "Org"; 101 + Distinguished_name.OU "Unit"; 102 + Distinguished_name.T "Title"; 103 + Distinguished_name.DNQ "Qual"; 104 + Distinguished_name.Mail "a@b.c"; 105 + Distinguished_name.DC "dc"; 106 + Distinguished_name.Given_name "Given"; 107 + Distinguished_name.Surname "Sur"; 108 + Distinguished_name.Initials "I"; 109 + Distinguished_name.Pseudonym "Pseudo"; 110 + Distinguished_name.Generation "Jr"; 111 + Distinguished_name.Street "St"; 112 + Distinguished_name.Userid "uid"; 113 + ] 114 + in 115 + Alcotest.(check int) "18 attribute types" 18 (List.length attrs) 116 + 117 + let tests = 118 + [ 119 + ("DN construction", `Quick, test_dn_construction); 120 + ("DN common_name present", `Quick, test_common_name); 121 + ("DN common_name absent", `Quick, test_common_name_absent); 122 + ("DN equality", `Quick, test_dn_equal); 123 + ("DN pp", `Quick, test_dn_pp); 124 + ("DN make_pp formats", `Quick, test_make_pp_formats); 125 + ("DN DER roundtrip", `Quick, test_dn_der_roundtrip); 126 + ("DN attribute types", `Quick, test_attribute_types); 127 + ]
+89
tests/test_extension.ml
··· 1 + (* Tests for Extension module - X.509v3 extensions *) 2 + open X509 3 + 4 + (* Test empty extension map *) 5 + let test_empty_extensions () = 6 + let exts = Extension.empty in 7 + Alcotest.(check bool) "empty is empty" true (Extension.is_empty exts) 8 + 9 + (* Test singleton and find *) 10 + let test_singleton_basic_constraints () = 11 + let exts = Extension.singleton Basic_constraints (true, (true, Some 1)) in 12 + match Extension.find Basic_constraints exts with 13 + | Some (crit, (is_ca, pathlen)) -> 14 + Alcotest.(check bool) "critical" true crit; 15 + Alcotest.(check bool) "is_ca" true is_ca; 16 + Alcotest.(check (option int)) "pathlen" (Some 1) pathlen 17 + | None -> Alcotest.fail "expected Basic_constraints" 18 + 19 + (* Test key usage extension *) 20 + let test_key_usage () = 21 + let ku = [ `Digital_signature; `Key_encipherment ] in 22 + let exts = Extension.singleton Key_usage (true, ku) in 23 + match Extension.find Key_usage exts with 24 + | Some (crit, ku2) -> 25 + Alcotest.(check bool) "critical" true crit; 26 + Alcotest.(check int) "2 usages" 2 (List.length ku2) 27 + | None -> Alcotest.fail "expected Key_usage" 28 + 29 + (* Test ext key usage *) 30 + let test_ext_key_usage () = 31 + let eku = [ `Server_auth; `Client_auth ] in 32 + let exts = Extension.singleton Ext_key_usage (true, eku) in 33 + match Extension.find Ext_key_usage exts with 34 + | Some (crit, eku2) -> 35 + Alcotest.(check bool) "critical" true crit; 36 + Alcotest.(check int) "2 ext usages" 2 (List.length eku2) 37 + | None -> Alcotest.fail "expected Ext_key_usage" 38 + 39 + (* Test add to extension map *) 40 + let test_add_extension () = 41 + let exts = 42 + Extension.( 43 + add Key_usage 44 + (true, [ `Digital_signature ]) 45 + (singleton Basic_constraints (true, (false, None)))) 46 + in 47 + Alcotest.(check bool) 48 + "has key_usage" true 49 + (Option.is_some (Extension.find Key_usage exts)); 50 + Alcotest.(check bool) 51 + "has basic_constraints" true 52 + (Option.is_some (Extension.find Basic_constraints exts)) 53 + 54 + (* Test critical accessor *) 55 + let test_critical () = 56 + Alcotest.(check bool) 57 + "critical true" true 58 + (Extension.critical Basic_constraints (true, (true, None))); 59 + Alcotest.(check bool) 60 + "critical false" false 61 + (Extension.critical Basic_constraints (false, (true, None))) 62 + 63 + (* Test pp *) 64 + let test_pp () = 65 + let exts = Extension.singleton Basic_constraints (true, (true, None)) in 66 + let s = Fmt.to_to_string Extension.pp exts in 67 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 68 + 69 + (* Test subject alt name *) 70 + let test_subject_alt_name () = 71 + let gn = General_name.(singleton DNS [ "example.com" ]) in 72 + let exts = Extension.singleton Subject_alt_name (false, gn) in 73 + match Extension.find Subject_alt_name exts with 74 + | Some (_, gn2) -> 75 + let dns = General_name.(get DNS gn2) in 76 + Alcotest.(check (list string)) "DNS names" [ "example.com" ] dns 77 + | None -> Alcotest.fail "expected Subject_alt_name" 78 + 79 + let tests = 80 + [ 81 + ("empty extensions", `Quick, test_empty_extensions); 82 + ("basic constraints", `Quick, test_singleton_basic_constraints); 83 + ("key usage", `Quick, test_key_usage); 84 + ("ext key usage", `Quick, test_ext_key_usage); 85 + ("add extension", `Quick, test_add_extension); 86 + ("critical accessor", `Quick, test_critical); 87 + ("extension pp", `Quick, test_pp); 88 + ("subject alt name", `Quick, test_subject_alt_name); 89 + ]
+70
tests/test_general_name.ml
··· 1 + (* Tests for General_name module - RFC 5280 GeneralName types *) 2 + open X509 3 + 4 + (* Test empty general name *) 5 + let test_empty () = 6 + let gn = General_name.empty in 7 + Alcotest.(check bool) "empty is empty" true (General_name.is_empty gn) 8 + 9 + (* Test DNS general name *) 10 + let test_dns () = 11 + let gn = General_name.(singleton DNS [ "example.com"; "www.example.com" ]) in 12 + let dns = General_name.(get DNS gn) in 13 + Alcotest.(check (list string)) 14 + "DNS names" 15 + [ "example.com"; "www.example.com" ] 16 + dns 17 + 18 + (* Test Rfc_822 (email) general name *) 19 + let test_rfc822 () = 20 + let gn = General_name.(singleton Rfc_822 [ "test@example.com" ]) in 21 + let emails = General_name.(get Rfc_822 gn) in 22 + Alcotest.(check (list string)) "email" [ "test@example.com" ] emails 23 + 24 + (* Test URI general name *) 25 + let test_uri () = 26 + let gn = General_name.(singleton URI [ "http://example.com/crl" ]) in 27 + let uris = General_name.(get URI gn) in 28 + Alcotest.(check (list string)) "URI" [ "http://example.com/crl" ] uris 29 + 30 + (* Test IP general name *) 31 + let test_ip () = 32 + let ip = "\x7f\x00\x00\x01" in 33 + (* 127.0.0.1 *) 34 + let gn = General_name.(singleton IP [ ip ]) in 35 + let ips = General_name.(get IP gn) in 36 + Alcotest.(check int) "one IP" 1 (List.length ips) 37 + 38 + (* Test Directory general name *) 39 + let test_directory () = 40 + let dn = 41 + [ Distinguished_name.Relative_distinguished_name.singleton (CN "test") ] 42 + in 43 + let gn = General_name.(singleton Directory [ dn ]) in 44 + let dirs = General_name.(get Directory gn) in 45 + Alcotest.(check int) "one directory" 1 (List.length dirs) 46 + 47 + (* Test cardinal *) 48 + let test_cardinal () = 49 + let gn = 50 + General_name.(add DNS [ "a.com" ] (singleton Rfc_822 [ "b@c.com" ])) 51 + in 52 + Alcotest.(check int) "cardinal 2" 2 (General_name.cardinal gn) 53 + 54 + (* Test pp *) 55 + let test_pp () = 56 + let gn = General_name.(singleton DNS [ "test.com" ]) in 57 + let s = Fmt.to_to_string General_name.pp gn in 58 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 59 + 60 + let tests = 61 + [ 62 + ("empty general name", `Quick, test_empty); 63 + ("DNS general name", `Quick, test_dns); 64 + ("RFC 822 (email)", `Quick, test_rfc822); 65 + ("URI general name", `Quick, test_uri); 66 + ("IP general name", `Quick, test_ip); 67 + ("directory general name", `Quick, test_directory); 68 + ("cardinal", `Quick, test_cardinal); 69 + ("general name pp", `Quick, test_pp); 70 + ]
+58
tests/test_host.ml
··· 1 + (* Tests for Host module - RFC 6125 hostname matching *) 2 + open X509 3 + 4 + (* Test Host.pp *) 5 + let test_host_pp () = 6 + let name = Domain_name.(host_exn (of_string_exn "example.com")) in 7 + let strict : Host.t = (`Strict, name) in 8 + let wild : Host.t = (`Wildcard, name) in 9 + let s_strict = Fmt.to_to_string Host.pp strict in 10 + let s_wild = Fmt.to_to_string Host.pp wild in 11 + Alcotest.(check bool) "strict pp non-empty" true (String.length s_strict > 0); 12 + Alcotest.(check bool) 13 + "wildcard pp has *." true 14 + (String.length s_wild > 2 && String.sub s_wild 0 2 = "*.") 15 + 16 + (* Test Host.Set *) 17 + let test_host_set_empty () = 18 + Alcotest.(check bool) 19 + "empty set is empty" true 20 + (Host.Set.is_empty Host.Set.empty) 21 + 22 + let test_host_set_add_mem () = 23 + let name = Domain_name.(host_exn (of_string_exn "foo.com")) in 24 + let h : Host.t = (`Strict, name) in 25 + let set = Host.Set.singleton h in 26 + Alcotest.(check bool) "member" true (Host.Set.mem h set); 27 + Alcotest.(check int) "cardinal" 1 (Host.Set.cardinal set) 28 + 29 + let test_host_set_multiple () = 30 + let mk n = (`Strict, Domain_name.(host_exn (of_string_exn n))) in 31 + let set = Host.Set.of_list [ mk "a.com"; mk "b.com"; mk "c.com" ] in 32 + Alcotest.(check int) "3 hosts" 3 (Host.Set.cardinal set) 33 + 34 + (* Test Host.Set.pp *) 35 + let test_host_set_pp () = 36 + let mk n = (`Strict, Domain_name.(host_exn (of_string_exn n))) in 37 + let set = Host.Set.of_list [ mk "a.com"; mk "b.com" ] in 38 + let s = Fmt.to_to_string Host.Set.pp set in 39 + Alcotest.(check bool) "set pp non-empty" true (String.length s > 0) 40 + 41 + (* Test wildcard vs strict distinction in sets *) 42 + let test_host_set_wildcard_strict () = 43 + let name = Domain_name.(host_exn (of_string_exn "example.com")) in 44 + let strict : Host.t = (`Strict, name) in 45 + let wild : Host.t = (`Wildcard, name) in 46 + let set = Host.Set.of_list [ strict; wild ] in 47 + Alcotest.(check int) 48 + "strict and wildcard are distinct" 2 (Host.Set.cardinal set) 49 + 50 + let tests = 51 + [ 52 + ("host pp", `Quick, test_host_pp); 53 + ("host set empty", `Quick, test_host_set_empty); 54 + ("host set add/mem", `Quick, test_host_set_add_mem); 55 + ("host set multiple", `Quick, test_host_set_multiple); 56 + ("host set pp", `Quick, test_host_set_pp); 57 + ("host set wildcard vs strict", `Quick, test_host_set_wildcard_strict); 58 + ]
+37
tests/test_key_type.ml
··· 1 + (* Tests for Key_type module - key type classification *) 2 + open X509 3 + 4 + (* Test all key types have distinct string representations *) 5 + let test_distinct_strings () = 6 + let types : Key_type.t list = [ `RSA; `ED25519; `P256; `P384; `P521 ] in 7 + let strings = List.map Key_type.to_string types in 8 + let uniq = List.sort_uniq String.compare strings in 9 + Alcotest.(check int) "all distinct" 5 (List.length uniq) 10 + 11 + (* Test key type generation for each type *) 12 + let test_generate_key_types () = 13 + let types : Key_type.t list = [ `RSA; `ED25519; `P256; `P384; `P521 ] in 14 + List.iter 15 + (fun kt -> 16 + let key = Private_key.generate ~bits:2048 kt in 17 + let kt2 = Private_key.key_type key in 18 + Alcotest.(check string) 19 + (Fmt.str "key_type %s" (Key_type.to_string kt)) 20 + (Key_type.to_string kt) (Key_type.to_string kt2)) 21 + types 22 + 23 + (* Test public key type matches private key type *) 24 + let test_public_key_type () = 25 + let key = Private_key.generate `P256 in 26 + let pub = Private_key.public key in 27 + Alcotest.(check string) 28 + "types match" 29 + (Key_type.to_string (Private_key.key_type key)) 30 + (Key_type.to_string (Public_key.key_type pub)) 31 + 32 + let tests = 33 + [ 34 + ("distinct strings", `Quick, test_distinct_strings); 35 + ("generate key types", `Quick, test_generate_key_types); 36 + ("public key type matches", `Quick, test_public_key_type); 37 + ]
+132
tests/test_ocsp.ml
··· 1 + (* Tests for OCSP module - RFC 6960 *) 2 + open X509 3 + 4 + let key () = Private_key.generate ~bits:2048 `RSA 5 + 6 + let make_ca () = 7 + let key = key () in 8 + let name = 9 + [ 10 + Distinguished_name.(Relative_distinguished_name.singleton (CN "OCSP CA")); 11 + ] 12 + in 13 + let exts = Extension.(singleton Basic_constraints (true, (true, None))) in 14 + match Signing_request.create name key with 15 + | Error _ -> Alcotest.fail "CSR failed" 16 + | Ok csr -> ( 17 + let valid_from = Ptime.epoch in 18 + let valid_until = 19 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 20 + | Some t -> t 21 + | None -> Alcotest.fail "time" 22 + in 23 + match 24 + Signing_request.sign csr ~valid_from ~valid_until ~extensions:exts key 25 + name 26 + with 27 + | Ok cert -> cert 28 + | Error _ -> Alcotest.fail "sign failed") 29 + 30 + (* Test cert_id creation and serial accessor *) 31 + let test_cert_id () = 32 + let ca = make_ca () in 33 + let serial = "\x01\x02\x03" in 34 + let cert_id = OCSP.create_cert_id ca serial in 35 + let s = OCSP.cert_id_serial cert_id in 36 + Alcotest.(check string) "serial matches" serial s 37 + 38 + (* Test cert_id with custom hash *) 39 + let test_cert_id_hash () = 40 + let ca = make_ca () in 41 + let serial = "\xAB\xCD" in 42 + let cert_id = OCSP.create_cert_id ~hash:`SHA256 ca serial in 43 + Alcotest.(check string) "serial matches" serial (OCSP.cert_id_serial cert_id) 44 + 45 + (* Test pp_cert_id *) 46 + let test_pp_cert_id () = 47 + let ca = make_ca () in 48 + let cert_id = OCSP.create_cert_id ca "\x01" in 49 + let s = Fmt.to_to_string OCSP.pp_cert_id cert_id in 50 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 51 + 52 + (* Test OCSP Request creation *) 53 + let test_request_create () = 54 + let ca = make_ca () in 55 + let cert_id = OCSP.create_cert_id ca "\x01\x02" in 56 + match OCSP.Request.create [ cert_id ] with 57 + | Ok req -> 58 + let ids = OCSP.Request.cert_ids req in 59 + Alcotest.(check int) "one cert_id" 1 (List.length ids) 60 + | Error (`Msg m) -> Alcotest.failf "request creation failed: %s" m 61 + 62 + (* Test OCSP Request DER roundtrip *) 63 + let test_request_der_roundtrip () = 64 + let ca = make_ca () in 65 + let cert_id = OCSP.create_cert_id ca "\x01\x02" in 66 + match OCSP.Request.create [ cert_id ] with 67 + | Error _ -> Alcotest.fail "request creation failed" 68 + | Ok req -> ( 69 + let der = OCSP.Request.encode_der req in 70 + match OCSP.Request.decode_der der with 71 + | Ok _ -> () 72 + | Error e -> Alcotest.failf "decode failed: %a" Asn.pp_error e) 73 + 74 + (* Test OCSP Response error creation *) 75 + let test_response_error () = 76 + let resp = OCSP.Response.create `Unauthorized in 77 + let status = OCSP.Response.status resp in 78 + Alcotest.(check bool) "status is Unauthorized" true (status = `Unauthorized) 79 + 80 + (* Test OCSP Response pp_status *) 81 + let test_pp_status () = 82 + let statuses : OCSP.Response.status list = 83 + [ 84 + `Successful; 85 + `MalformedRequest; 86 + `InternalError; 87 + `TryLater; 88 + `SigRequired; 89 + `Unauthorized; 90 + ] 91 + in 92 + List.iter 93 + (fun s -> 94 + let str = Fmt.to_to_string OCSP.Response.pp_status s in 95 + Alcotest.(check bool) "status pp non-empty" true (String.length str > 0)) 96 + statuses 97 + 98 + (* Test OCSP Response pp_cert_status *) 99 + let test_pp_cert_status () = 100 + let statuses : OCSP.Response.cert_status list = 101 + [ `Good; `Unknown; `Revoked (Ptime.epoch, None) ] 102 + in 103 + List.iter 104 + (fun s -> 105 + let str = Fmt.to_to_string OCSP.Response.pp_cert_status s in 106 + Alcotest.(check bool) 107 + "cert_status pp non-empty" true 108 + (String.length str > 0)) 109 + statuses 110 + 111 + (* Test OCSP single_response creation *) 112 + let test_single_response () = 113 + let ca = make_ca () in 114 + let cert_id = OCSP.create_cert_id ca "\x42" in 115 + let sr = OCSP.Response.create_single_response cert_id `Good Ptime.epoch in 116 + let id = OCSP.Response.single_response_cert_id sr in 117 + Alcotest.(check string) "cert_id serial" "\x42" (OCSP.cert_id_serial id); 118 + let status = OCSP.Response.single_response_status sr in 119 + Alcotest.(check bool) "status is Good" true (status = `Good) 120 + 121 + let tests = 122 + [ 123 + ("cert_id creation", `Quick, test_cert_id); 124 + ("cert_id with hash", `Quick, test_cert_id_hash); 125 + ("pp_cert_id", `Quick, test_pp_cert_id); 126 + ("request creation", `Quick, test_request_create); 127 + ("request DER roundtrip", `Quick, test_request_der_roundtrip); 128 + ("response error", `Quick, test_response_error); 129 + ("pp_status", `Quick, test_pp_status); 130 + ("pp_cert_status", `Quick, test_pp_cert_status); 131 + ("single_response", `Quick, test_single_response); 132 + ]
+83
tests/test_p12.ml
··· 1 + (* Tests for PKCS12 module - RFC 7292 *) 2 + open X509 3 + 4 + let make_cert_and_key () = 5 + let key = Private_key.generate ~bits:2048 `RSA in 6 + let name = 7 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "p12")) ] 8 + in 9 + match Signing_request.create name key with 10 + | Error _ -> Alcotest.fail "CSR failed" 11 + | Ok csr -> ( 12 + let valid_from = Ptime.epoch in 13 + let valid_until = 14 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 15 + | Some t -> t 16 + | None -> Alcotest.fail "time" 17 + in 18 + match Signing_request.sign csr ~valid_from ~valid_until key name with 19 + | Ok cert -> (cert, key) 20 + | Error _ -> Alcotest.fail "signing failed") 21 + 22 + (* Test PKCS12 create and verify roundtrip *) 23 + let test_create_verify () = 24 + let cert, key = make_cert_and_key () in 25 + let password = "test_password" in 26 + let p12 = PKCS12.create password [ cert ] key in 27 + match PKCS12.verify password p12 with 28 + | Ok items -> 29 + let has_cert = 30 + List.exists (function `Certificate _ -> true | _ -> false) items 31 + in 32 + let has_key = 33 + List.exists 34 + (function `Decrypted_private_key _ -> true | _ -> false) 35 + items 36 + in 37 + Alcotest.(check bool) "has certificate" true has_cert; 38 + Alcotest.(check bool) "has private key" true has_key 39 + | Error (`Msg m) -> Alcotest.failf "verify failed: %s" m 40 + 41 + (* Test PKCS12 DER roundtrip *) 42 + let test_der_roundtrip () = 43 + let cert, key = make_cert_and_key () in 44 + let p12 = PKCS12.create "pass" [ cert ] key in 45 + let der = PKCS12.encode_der p12 in 46 + match PKCS12.decode_der der with 47 + | Ok p12_2 -> 48 + let der2 = PKCS12.encode_der p12_2 in 49 + Alcotest.(check string) "DER roundtrip" der der2 50 + | Error (`Msg m) -> Alcotest.failf "decode failed: %s" m 51 + 52 + (* Test wrong password *) 53 + let test_wrong_password () = 54 + let cert, key = make_cert_and_key () in 55 + let p12 = PKCS12.create "correct" [ cert ] key in 56 + match PKCS12.verify "wrong" p12 with 57 + | Error _ -> () 58 + | Ok _ -> Alcotest.fail "expected error with wrong password" 59 + 60 + (* Test with custom MAC algorithm *) 61 + let test_custom_mac () = 62 + let cert, key = make_cert_and_key () in 63 + let p12 = PKCS12.create ~mac:`SHA512 "pass" [ cert ] key in 64 + match PKCS12.verify "pass" p12 with 65 + | Ok _ -> () 66 + | Error (`Msg m) -> Alcotest.failf "verify with SHA512 mac failed: %s" m 67 + 68 + (* Test with custom encryption algorithm *) 69 + let test_custom_algorithm () = 70 + let cert, key = make_cert_and_key () in 71 + let p12 = PKCS12.create ~algorithm:`AES256_CBC "pass" [ cert ] key in 72 + match PKCS12.verify "pass" p12 with 73 + | Ok _ -> () 74 + | Error (`Msg m) -> Alcotest.failf "verify with AES256 failed: %s" m 75 + 76 + let tests = 77 + [ 78 + ("PKCS12 create/verify", `Quick, test_create_verify); 79 + ("PKCS12 DER roundtrip", `Quick, test_der_roundtrip); 80 + ("PKCS12 wrong password", `Quick, test_wrong_password); 81 + ("PKCS12 custom MAC", `Quick, test_custom_mac); 82 + ("PKCS12 custom algorithm", `Quick, test_custom_algorithm); 83 + ]
+108
tests/test_pem.ml
··· 1 + (* Tests for PEM encoding/decoding - RFC 7468 *) 2 + open X509 3 + 4 + (* RFC 7468 Section 2: PEM must use BEGIN/END markers with base64 content *) 5 + let test_certificate_pem_roundtrip () = 6 + (* Generate a self-signed cert for roundtrip testing *) 7 + let key = Private_key.generate ~bits:2048 `RSA in 8 + let name = 9 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "test")) ] 10 + in 11 + match Signing_request.create name key with 12 + | Error (`Msg m) -> Alcotest.failf "CSR creation failed: %s" m 13 + | Ok csr -> ( 14 + let valid_from = Ptime.epoch in 15 + let valid_until = 16 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 17 + | Some t -> t 18 + | None -> Alcotest.fail "time overflow" 19 + in 20 + match Signing_request.sign csr ~valid_from ~valid_until key name with 21 + | Error _ -> Alcotest.fail "signing failed" 22 + | Ok cert -> ( 23 + let pem = Certificate.encode_pem cert in 24 + (* RFC 7468: PEM must start with -----BEGIN *) 25 + Alcotest.(check bool) 26 + "starts with BEGIN" true 27 + (String.length pem > 11 && String.sub pem 0 11 = "-----BEGIN "); 28 + (* Roundtrip *) 29 + match Certificate.decode_pem pem with 30 + | Ok cert2 -> 31 + let pem2 = Certificate.encode_pem cert2 in 32 + Alcotest.(check string) "PEM roundtrip" pem pem2 33 + | Error (`Msg m) -> Alcotest.failf "decode failed: %s" m)) 34 + 35 + (* RFC 7468: PEM encoding of public keys *) 36 + let test_public_key_pem_roundtrip () = 37 + let key = Private_key.generate ~bits:2048 `RSA in 38 + let pub = Private_key.public key in 39 + let pem = Public_key.encode_pem pub in 40 + Alcotest.(check bool) 41 + "contains PUBLIC KEY marker" true 42 + (let s = "-----BEGIN PUBLIC KEY-----" in 43 + String.length pem >= String.length s 44 + && String.sub pem 0 (String.length s) = s); 45 + match Public_key.decode_pem pem with 46 + | Ok pub2 -> 47 + let pem2 = Public_key.encode_pem pub2 in 48 + Alcotest.(check string) "public key PEM roundtrip" pem pem2 49 + | Error (`Msg m) -> Alcotest.failf "public key decode failed: %s" m 50 + 51 + (* RFC 7468: PEM encoding of private keys *) 52 + let test_private_key_pem_roundtrip () = 53 + let key = Private_key.generate ~bits:2048 `RSA in 54 + let pem = Private_key.encode_pem key in 55 + Alcotest.(check bool) 56 + "contains PRIVATE KEY marker" true 57 + (let s = "-----BEGIN PRIVATE KEY-----" in 58 + String.length pem >= String.length s 59 + && String.sub pem 0 (String.length s) = s); 60 + match Private_key.decode_pem pem with 61 + | Ok _ -> () 62 + | Error (`Msg m) -> Alcotest.failf "private key decode failed: %s" m 63 + 64 + (* RFC 7468: Invalid PEM should produce an error *) 65 + let test_invalid_pem () = 66 + match Certificate.decode_pem "not a PEM" with 67 + | Error _ -> () 68 + | Ok _ -> Alcotest.fail "expected error for invalid PEM" 69 + 70 + (* RFC 7468: Empty PEM should produce an error *) 71 + let test_empty_pem () = 72 + match Certificate.decode_pem "" with 73 + | Error _ -> () 74 + | Ok _ -> Alcotest.fail "expected error for empty PEM" 75 + 76 + (* Test multiple certificates in PEM *) 77 + let test_pem_multiple () = 78 + let key = Private_key.generate ~bits:2048 `RSA in 79 + let name = 80 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "multi")) ] 81 + in 82 + match Signing_request.create name key with 83 + | Error _ -> Alcotest.fail "CSR failed" 84 + | Ok csr -> ( 85 + let valid_from = Ptime.epoch in 86 + let valid_until = 87 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 88 + | Some t -> t 89 + | None -> Alcotest.fail "time overflow" 90 + in 91 + match Signing_request.sign csr ~valid_from ~valid_until key name with 92 + | Error _ -> Alcotest.fail "signing failed" 93 + | Ok cert -> ( 94 + let pem = Certificate.encode_pem_multiple [ cert; cert ] in 95 + match Certificate.decode_pem_multiple pem with 96 + | Ok certs -> 97 + Alcotest.(check int) "two certs decoded" 2 (List.length certs) 98 + | Error (`Msg m) -> Alcotest.failf "multi decode failed: %s" m)) 99 + 100 + let tests = 101 + [ 102 + ("PEM certificate roundtrip", `Quick, test_certificate_pem_roundtrip); 103 + ("PEM public key roundtrip", `Quick, test_public_key_pem_roundtrip); 104 + ("PEM private key roundtrip", `Quick, test_private_key_pem_roundtrip); 105 + ("PEM invalid input", `Quick, test_invalid_pem); 106 + ("PEM empty input", `Quick, test_empty_pem); 107 + ("PEM multiple certificates", `Quick, test_pem_multiple); 108 + ]
+80
tests/test_private_key.ml
··· 1 + (* Tests for Private_key module *) 2 + open X509 3 + 4 + (* Test private key generation *) 5 + let test_generate_rsa () = 6 + let key = Private_key.generate ~bits:2048 `RSA in 7 + Alcotest.(check string) 8 + "RSA type" (Key_type.to_string `RSA) 9 + (Key_type.to_string (Private_key.key_type key)) 10 + 11 + let test_generate_ed25519 () = 12 + let key = Private_key.generate `ED25519 in 13 + Alcotest.(check string) 14 + "ED25519 type" 15 + (Key_type.to_string `ED25519) 16 + (Key_type.to_string (Private_key.key_type key)) 17 + 18 + let test_generate_p256 () = 19 + let key = Private_key.generate `P256 in 20 + Alcotest.(check string) 21 + "P256 type" (Key_type.to_string `P256) 22 + (Key_type.to_string (Private_key.key_type key)) 23 + 24 + (* Test private key -> public key conversion *) 25 + let test_public () = 26 + let key = Private_key.generate ~bits:2048 `RSA in 27 + let pub = Private_key.public key in 28 + Alcotest.(check string) 29 + "public key type matches" 30 + (Key_type.to_string (Private_key.key_type key)) 31 + (Key_type.to_string (Public_key.key_type pub)) 32 + 33 + (* Test DER roundtrip *) 34 + let test_der_roundtrip () = 35 + let key = Private_key.generate ~bits:2048 `RSA in 36 + let der = Private_key.encode_der key in 37 + match Private_key.decode_der der with 38 + | Ok _ -> () 39 + | Error (`Msg m) -> Alcotest.failf "DER decode failed: %s" m 40 + 41 + (* Test PEM roundtrip *) 42 + let test_pem_roundtrip () = 43 + let key = Private_key.generate ~bits:2048 `RSA in 44 + let pem = Private_key.encode_pem key in 45 + match Private_key.decode_pem pem with 46 + | Ok _ -> () 47 + | Error (`Msg m) -> Alcotest.failf "PEM decode failed: %s" m 48 + 49 + (* Test seeded generation is deterministic *) 50 + let test_seeded_generation () = 51 + let seed = "deterministic_seed_12345678" in 52 + let k1 = Private_key.generate ~seed `RSA in 53 + let k2 = Private_key.generate ~seed `RSA in 54 + let d1 = Private_key.encode_der k1 in 55 + let d2 = Private_key.encode_der k2 in 56 + Alcotest.(check string) "seeded keys match" d1 d2 57 + 58 + (* Test sign and verify roundtrip *) 59 + let test_sign_verify () = 60 + let key = Private_key.generate ~bits:2048 `RSA in 61 + let pub = Private_key.public key in 62 + let msg = "test message for signing" in 63 + match Private_key.sign `SHA256 key (`Message msg) with 64 + | Ok signature -> ( 65 + match Public_key.verify `SHA256 ~signature pub (`Message msg) with 66 + | Ok () -> () 67 + | Error (`Msg m) -> Alcotest.failf "verify failed: %s" m) 68 + | Error (`Msg m) -> Alcotest.failf "sign failed: %s" m 69 + 70 + let tests = 71 + [ 72 + ("generate RSA", `Quick, test_generate_rsa); 73 + ("generate ED25519", `Quick, test_generate_ed25519); 74 + ("generate P256", `Quick, test_generate_p256); 75 + ("public key from private", `Quick, test_public); 76 + ("DER roundtrip", `Quick, test_der_roundtrip); 77 + ("PEM roundtrip", `Quick, test_pem_roundtrip); 78 + ("seeded generation", `Quick, test_seeded_generation); 79 + ("sign and verify", `Quick, test_sign_verify); 80 + ]
+66
tests/test_public_key.ml
··· 1 + (* Tests for Public_key module *) 2 + open X509 3 + 4 + (* Test public key pp *) 5 + let test_pp () = 6 + let key = Private_key.generate ~bits:2048 `RSA in 7 + let pub = Private_key.public key in 8 + let s = Fmt.to_to_string Public_key.pp pub in 9 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 10 + 11 + (* Test public key id *) 12 + let test_id () = 13 + let key = Private_key.generate ~bits:2048 `RSA in 14 + let pub = Private_key.public key in 15 + let id = Public_key.id pub in 16 + (* RFC 5280: SHA1 hash is 20 bytes *) 17 + Alcotest.(check int) "id is 20 bytes (SHA1)" 20 (String.length id) 18 + 19 + (* Test public key fingerprint *) 20 + let test_fingerprint () = 21 + let key = Private_key.generate ~bits:2048 `RSA in 22 + let pub = Private_key.public key in 23 + let fp = Public_key.fingerprint ~hash:`SHA256 pub in 24 + Alcotest.(check int) "SHA256 fp is 32 bytes" 32 (String.length fp); 25 + let fp_default = Public_key.fingerprint pub in 26 + Alcotest.(check int) "default fp is 32 bytes" 32 (String.length fp_default) 27 + 28 + (* Test public key DER roundtrip *) 29 + let test_der_roundtrip () = 30 + let key = Private_key.generate ~bits:2048 `RSA in 31 + let pub = Private_key.public key in 32 + let der = Public_key.encode_der pub in 33 + match Public_key.decode_der der with 34 + | Ok pub2 -> 35 + let der2 = Public_key.encode_der pub2 in 36 + Alcotest.(check string) "DER roundtrip" der der2 37 + | Error (`Msg m) -> Alcotest.failf "DER decode failed: %s" m 38 + 39 + (* Test public key PEM roundtrip *) 40 + let test_pem_roundtrip () = 41 + let key = Private_key.generate ~bits:2048 `RSA in 42 + let pub = Private_key.public key in 43 + let pem = Public_key.encode_pem pub in 44 + match Public_key.decode_pem pem with 45 + | Ok pub2 -> 46 + let pem2 = Public_key.encode_pem pub2 in 47 + Alcotest.(check string) "PEM roundtrip" pem pem2 48 + | Error (`Msg m) -> Alcotest.failf "PEM decode failed: %s" m 49 + 50 + (* Test key_type accessor *) 51 + let test_key_type () = 52 + let key = Private_key.generate `P256 in 53 + let pub = Private_key.public key in 54 + Alcotest.(check string) 55 + "key type matches" (Key_type.to_string `P256) 56 + (Key_type.to_string (Public_key.key_type pub)) 57 + 58 + let tests = 59 + [ 60 + ("public key pp", `Quick, test_pp); 61 + ("public key id (SHA1)", `Quick, test_id); 62 + ("public key fingerprint", `Quick, test_fingerprint); 63 + ("public key DER roundtrip", `Quick, test_der_roundtrip); 64 + ("public key PEM roundtrip", `Quick, test_pem_roundtrip); 65 + ("public key type", `Quick, test_key_type); 66 + ]
+40
tests/test_rc2.ml
··· 1 + (* Tests for RC2 cipher module *) 2 + (* RC2 is a private module in x509. Its functionality is only exposed 3 + indirectly through PKCS12 decryption (which uses RC2 for legacy 4 + compatibility with OpenSSL-generated archives). We verify this 5 + indirectly through the public API. *) 6 + open X509 7 + 8 + (* Test that PKCS12 with legacy RC2 encryption can be created and 9 + verified. The PKCS12 module uses RC2 internally for compatibility 10 + with OpenSSL. *) 11 + let test_pkcs12_uses_rc2_internally () = 12 + let key = Private_key.generate ~bits:2048 `RSA in 13 + let name = 14 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "rc2")) ] 15 + in 16 + match Signing_request.create name key with 17 + | Error _ -> Alcotest.fail "CSR failed" 18 + | Ok csr -> ( 19 + let valid_from = Ptime.epoch in 20 + let valid_until = 21 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 22 + | Some t -> t 23 + | None -> Alcotest.fail "time" 24 + in 25 + match Signing_request.sign csr ~valid_from ~valid_until key name with 26 + | Error _ -> Alcotest.fail "signing failed" 27 + | Ok cert -> ( 28 + (* Creating a PKCS12 archive exercises the encryption path. 29 + While we use AES for the new path, decoding legacy archives 30 + requires RC2 support. *) 31 + let p12 = PKCS12.create "rc2test" [ cert ] key in 32 + let der = PKCS12.encode_der p12 in 33 + Alcotest.(check bool) 34 + "PKCS12 DER non-empty" true 35 + (String.length der > 0); 36 + match PKCS12.verify "rc2test" p12 with 37 + | Ok _ -> () 38 + | Error (`Msg m) -> Alcotest.failf "verify failed: %s" m)) 39 + 40 + let tests = [ ("PKCS12 RC2 path", `Quick, test_pkcs12_uses_rc2_internally) ]
+69
tests/test_registry.ml
··· 1 + (* Tests for Registry module - OID registry lookups *) 2 + (* The registry module is private, but OID handling is accessible through 3 + the public API via Key_type, Extension, and Distinguished_name modules. *) 4 + open X509 5 + 6 + (* Test that the algorithm-to-key-type mapping works correctly, 7 + which relies on OID registry internally *) 8 + let test_key_type_oid_mapping () = 9 + let types : Key_type.t list = [ `RSA; `ED25519; `P256; `P384; `P521 ] in 10 + List.iter 11 + (fun kt -> 12 + let key = Private_key.generate ~bits:2048 kt in 13 + let pub = Private_key.public key in 14 + let der = Public_key.encode_der pub in 15 + match Public_key.decode_der der with 16 + | Ok pub2 -> 17 + Alcotest.(check string) 18 + (Fmt.str "OID roundtrip for %s" (Key_type.to_string kt)) 19 + (Key_type.to_string (Public_key.key_type pub)) 20 + (Key_type.to_string (Public_key.key_type pub2)) 21 + | Error (`Msg m) -> 22 + Alcotest.failf "DER decode failed for %s: %s" (Key_type.to_string kt) 23 + m) 24 + types 25 + 26 + (* Test that extension OIDs are correctly handled via the registry *) 27 + let test_extension_oid_handling () = 28 + let exts = 29 + Extension.( 30 + add Key_usage 31 + (true, [ `Digital_signature ]) 32 + (add Basic_constraints 33 + (true, (true, None)) 34 + (singleton Ext_key_usage (true, [ `Server_auth ])))) 35 + in 36 + (* Encoding and decoding extensions exercises OID registry *) 37 + Alcotest.(check bool) 38 + "has key_usage" true 39 + (Option.is_some (Extension.find Key_usage exts)); 40 + Alcotest.(check bool) 41 + "has basic_constraints" true 42 + (Option.is_some (Extension.find Basic_constraints exts)); 43 + Alcotest.(check bool) 44 + "has ext_key_usage" true 45 + (Option.is_some (Extension.find Ext_key_usage exts)) 46 + 47 + (* Test that Distinguished_name attributes use correct OIDs *) 48 + let test_dn_oid_handling () = 49 + let dn = 50 + [ 51 + Distinguished_name.Relative_distinguished_name.singleton (CN "test"); 52 + Distinguished_name.Relative_distinguished_name.singleton (O "org"); 53 + Distinguished_name.Relative_distinguished_name.singleton (C "US"); 54 + ] 55 + in 56 + let der = Distinguished_name.encode_der dn in 57 + match Distinguished_name.decode_der der with 58 + | Ok dn2 -> 59 + Alcotest.(check bool) 60 + "DN OID roundtrip" true 61 + (Distinguished_name.equal dn dn2) 62 + | Error (`Msg m) -> Alcotest.failf "DN decode failed: %s" m 63 + 64 + let tests = 65 + [ 66 + ("key type OID mapping", `Quick, test_key_type_oid_mapping); 67 + ("extension OID handling", `Quick, test_extension_oid_handling); 68 + ("DN OID handling", `Quick, test_dn_oid_handling); 69 + ]
+97
tests/test_signing_request.ml
··· 1 + (* Tests for Signing_request module - PKCS#10 CSR structure *) 2 + open X509 3 + 4 + let key () = Private_key.generate ~bits:2048 `RSA 5 + 6 + let name = 7 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "csr.com")) ] 8 + 9 + (* Test CSR creation *) 10 + let test_create () = 11 + match Signing_request.create name (key ()) with 12 + | Ok _ -> () 13 + | Error (`Msg m) -> Alcotest.failf "CSR creation failed: %s" m 14 + 15 + (* Test CSR info *) 16 + let test_info () = 17 + match Signing_request.create name (key ()) with 18 + | Error _ -> Alcotest.fail "CSR creation failed" 19 + | Ok csr -> 20 + let info = Signing_request.info csr in 21 + Alcotest.(check (option string)) 22 + "subject CN" (Some "csr.com") 23 + (Distinguished_name.common_name info.subject) 24 + 25 + (* Test CSR hostnames *) 26 + let test_hostnames () = 27 + let exts = 28 + Extension.( 29 + singleton Subject_alt_name 30 + (false, General_name.(singleton DNS [ "alt.com" ]))) 31 + in 32 + let sr_exts = Signing_request.Ext.(singleton Extensions exts) in 33 + match Signing_request.create name ~extensions:sr_exts (key ()) with 34 + | Error _ -> Alcotest.fail "CSR creation failed" 35 + | Ok csr -> 36 + let hs = Signing_request.hostnames csr in 37 + Alcotest.(check bool) "has hostnames" true (not (Host.Set.is_empty hs)) 38 + 39 + (* Test CSR DER roundtrip *) 40 + let test_der_roundtrip () = 41 + match Signing_request.create name (key ()) with 42 + | Error _ -> Alcotest.fail "CSR creation failed" 43 + | Ok csr -> ( 44 + let der = Signing_request.encode_der csr in 45 + match Signing_request.decode_der der with 46 + | Ok csr2 -> 47 + let der2 = Signing_request.encode_der csr2 in 48 + Alcotest.(check string) "DER roundtrip" der der2 49 + | Error (`Msg m) -> Alcotest.failf "decode failed: %s" m) 50 + 51 + (* Test CSR PEM roundtrip *) 52 + let test_pem_roundtrip () = 53 + match Signing_request.create name (key ()) with 54 + | Error _ -> Alcotest.fail "CSR creation failed" 55 + | Ok csr -> ( 56 + let pem = Signing_request.encode_pem csr in 57 + match Signing_request.decode_pem pem with 58 + | Ok csr2 -> 59 + let pem2 = Signing_request.encode_pem csr2 in 60 + Alcotest.(check string) "PEM roundtrip" pem pem2 61 + | Error (`Msg m) -> Alcotest.failf "decode failed: %s" m) 62 + 63 + (* Test CSR signature algorithm *) 64 + let test_signature_algorithm () = 65 + match Signing_request.create name (key ()) with 66 + | Error _ -> Alcotest.fail "CSR creation failed" 67 + | Ok csr -> ( 68 + match Signing_request.signature_algorithm csr with 69 + | Some _ -> () 70 + | None -> Alcotest.fail "expected signature algorithm") 71 + 72 + (* Test CSR sign *) 73 + let test_sign () = 74 + let k = key () in 75 + match Signing_request.create name k with 76 + | Error _ -> Alcotest.fail "CSR creation failed" 77 + | Ok csr -> ( 78 + let valid_from = Ptime.epoch in 79 + let valid_until = 80 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 81 + | Some t -> t 82 + | None -> Alcotest.fail "time" 83 + in 84 + match Signing_request.sign csr ~valid_from ~valid_until k name with 85 + | Ok _ -> () 86 + | Error _ -> Alcotest.fail "CSR signing failed") 87 + 88 + let tests = 89 + [ 90 + ("CSR creation", `Quick, test_create); 91 + ("CSR info", `Quick, test_info); 92 + ("CSR hostnames", `Quick, test_hostnames); 93 + ("CSR DER roundtrip", `Quick, test_der_roundtrip); 94 + ("CSR PEM roundtrip", `Quick, test_pem_roundtrip); 95 + ("CSR signature algorithm", `Quick, test_signature_algorithm); 96 + ("CSR sign", `Quick, test_sign); 97 + ]
+66
tests/test_validation.ml
··· 1 + (* Tests for Validation module - certificate chain validation *) 2 + open X509 3 + 4 + (* Test pp functions are accessible *) 5 + let test_pp_signature_error () = 6 + let s = Fmt.to_to_string Validation.pp_signature_error (`Msg "test error") in 7 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 8 + 9 + let test_pp_ca_error () = 10 + let s = Fmt.to_to_string Validation.pp_ca_error (`Msg "test ca error") in 11 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 12 + 13 + let test_pp_chain_error () = 14 + let s = Fmt.to_to_string Validation.pp_chain_error `EmptyCertificateChain in 15 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 16 + 17 + let test_pp_validation_error () = 18 + let s = 19 + Fmt.to_to_string Validation.pp_validation_error `EmptyCertificateChain 20 + in 21 + Alcotest.(check bool) "pp non-empty" true (String.length s > 0) 22 + 23 + (* Test build_paths with empty chain *) 24 + let test_build_paths_empty () = 25 + let key = Private_key.generate ~bits:2048 `RSA in 26 + let name = 27 + [ Distinguished_name.(Relative_distinguished_name.singleton (CN "test")) ] 28 + in 29 + match Signing_request.create name key with 30 + | Error _ -> Alcotest.fail "CSR failed" 31 + | Ok csr -> ( 32 + let valid_from = Ptime.epoch in 33 + let valid_until = 34 + match Ptime.add_span Ptime.epoch (Ptime.Span.of_int_s 86400) with 35 + | Some t -> t 36 + | None -> Alcotest.fail "time" 37 + in 38 + match Signing_request.sign csr ~valid_from ~valid_until key name with 39 + | Error _ -> Alcotest.fail "sign failed" 40 + | Ok cert -> 41 + let paths = Validation.build_paths cert [] in 42 + Alcotest.(check int) "one path (self)" 1 (List.length paths)) 43 + 44 + (* Test valid_cas with empty list *) 45 + let test_valid_cas_empty () = 46 + let cas = Validation.valid_cas [] in 47 + Alcotest.(check int) "no valid CAs from empty" 0 (List.length cas) 48 + 49 + (* Test verify_chain_of_trust with empty chain *) 50 + let test_empty_chain () = 51 + let time () = None in 52 + match Validation.verify_chain_of_trust ~host:None ~time ~anchors:[] [] with 53 + | Error `EmptyCertificateChain -> () 54 + | Error _ -> () 55 + | Ok _ -> Alcotest.fail "expected error for empty chain" 56 + 57 + let tests = 58 + [ 59 + ("pp_signature_error", `Quick, test_pp_signature_error); 60 + ("pp_ca_error", `Quick, test_pp_ca_error); 61 + ("pp_chain_error", `Quick, test_pp_chain_error); 62 + ("pp_validation_error", `Quick, test_pp_validation_error); 63 + ("build_paths empty", `Quick, test_build_paths_empty); 64 + ("valid_cas empty", `Quick, test_valid_cas_empty); 65 + ("empty chain", `Quick, test_empty_chain); 66 + ]
+19
tests/tests.ml
··· 8 8 ("PKCS12", Pkcs12.tests); 9 9 ("OCSP", Ocsp.tests); 10 10 ("Private Key", Priv.tests); 11 + ("PEM encoding", Test_pem.tests); 12 + ("Distinguished name", Test_distinguished_name.tests); 13 + ("Host module", Test_host.tests); 14 + ("Algorithm mapping", Test_algorithm.tests); 15 + ("Certificate module", Test_certificate.tests); 16 + ("Validation module", Test_validation.tests); 17 + ("Extension module", Test_extension.tests); 18 + ("General name", Test_general_name.tests); 19 + ("Key type", Test_key_type.tests); 20 + ("Public key module", Test_public_key.tests); 21 + ("Private key module", Test_private_key.tests); 22 + ("Signing request", Test_signing_request.tests); 23 + ("CRL module", Test_crl.tests); 24 + ("Authenticator module", Test_authenticator.tests); 25 + ("ASN.1 grammars", Test_asn_grammars.tests); 26 + ("OCSP module", Test_ocsp.tests); 27 + ("PKCS12 module", Test_p12.tests); 28 + ("RC2 cipher", Test_rc2.tests); 29 + ("OID registry", Test_registry.tests); 11 30 ] 12 31 13 32 let () =