package tiny_libs
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/tiny_libs.networking_tls/X509.ml.html
Source file X509.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) (* See X509.mli *) type public_key = Rsa of Bignum.t * Bignum.t | Ec of Ecdsa.curve * string | Other of string type t = { der : string; tbs : string; issuer : string; subject : string; common_name : string; not_before : float; not_after : float; key : public_key; algorithm : string; signature : string; names : string list; ca : bool; } let ( let* ) = Result.bind let one (s : string) : (Asn1.t, string) result = Result.map fst (Asn1.parse s 0) (*****************************************************************************) (* Reading *) (*****************************************************************************) (* a Name's CommonName (2.5.4.3), if it has one *) let common_name (name : Asn1.t) : string = List.concat_map Asn1.children (Asn1.children name) (* the SETs of attributes *) |> List.find_map (fun attr -> match Asn1.children attr with [ oid; v ] when Asn1.oid oid = "2.5.4.3" -> Some (Asn1.text v) | _ -> None) |> Option.value ~default:"" let public_key (spki : Asn1.t) : public_key = match Asn1.children spki with | [ alg; key ] -> ( match Asn1.children alg with | oid :: params -> ( match (Asn1.oid oid, params) with | "1.2.840.113549.1.1.1", _ -> ( match Result.map Asn1.children (one (Asn1.bit_string key)) with | Ok [ n; e ] -> Rsa (Asn1.integer n, Asn1.integer e) | _ -> Other "bad RSA key") | "1.2.840.10045.2.1", [ curve ] -> ( match Asn1.oid curve with | "1.2.840.10045.3.1.7" -> Ec (Ecdsa.p256, Asn1.bit_string key) | "1.3.132.0.34" -> Ec (Ecdsa.p384, Asn1.bit_string key) | other -> Other other) | other, _ -> Other other) | [] -> Other "") | _ -> Other "" (* the extensions we read: subjectAltName's names, basic constraints *) let extensions (exts : Asn1.t list) : string list * bool = List.fold_left (fun (names, ca) ext -> match Asn1.children ext with | oid :: rest -> ( let value = match List.rev rest with v :: _ -> Asn1.value_of v | [] -> "" in match Asn1.oid oid with | "2.5.29.17" -> ( match one value with | Ok seq -> (names @ List.filter_map (fun (g : Asn1.t) -> if g.tag = 0x82 then Some g.value else None) (Asn1.children seq), ca) | Error _ -> (names, ca)) | "2.5.29.19" -> ( match Result.map Asn1.children (one value) with | Ok ((b : Asn1.t) :: _) when b.tag = 0x01 -> (names, b.value <> "\000") | _ -> (names, ca)) | _ -> (names, ca)) | [] -> (names, ca)) ([], false) exts let parse (der : string) : (t, string) result = let* cert = one der in match Asn1.children cert with | [ tbs; alg; sigv ] -> ( let fields = Asn1.children tbs in (* the version, [0], is optional (v1): skip it if there *) let fields = match fields with (v : Asn1.t) :: rest when v.tag = 0xa0 -> rest | l -> l in match fields with | _serial :: _sigalg :: issuer :: validity :: subject :: spki :: rest -> ( let exts = List.concat_map (fun (x : Asn1.t) -> if x.tag = 0xa3 then List.concat_map Asn1.children (Asn1.children x) else []) rest in let names, ca = extensions exts in match (Asn1.children validity, Asn1.children alg) with | [ nb; na ], oid :: _ -> ( match (Asn1.time nb, Asn1.time na) with | Some not_before, Some not_after -> Ok { der; tbs = tbs.raw; issuer = issuer.raw; subject = subject.raw; common_name = common_name subject; not_before; not_after; key = public_key spki; algorithm = Asn1.oid oid; signature = Asn1.bit_string sigv; names; ca; } | _ -> Error "a certificate's dates unreadable") | _ -> Error "a certificate's validity or algorithm unreadable") | _ -> Error "a certificate with too few fields") | _ -> Error "not a certificate" (*****************************************************************************) (* Signatures *) (*****************************************************************************) (* an ECDSA signature's DER: SEQUENCE { r, s } *) let ecdsa (curve : Ecdsa.curve) (point : string) ~(hash : string) (signature : string) : bool = match Result.map Asn1.children (one signature) with | Ok [ r; s ] -> Ecdsa.verify curve ~public:point ~hash ~r:(Asn1.integer r) ~s:(Asn1.integer s) | _ -> false let signed_by ~(issuer : t) (cert : t) : bool = let rsa h = match issuer.key with Rsa (n, e) -> Rsa.verify_pkcs1 ~n ~e h ~message:cert.tbs ~signature:cert.signature | _ -> false in let ec digest = match issuer.key with Ec (c, p) -> ecdsa c p ~hash:(digest cert.tbs) cert.signature | _ -> false in match cert.algorithm with | "1.2.840.113549.1.1.11" -> rsa Rsa.Sha256 | "1.2.840.113549.1.1.12" -> rsa Rsa.Sha384 | "1.2.840.113549.1.1.13" -> rsa Rsa.Sha512 | "1.2.840.10045.4.3.2" -> ec Sha256.digest | "1.2.840.10045.4.3.3" -> ec Sha512.digest384 | "1.2.840.10045.4.3.4" -> ec Sha512.digest | _ -> false let verify_scheme (cert : t) ~(scheme : int) ~(message : string) ~(signature : string) : bool = match (scheme, cert.key) with | 0x0403, Ec (c, p) -> ecdsa c p ~hash:(Sha256.digest message) signature | 0x0503, Ec (c, p) -> ecdsa c p ~hash:(Sha512.digest384 message) signature | 0x0804, Rsa (n, e) -> Rsa.verify_pss ~n ~e Rsa.Sha256 ~message ~signature | 0x0805, Rsa (n, e) -> Rsa.verify_pss ~n ~e Rsa.Sha384 ~message ~signature | 0x0806, Rsa (n, e) -> Rsa.verify_pss ~n ~e Rsa.Sha512 ~message ~signature | 0x0401, Rsa (n, e) -> Rsa.verify_pkcs1 ~n ~e Rsa.Sha256 ~message ~signature | 0x0501, Rsa (n, e) -> Rsa.verify_pkcs1 ~n ~e Rsa.Sha384 ~message ~signature | _ -> false (*****************************************************************************) (* Names, and the path *) (*****************************************************************************) (* "*.wikipedia.org" covers "en.wikipedia.org", not "wikipedia.org" nor "a.b.wikipedia.org" *) let matches (pattern : string) (host : string) : bool = let pattern = String.lowercase_ascii pattern and host = String.lowercase_ascii host in if String.length pattern > 2 && String.sub pattern 0 2 = "*." then match String.index_opt host '.' with | Some i -> String.sub host (i + 1) (String.length host - i - 1) = String.sub pattern 2 (String.length pattern - 2) && i > 0 | None -> false else pattern = host let names_host (cert : t) (host : string) : bool = let names = if cert.names = [] then [ cert.common_name ] else cert.names in List.exists (fun p -> matches p host) names let verify ~(trust : t list) ~(now : float) ~(host : string) (chain : t list) : (unit, string) result = let dated (c : t) = now >= c.not_before && now <= c.not_after in let name (c : t) = if c.common_name <> "" then c.common_name else "a certificate" in match chain with | [] -> Error "no certificate" | leaf :: others -> if not (names_host leaf host) then Error (Printf.sprintf "the certificate is for %s, not %s" (String.concat ", " (if leaf.names = [] then [ leaf.common_name ] else leaf.names)) host) else (* up from [c]: a trusted root that signed it, or else one of the certificates sent that is a CA and did *) let rec up (c : t) (depth : int) (unused : t list) = if not (dated c) then Error (name c ^ ": out of its dates") else if depth > 8 then Error "a chain too long" else match List.find_opt (fun (r : t) -> r.subject = c.issuer && signed_by ~issuer:r c) trust with | Some root -> if dated root then Ok () else Error (name root ^ ": a root out of its dates") | None -> ( match List.find_opt (fun (i : t) -> i.subject = c.issuer && i.der <> c.der) unused with | Some i when not i.ca -> Error (name i ^ " is not allowed to sign certificates") | Some i when not (signed_by ~issuer:i c) -> Error (name c ^ ": its signature does not check") | Some i -> up i (depth + 1) (List.filter (fun (x : t) -> x != i) unused) | None -> Error (name c ^ ": issued by no one we trust")) in up leaf 0 others
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>