package dns-certify
MirageOS let's encrypt certificate retrieval
Install
Dune Dependency
Authors
Maintainers
Sources
dns-7.0.3.tbz
sha256=36afa54678979acb27a8760d3bc361e9970da7b12110f373685f7400002e16c4
sha512=71e1f6a4f7ee334de419dda2d9ef23efd2ac1a9a27194d99952a3f9f03025adcb113f8ca1de0ae41e44fa4201ff92dc77134a772356167edd70a199e2df16149
doc/src/dns-certify/dns_certify.ml.html
Source file dns_certify.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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
open Dns let src = Logs.Src.create "dns_certify" ~doc:"DNS certify" module Log = (val Logs.src_log src : Logs.LOG) let tlsa_is usage sel typ t = t.Tlsa.cert_usage = usage && t.Tlsa.selector = sel && t.Tlsa.matching_type = typ let is_csr t = tlsa_is Tlsa.Domain_issued_certificate Tlsa.Private Tlsa.No_hash t let csr req = let data = X509.Signing_request.encode_der req in { Tlsa.matching_type = Tlsa.No_hash ; cert_usage = Tlsa.Domain_issued_certificate ; selector = Tlsa.Private ; data } let is_certificate t = tlsa_is Tlsa.Domain_issued_certificate Tlsa.Full_certificate Tlsa.No_hash t let certificate cert = let data = X509.Certificate.encode_der cert in { Tlsa.matching_type = Tlsa.No_hash ; cert_usage = Tlsa.Domain_issued_certificate ; selector = Tlsa.Full_certificate ; data } let is_ca_certificate t = tlsa_is Tlsa.CA_constraint Tlsa.Full_certificate Tlsa.No_hash t let ca_certificate data = { Tlsa.matching_type = Tlsa.No_hash ; cert_usage = Tlsa.CA_constraint ; selector = Tlsa.Full_certificate ; data } let signing_request hostname ?(more_hostnames = []) key = let host = Domain_name.to_string hostname in let extensions = match more_hostnames with | [] -> X509.Signing_request.Ext.empty | _ -> let ext = let additional = List.map Domain_name.to_string more_hostnames in let gn = X509.General_name.(singleton DNS (host :: additional)) in X509.Extension.(singleton Subject_alt_name (false, gn)) in X509.Signing_request.Ext.(singleton Extensions ext) in X509.(Signing_request.create [Distinguished_name.(Relative_distinguished_name.singleton (CN host))] ~extensions key) let dns_header rng = let id = Randomconv.int16 rng in (id, Packet.Flags.empty) let le_label = "_letsencrypt" and p_label = "_tcp" let is_name name = if Domain_name.count_labels name < 2 then false else Domain_name.(equal_label le_label (get_label_exn name 0) && equal_label p_label (get_label_exn name 1)) let letsencrypt_name name = match Domain_name.(prepend_label (raw name) p_label) with | Ok name' -> Domain_name.prepend_label name' le_label | Error e -> Error e type u_err = [ `Tsig of Dns_tsig.e | `Bad_reply of Packet.mismatch * Packet.t | `Unexpected_reply of Packet.reply ] let pp_u_err ppf = function | `Tsig e -> Fmt.pf ppf "tsig error %a" Dns_tsig.pp_e e | `Bad_reply (e, res) -> Fmt.pf ppf "bad reply %a: %a" Packet.pp_mismatch e Packet.pp res | `Unexpected_reply r -> Fmt.pf ppf "unexpected reply %a" Packet.pp_reply r let nsupdate rng now ~host ~keyname ~zone dnskey request = match letsencrypt_name host with | Error e -> Error e | Ok host -> let tlsa = csr request in let zone = Packet.Question.create zone Soa and update = let up = Domain_name.Map.singleton host [ Packet.Update.Remove (K Tlsa) ; Packet.Update.Add (B (Tlsa, (3600l, Rr_map.Tlsa_set.singleton tlsa))) ] in (Domain_name.Map.empty, up) and header = dns_header rng in let packet = Packet.create header zone (`Update update) in let now = now () in match Dns_tsig.encode_and_sign ~proto:`Tcp packet now dnskey keyname with | Error e -> Error (`Msg (Fmt.to_to_string Dns_tsig.pp_s e)) | Ok (data, mac) -> Ok (data, (fun data -> match Dns_tsig.decode_and_verify now dnskey keyname ~mac data with | Error e -> Error (`Tsig e) | Ok (res, _, _) -> match Packet.reply_matches_request ~request:packet res with | Ok `Update_ack -> Ok () | Ok r -> Error (`Unexpected_reply r) | Error e -> Error (`Bad_reply (e, res)))) type q_err = [ | `Decode of Packet.err | `Bad_reply of Packet.mismatch * Packet.t | `Unexpected_reply of Packet.reply | `No_tlsa ] let pp_q_err ppf = function | `Decode err -> Fmt.pf ppf "decoding failed %a" Packet.pp_err err | `Bad_reply (e, res) -> Fmt.pf ppf "bad reply %a: %a" Packet.pp_mismatch e Packet.pp res | `Unexpected_reply r -> Fmt.pf ppf "unexpected reply %a" Packet.pp_reply r | `No_tlsa -> Fmt.pf ppf "No TLSA record found" (* may be better suited in X509? *) let cert_matches_csr ?until now csr cert = let until = match until with None -> now | Some x -> x in let csr_key = X509.Signing_request.((info csr).public_key) and csr_hostnames = X509.Signing_request.hostnames csr and cert_key = X509.Certificate.public_key cert and cert_hostnames = X509.Certificate.hostnames cert and (st, en) = X509.Certificate.validity cert in let valid = Ptime.is_later ~than:st now && Ptime.is_later ~than:until en in if not (Cstruct.equal (X509.Public_key.fingerprint cert_key) (X509.Public_key.fingerprint csr_key)) then begin Log.info (fun m -> m "public key of CSR and certificate %a do not match" X509.Certificate.pp cert); false end else if not (X509.Host.Set.equal cert_hostnames csr_hostnames) then begin Log.info (fun m -> m "hostnames of CSR %a and certificate %a do not match" X509.Host.Set.pp csr_hostnames X509.Host.Set.pp cert_hostnames); false end else if not valid then begin let pp_pt = Ptime.pp_rfc3339 () in Log.info (fun m -> m "Certificate is not valid now %a (until %a), it is \ valid from %a until %a)" pp_pt now pp_pt until pp_pt st pp_pt en); false end else true let tlsas_to_certchain host now csr tlsas = let certificates, ca_certificates = Rr_map.Tlsa_set.fold (fun tlsa (certs, cacerts as acc) -> if is_certificate tlsa || is_ca_certificate tlsa then match X509.Certificate.decode_der tlsa.Tlsa.data with | Error (`Msg msg) -> Log.warn (fun m -> m "couldn't decode tlsa record %a: %s (%a)" Domain_name.pp host msg Cstruct.hexdump_pp tlsa.Tlsa.data); acc | Ok cert -> match is_certificate tlsa, is_ca_certificate tlsa with | true, _ -> (cert :: certs, cacerts) | _, true -> (certs, cert :: cacerts) | _ -> acc else acc) tlsas ([], []) in match List.find_opt (cert_matches_csr now csr) certificates with | None -> Error `No_tlsa | Some server_cert -> match List.rev (X509.Validation.build_paths server_cert ca_certificates) with | (_server :: chain) :: _ -> Ok (server_cert, chain) | _ -> Ok (server_cert, []) (* build_paths always returns the server_cert *) let query rng now host csr = match letsencrypt_name host with | Error e -> Error e | Ok host -> let header = dns_header rng and question = Packet.Question.create host Tlsa in let request = Packet.create header question `Query in let out, _ = Packet.encode `Tcp request and react data = match Packet.decode data with | Error e -> Error (`Decode e) | Ok reply -> match Packet.reply_matches_request ~request reply with | Ok (`Answer (answer, _)) -> begin match Name_rr_map.find host Tlsa answer with | None -> Error `No_tlsa | Some (_, tlsas) -> tlsas_to_certchain host now csr tlsas end | Ok (`Rcode_error (Rcode.NXDomain, Opcode.Query, _)) -> Error `No_tlsa | Ok reply -> Error (`Unexpected_reply reply) | Error e -> Error (`Bad_reply (e, reply)) in Ok (out, react)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>