This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,238 @@
let ( let* ) = Result.bind
module Ext = struct
type _ k =
| Password : string k
| Name : string k
| Extensions : Extension.t k
module K = struct
type 'a t = 'a k
let compare : type a b . a t -> b t -> (a, b) Gmap.Order.t = fun t t' ->
let open Gmap.Order in
match t, t' with
| Password, Password -> Eq | Password, _ -> Lt | _, Password -> Gt
| Name, Name -> Eq | Name, _ -> Lt | _, Name -> Gt
| Extensions, Extensions -> Eq
end
include Gmap.Make(K)
let pp_one : type a. a k -> Format.formatter -> a -> unit = fun k ppf v ->
match k, v with
| Password, pass -> Fmt.pf ppf "password %s" pass
| Name, name -> Fmt.pf ppf "name %s" name
| Extensions, ext -> Fmt.pf ppf "extensions %a" Extension.pp ext
let pp ppf m = iter (fun (B (k, v)) -> pp_one k ppf v ; Fmt.sp ppf ()) m
end
type request_info = {
subject : Distinguished_name.t ;
public_key : Public_key.t ;
extensions : Ext.t ;
}
type request = {
info : request_info ;
signature_algorithm : Algorithm.t ;
signature : string
}
type t = {
asn : request ;
raw : string ;
}
module Asn = struct
open Asn_grammars
open Asn.S
open Registry
let attributes =
let f = function[@ocaml.warning "-8"]
| (oid, [`C1 p]) when oid = PKCS9.challenge_password -> Ext.B (Password, p)
| (oid, [`C1 n]) when oid = PKCS9.unstructured_name -> Ext.B (Name, n)
| (oid, [`C2 es]) when oid = PKCS9.extension_request -> Ext.B (Extensions, es)
and g (Ext.B (k, v)) : Asn.oid * [ `C1 of string | `C2 of Extension.t ] list = match k, v with
| Ext.Password, v -> (PKCS9.challenge_password, [`C1 v])
| Ext.Name, v -> (PKCS9.unstructured_name, [`C1 v])
| Ext.Extensions, v -> (PKCS9.extension_request, [`C2 v])
in
map f g @@
sequence2
(required ~label:"attr type" oid)
(required ~label:"attr value"
(set_of (choice2
utf8_string
Extension.Asn.extensions_der)))
let request_info =
let f = function
| (0, subject, public_key, extensions) ->
let extensions =
List.fold_left (fun map (Ext.B (k, v)) ->
match Ext.add_unless_bound k v map with
| None -> parse_error "request extension %a already bound"
(Ext.pp_one k) v
| Some b -> b)
Ext.empty extensions
in
{ subject ; public_key ; extensions }
| _ ->
parse_error "unknown certificate request info"
and g { subject ; public_key ; extensions } =
let extensions = Ext.bindings extensions in
(0, subject, public_key, extensions)
in
map f g @@
sequence4
(required ~label:"version" int)
(required ~label:"subject" Distinguished_name.Asn.name)
(required ~label:"subjectPKInfo" Public_key.Asn.pk_info_der)
(required ~label:"attributes" @@ implicit 0 (set_of attributes))
let request_info_of_str, request_info_to_str =
projections_of Asn.der request_info
let signing_request =
let f = fun (info, signature_algorithm, signature) ->
{ info ; signature_algorithm ; signature }
and g = fun { info ; signature_algorithm ; signature } ->
(info, signature_algorithm, signature)
in
map f g @@
sequence3
(required ~label:"certificationRequestInfo" request_info)
(required ~label:"signatureAlgorithm" Algorithm.identifier)
(required ~label:"signature" bit_string_octets)
let signing_request_of_str, signing_request_to_str =
projections_of Asn.der signing_request
end
let info { asn ; _ } = asn.info
let signature_algorithm { asn ; _ } =
Algorithm.to_signature_algorithm asn.signature_algorithm
let hostnames csr =
let info = info csr in
let subj =
match Distinguished_name.common_name info.subject with
| None -> Host.Set.empty
| Some x ->
match Host.host x with
| Some (typ, n) -> Host.Set.singleton (typ, n)
| None -> Host.Set.empty
in
match Ext.(find Extensions info.extensions) with
| None -> subj
| Some exts -> match Extension.hostnames exts with
| Some names -> names
| None -> subj
let validate_signature allowed_hashes { asn ; raw } =
let raw_data = Validation.raw_cert_hack raw in
Validation.validate_raw_signature asn.info.subject allowed_hashes raw_data
asn.signature_algorithm asn.signature asn.info.public_key
let decode_der ?(allowed_hashes = Validation.sha2) cs =
let* csr = Asn_grammars.err_to_msg (Asn.signing_request_of_str cs) in
let csr = { raw = cs ; asn = csr } in
let* () =
Result.map_error
(fun e -> `Msg (Fmt.to_to_string Validation.pp_signature_error e))
(validate_signature allowed_hashes csr)
in
Ok csr
let encode_der { raw ; _ } = raw
let decode_pem cs =
let* data = Pem.parse cs in
let crs =
List.filter (fun (t, _) -> String.equal "CERTIFICATE REQUEST" t) data
in
let* csrs = Pem.foldM (fun (_, cs) -> decode_der cs) crs in
Pem.exactly_one ~what:"certificate request" csrs
let encode_pem v =
Pem.unparse ~tag:"CERTIFICATE REQUEST" (encode_der v)
let digest_of_key = function
| `RSA _ -> `SHA256
| `ED25519 _ -> `SHA512
| `P256 _ -> `SHA256
| `P384 _ -> `SHA384
| `P521 _ -> `SHA512
let default_digest digest key =
match digest with None -> digest_of_key key | Some x -> x
let create subject ?digest ?(extensions = Ext.empty) (key : Private_key.t) =
let hash = default_digest digest key in
let public_key = Private_key.public key in
let info : request_info = { subject ; public_key ; extensions } in
let info_str = Asn.request_info_to_str info in
let scheme = Key_type.x509_default_scheme (Private_key.key_type key) in
let* signature = Private_key.sign hash ~scheme key (`Message info_str) in
let signature_algorithm = Algorithm.of_signature_algorithm scheme hash in
let asn = { info ; signature_algorithm ; signature } in
let raw = Asn.signing_request_to_str asn in
Ok { asn ; raw }
let sign signing_request
~valid_from ~valid_until
?(allowed_hashes = Validation.sha2)
?digest
?serial
?(extensions = Extension.empty)
?(subject = signing_request.asn.info.subject)
key issuer =
let hash = default_digest digest key in
let serial = match serial with
| Some s -> s
| None ->
(* we generate a positive integer, asn1-encoded: so if the high bit is
set, we prepend a 0 byte *)
(* if it starts with 0x00 followed by 0xNN with NN <= 0x7f, we prepend
0x7f to make the integer valid *)
let s = Mirage_crypto_rng.generate 10 in
let start = String.get_uint8 s 0 in
if start > 0x7f then
"\x00" ^ s
else if start = 0x00 && String.get_uint8 s 1 <= 0x7f then
"\x7f" ^ s
else
s
in
let* () = validate_signature allowed_hashes signing_request in
let signature_algo =
let scheme = Key_type.x509_default_scheme (Private_key.key_type key) in
Algorithm.of_signature_algorithm scheme hash
and info = signing_request.asn.info
in
let tbs_cert : Certificate.tBSCertificate = {
version = `V3 ;
serial ;
signature = signature_algo ;
issuer = issuer ;
validity = (valid_from, valid_until) ;
subject ;
pk_info = info.public_key ;
issuer_id = None ;
subject_id = None ;
extensions
} in
let tbs_raw = Certificate.Asn.tbs_certificate_to_octets tbs_cert in
let scheme = Key_type.x509_default_scheme (Private_key.key_type key) in
let* signature_val = Private_key.sign hash ~scheme key (`Message tbs_raw) in
let asn = {
Certificate.tbs_cert ;
signature_algo ;
signature_val ;
} in
let raw = Certificate.Asn.certificate_to_octets asn in
Ok { Certificate.asn ; raw }