type attribute = | CN of string | Serialnumber of string | C of string | L of string | ST of string | O of string | OU of string | T of string | DNQ of string | Mail of string | DC of string | Given_name of string | Surname of string | Initials of string | Pseudonym of string | Generation of string | Street of string | Userid of string | Other of Asn.oid * string (* Escaping is described in RFC4514. Escaing '=' is optional, otherwise the * following is minimal, using the character instead of hex where possible. *) let pp_attribute_value ?(osf = false) () ppf s = let n = String.length s in for i = 0 to n - 1 do match s.[i] with | '#' when i = 0 -> Fmt.string ppf "\\#" | ' ' when i = 0 || i = n - 1 -> Fmt.string ppf "\\ " | ',' when not osf -> Fmt.string ppf "\\," | ';' when not osf -> Fmt.string ppf "\\;" | '/' when osf -> Fmt.string ppf "\\/" | '"' | '+' | '<' | '=' | '>' | '\\' as c -> Fmt.pf ppf "\\%c" c | '\x00' -> Fmt.string ppf "\\00" | c -> Fmt.char ppf c done let pp_string_hex ppf s = for i = 0 to String.length s - 1 do Fmt.pf ppf "%02x" (Char.code s.[i]) done let pp_attribute ?osf ?(ava_equal = Fmt.any "=") () ppf attr = let aux a v = Fmt.pf ppf "%s%a%a" a ava_equal () (pp_attribute_value ?osf ()) v in match attr with | CN s -> aux "CN" s | Serialnumber s -> aux "Serialnumber" s | C s -> aux "C" s | L s -> aux "L" s | ST s -> aux "ST" s | O s -> aux "O" s | OU s -> aux "OU" s | T s -> aux "T" s | DNQ s -> aux "DNQ" s | Mail s -> aux "Mail" s | DC s -> aux "DC" s | Given_name s -> aux "Given_name" s | Surname s -> aux "Surname" s | Initials s -> aux "Initials" s | Pseudonym s -> aux "Pseudonym" s | Generation s -> aux "Generation" s | Street s -> aux "Street" s | Userid s -> aux "UID" s | Other (oid, s) -> Fmt.pf ppf "%a%a#%a" Asn.OID.pp oid ava_equal () pp_string_hex s module K = struct type t = attribute let compare t t' = match t, t' with | CN a, CN b -> String.compare a b | CN _, _ -> -1 | _, CN _ -> 1 | Serialnumber a, Serialnumber b -> String.compare a b | Serialnumber _, _ -> -1 | _, Serialnumber _ -> 1 | C a, C b -> String.compare a b | C _, _ -> -1 | _, C _ -> 1 | L a, L b -> String.compare a b | L _, _ -> -1 | _, L _ -> 1 | ST a, ST b -> String.compare a b | ST _, _ -> -1 | _, ST _ -> 1 | O a, O b -> String.compare a b | O _, _ -> -1 | _, O _ -> 1 | OU a, OU b -> String.compare a b | OU _, _ -> -1 | _, OU _ -> 1 | T a, T b -> String.compare a b | T _, _ -> -1 | _, T _ -> 1 | DNQ a, DNQ b -> String.compare a b | DNQ _, _ -> -1 | _, DNQ _ -> 1 | Mail a, Mail b -> String.compare a b | Mail _, _ -> -1 | _, Mail _ -> 1 | DC a, DC b -> String.compare a b | DC _, _ -> -1 | _, DC _ -> 1 | Given_name a, Given_name b -> String.compare a b | Given_name _, _ -> -1 | _, Given_name _ -> 1 | Surname a, Surname b -> String.compare a b | Surname _, _ -> -1 | _, Surname _ -> 1 | Initials a, Initials b -> String.compare a b | Initials _, _ -> -1 | _, Initials _ -> 1 | Pseudonym a, Pseudonym b -> String.compare a b | Pseudonym _, _ -> -1 | _, Pseudonym _ -> 1 | Generation a, Generation b -> String.compare a b | Generation _, _ -> -1 | _, Generation _ -> 1 | Street a, Street b -> String.compare a b | Street _, _ -> -1 | _, Street _ -> 1 | Userid a, Userid b -> String.compare a b | Userid _, _ -> -1 | _, Userid _ -> 1 | Other (oid_a, v_a), Other (oid_b, v_b) -> match Asn.OID.compare oid_a oid_b with | 0 -> String.compare v_a v_b | x when x < 0 -> -1 | _ -> 1 end module Relative_distinguished_name = Set.Make(K) (* TODO: - each RDN should be a non-empty set - nothing prevents a user from putting Other (base 2 5 <| 4 <| 3, "foo") and Common_name "foo" into the same RDN -- which are identical (i.e. Other should filter the other named constructors) *) type t = Relative_distinguished_name.t list let equal a b = List.length a = List.length b && List.for_all2 Relative_distinguished_name.equal a b let make_pp_rdn ?osf ?(spacing = `Tight) () = let ava_sep, ava_equal = match spacing with | `Tight -> Fmt.(any "+" ++ cut, any "=") | `Medium -> Fmt.(any " +" ++ sp, any "=") | `Loose -> Fmt.(any " +" ++ sp, any " = ") in let pp_ava = pp_attribute ?osf ~ava_equal () in Fmt.(using Relative_distinguished_name.elements @@ list ~sep:ava_sep pp_ava) let make_pp ~format ?spacing () = match format, spacing with | `RFC4514, (None | Some `Tight) -> Fmt.(using List.rev @@ list ~sep:(any "," ++ cut) (make_pp_rdn ())) | `RFC4514, Some (`Medium | `Loose as spacing) -> Fmt.(using List.rev @@ list ~sep:comma (make_pp_rdn ~spacing ())) | `OpenSSL, (None | Some `Loose) -> Fmt.(list ~sep:comma (make_pp_rdn ~spacing:`Loose ())) | `OpenSSL, Some (`Tight | `Medium as spacing) -> Fmt.(list ~sep:(any "," ++ cut) (make_pp_rdn ~spacing ())) | `OSF, _ -> Fmt.(any "/" ++ list ~sep:(any "/") (make_pp_rdn ~osf:true ())) let pp = Fmt.hbox (make_pp ~format:`OSF ()) let common_name t = let is_cn = function CN _ -> true | _ -> false in List.fold_left (fun acc dn -> match Relative_distinguished_name.find_first_opt is_cn dn with | Some CN x -> Some x | _ -> acc) None t module Asn = struct open Asn.S open Asn_grammars (* ASN `Name' fragmet appears all over. *) (* rfc5280 section 4.1.2.4 - name components we "must" handle. *) (* A list of abbreviations: http://pic.dhe.ibm.com/infocenter/wmqv7/v7r1/index.jsp?topic=%2Fcom.ibm.mq.doc%2Fsy10570_.htm *) (* Also rfc4519. *) (* See rfc5280 section 4.1.2.4. *) let directory_name = choice6 utf8_string printable_string ia5_string universal_string teletex_string bmp_string (* We flatten the sequence-of-set-of-tuple here into a single list. * This means that we can't write non-singleton sets back. * Does anyone need that, ever? *) let name = let open Registry in let of_c = function | `C1 x | `C2 x | `C3 x | `C4 x | `C5 x | `C6 x -> x in let a_f = case_of_oid_f [ (domain_component , fun x -> DC (of_c x)) ; (X520.common_name , fun x -> CN (of_c x)) ; (X520.serial_number , fun x -> Serialnumber (of_c x)) ; (X520.country_name , fun x -> C (of_c x)) ; (X520.locality_name , fun x -> L (of_c x)) ; (X520.state_or_province_name , fun x -> ST (of_c x)) ; (X520.organization_name , fun x -> O (of_c x)) ; (X520.organizational_unit_name , fun x -> OU (of_c x)) ; (X520.title , fun x -> T (of_c x)) ; (X520.dn_qualifier , fun x -> DNQ (of_c x)) ; (PKCS9.email , fun x -> Mail (of_c x)) ; (X520.given_name , fun x -> Given_name (of_c x)) ; (X520.surname , fun x -> Surname (of_c x)) ; (X520.initials , fun x -> Initials (of_c x)) ; (X520.pseudonym , fun x -> Pseudonym (of_c x)) ; (X520.generation_qualifier , fun x -> Generation (of_c x)) ; (X520.street_address , fun x -> Street (of_c x)) ; (userid , fun x -> Userid (of_c x))] ~default:(fun oid x -> Other (oid, of_c x)) and a_g = function | DC x -> (domain_component, `C3 x ) | CN x -> (X520.common_name, `C1 x ) | Serialnumber x -> (X520.serial_number, `C2 x ) | C x -> (X520.country_name, `C2 x ) | L x -> (X520.locality_name, `C1 x ) | ST x -> (X520.state_or_province_name, `C1 x ) | O x -> (X520.organization_name, `C1 x ) | OU x -> (X520.organizational_unit_name, `C1 x ) | T x -> (X520.title, `C1 x ) | DNQ x -> (X520.dn_qualifier, `C2 x ) | Mail x -> (PKCS9.email, `C3 x ) | Given_name x -> (X520.given_name, `C1 x ) | Surname x -> (X520.surname, `C1 x ) | Initials x -> (X520.initials, `C1 x ) | Pseudonym x -> (X520.pseudonym, `C1 x ) | Generation x -> (X520.generation_qualifier, `C1 x ) | Street x -> (X520.street_address, `C1 x ) | Userid x -> (userid, `C1 x ) | Other (oid, x) -> (oid, `C1 x ) in let attribute_tv = map a_f a_g @@ sequence2 (required ~label:"attr type" oid) (* This is ANY according to rfc5280. *) (required ~label:"attr value" directory_name) in let rd_name = let f exts = List.fold_left (fun set attr -> Relative_distinguished_name.add attr set) Relative_distinguished_name.empty exts and g map = Relative_distinguished_name.elements map in map f g @@ set_of attribute_tv in sequence_of rd_name (* A vacuous choice, in the standard. *) let (name_of_octets, name_to_octets) = projections_of Asn.der name end let decode_der cs = Asn_grammars.err_to_msg (Asn.name_of_octets cs) let encode_der = Asn.name_to_octets