diff --git a/lib/algorithm.ml b/lib/algorithm.ml index aab8dbe5..2a9aea54 100644 --- a/lib/algorithm.ml +++ b/lib/algorithm.ml @@ -60,6 +60,29 @@ type t = | SHA512_224 | SHA512_256 + (* HMAC algorithms *) + | HMAC_SHA1 + | HMAC_SHA224 + | HMAC_SHA256 + | HMAC_SHA384 + | HMAC_SHA512 + + (* symmetric block ciphers *) + | AES128_CBC of Cstruct.t + | AES192_CBC of Cstruct.t + | AES256_CBC of Cstruct.t + + (* PBE encryption algorithms *) + | SHA_RC4_128 of Cstruct.t * int + | SHA_RC4_40 of Cstruct.t * int + | SHA_3DES_CBC of Cstruct.t * int + | SHA_2DES_CBC of Cstruct.t * int + | SHA_RC2_128_CBC of Cstruct.t * int + | SHA_RC2_40_CBC of Cstruct.t * int + + | PBKDF2 of Cstruct.t * int * int option * t + | PBES2 of t * t + let to_string = function | RSA -> "RSA" | EC_pub curve -> ec_curve_to_string curve @@ -88,6 +111,22 @@ let to_string = function | SHA224 -> "SHA224" | SHA512_224 -> "SHA512/224" | SHA512_256 -> "SHA512/256" + | HMAC_SHA1 -> "HMAC SHA1" + | HMAC_SHA224 -> "HMAC SHA224" + | HMAC_SHA256 -> "HMAC SHA256" + | HMAC_SHA384 -> "HMAC SHA384" + | HMAC_SHA512 -> "HMAC SHA512" + | AES128_CBC _ -> "AES128 CBC" + | AES192_CBC _ -> "AES192 CBC" + | AES256_CBC _ -> "AES256 CBC" + | SHA_RC4_128 (_, _) -> "PBES: SHA RC4 128" + | SHA_RC4_40 (_, _) -> "PBES: SHA RC4 40" + | SHA_3DES_CBC (_, _) -> "PBES: SHA 3DES CBC" + | SHA_2DES_CBC (_, _) -> "PBES: SHA 2DES CBC" + | SHA_RC2_128_CBC (_, _) -> "PBES: SHA RC2 128" + | SHA_RC2_40_CBC (_, _) -> "PBES: SHA RC2 40" + | PBKDF2 (_, _, _, _) -> "PBKDF2" + | PBES2 (_, _) -> "PBES2" let to_hash = function | MD5 -> Some `MD5 @@ -106,6 +145,21 @@ and of_hash = function | `SHA384 -> SHA384 | `SHA512 -> SHA512 +and to_hmac = function + | HMAC_SHA1 -> Some `SHA1 + | HMAC_SHA224 -> Some `SHA224 + | HMAC_SHA256 -> Some `SHA256 + | HMAC_SHA384 -> Some `SHA384 + | HMAC_SHA512 -> Some `SHA512 + | _ -> None + +and of_hmac = function + | `SHA1 -> HMAC_SHA1 + | `SHA224 -> HMAC_SHA224 + | `SHA256 -> HMAC_SHA256 + | `SHA384 -> HMAC_SHA384 + | `SHA512 -> HMAC_SHA512 + and to_key_type = function | RSA -> Some `RSA | EC_pub curve -> Some (`EC curve) @@ -178,6 +232,18 @@ let identifier = and oid f = function | Some (`C2 id) -> f id | _ -> parse_error "Algorithm: expected parameter OID" + and pbe f = function + | Some (`C3 `PBE pbe) -> f pbe + | _ -> parse_error "Algorithm: expected parameter PBE" + and pbkdf2 f = function + | Some (`C3 `PBKDF2 params) -> f params + | _ -> parse_error "Algorithm: expected parameter PBKDF2" + and pbes2 f = function + | Some (`C3 `PBES2 params) -> f params + | _ -> parse_error "Algorithm: expected parameter PBES2" + and octets f = function + | Some (`C4 salt) -> f salt + | _ -> parse_error "Algorithm: expected parameter octet_string" and default oid = Asn.(S.parse_error "Unknown algorithm %a" OID.pp oid) and curve = let default oid = `Other oid in @@ -221,7 +287,28 @@ let identifier = (sha512 , null SHA512 ) ; (sha224 , null SHA224 ) ; (sha512_224 , null SHA512_224 ) ; - (sha512_256 , null SHA512_256 ) ] + (sha512_256 , null SHA512_256 ) ; + + (PKCS2.hmac_sha1 , null HMAC_SHA1 ); + (PKCS2.hmac_sha224 , null HMAC_SHA224 ); + (PKCS2.hmac_sha256 , null HMAC_SHA256 ); + (PKCS2.hmac_sha384 , null HMAC_SHA384 ); + (PKCS2.hmac_sha512 , null HMAC_SHA512 ); + + (PKCS5.aes128_cbc , octets (fun iv -> AES128_CBC iv)); + (PKCS5.aes192_cbc , octets (fun iv -> AES192_CBC iv)); + (PKCS5.aes256_cbc , octets (fun iv -> AES256_CBC iv)); + + (PKCS12.pbe_with_SHA_and_128Bit_RC4, pbe (fun (s, i) -> SHA_RC4_128 (s, i))) ; + (PKCS12.pbe_with_SHA_and_40Bit_RC4, pbe (fun (s, i) -> SHA_RC4_40 (s, i))) ; + (PKCS12.pbe_with_SHA_and_3_KeyTripleDES_CBC, pbe (fun (s, i) -> SHA_3DES_CBC (s, i))) ; + (PKCS12.pbe_with_SHA_and_2_KeyTripleDES_CBC, pbe (fun (s, i) -> SHA_2DES_CBC (s, i))) ; + (PKCS12.pbe_with_SHA_and_128Bit_RC2_CBC, pbe (fun (s, i) -> SHA_RC2_128_CBC (s, i))) ; + (PKCS12.pbe_with_SHA_and_40Bit_RC2_CBC, pbe (fun (s, i) -> SHA_RC2_40_CBC (s, i))) ; + + (PKCS5.pbkdf2, pbkdf2 (fun (s, i, l, m) -> PBKDF2 (s, i, l, m))) ; + (PKCS5.pbes2, pbes2 (fun (oid, oid') -> PBES2 (oid, oid'))) + ] and g = let none = None @@ -233,6 +320,10 @@ let identifier = | `SECP384R1 -> ANSI_X9_62.secp384r1 | `SECP521R1 -> ANSI_X9_62.secp521r1 | `Other oid -> oid + and pbe (s, i) = Some (`C3 (`PBE (s, i))) + and pbkdf2 (s, i, k, m) = Some (`C3 (`PBKDF2 (s, i, k, m))) + and pbes2 (oid, oid') = Some (`C3 (`PBES2 (oid, oid'))) + and octets data = Some (`C4 data) in function | EC_pub id -> (ANSI_X9_62.ec_pub_key , oid (curve id)) @@ -266,12 +357,54 @@ let identifier = | SHA224 -> (sha224 , null) | SHA512_224 -> (sha512_224 , null) | SHA512_256 -> (sha512_256 , null) + + | HMAC_SHA1 -> (PKCS2.hmac_sha1 , null) + | HMAC_SHA224 -> (PKCS2.hmac_sha224 , null) + | HMAC_SHA256 -> (PKCS2.hmac_sha256 , null) + | HMAC_SHA384 -> (PKCS2.hmac_sha384 , null) + | HMAC_SHA512 -> (PKCS2.hmac_sha512 , null) + + | AES128_CBC iv -> (PKCS5.aes128_cbc , octets iv) + | AES192_CBC iv -> (PKCS5.aes192_cbc , octets iv) + | AES256_CBC iv -> (PKCS5.aes256_cbc , octets iv) + + | SHA_RC4_128 (s, i) -> (PKCS12.pbe_with_SHA_and_128Bit_RC4, pbe (s, i)) + | SHA_RC4_40 (s, i) -> (PKCS12.pbe_with_SHA_and_40Bit_RC4, pbe (s, i)) + | SHA_3DES_CBC (s, i) -> (PKCS12.pbe_with_SHA_and_3_KeyTripleDES_CBC, pbe (s, i)) + | SHA_2DES_CBC (s, i) -> (PKCS12.pbe_with_SHA_and_2_KeyTripleDES_CBC, pbe (s, i)) + | SHA_RC2_128_CBC (s, i) -> (PKCS12.pbe_with_SHA_and_128Bit_RC2_CBC, pbe (s, i)) + | SHA_RC2_40_CBC (s, i) -> (PKCS12.pbe_with_SHA_and_40Bit_RC2_CBC, pbe (s, i)) + + | PBKDF2 (s, i, k, m) -> (PKCS5.pbkdf2, pbkdf2 (s, i, k, m)) + | PBES2 (oid, oid') -> (PKCS5.pbes2, pbes2 (oid, oid')) in - map f g @@ - sequence2 - (required ~label:"algorithm" oid) - (optional ~label:"params" (choice2 null oid)) + fix (fun id -> + let pbkdf2_or_pbe_or_pbes2_params = + (* TODO PBKDF2 should support `C2 oid (saltSources) *) + let f (salt, count, (* key_len, *) prf) = + match salt, count, (* key_len, *) prf with + | `C1 salt, Some count, (* None, *) None -> `PBE (salt, count) + | `C1 salt, Some count, (* x, *) Some prf -> `PBKDF2 (salt, count, None, prf) + | `C2 oid, None, (* None, *) Some oid' -> `PBES2 (oid, oid') + | _ -> parse_error "bad parameters" + and g = function + | `PBE (salt, count) -> (`C1 salt, Some count, (* None, *) None) + | `PBKDF2 (salt, count, _key_len, prf) -> (`C1 salt, Some count, (* key_len, *) Some prf) + | `PBES2 (oid, oid') -> (`C2 oid, None, (* None, *) Some oid') + in + map f g @@ + sequence3 + (required ~label:"salt" (choice2 octet_string id)) + (optional ~label:"iteration count" int) (* modified - required for pbkdf2/pbes *) + (* (optional ~label:"key length" int) (* should be there and optional *) *) + (optional ~label:"prf" id) (* only present in pbkdf2 / pbes2 *) + in + map f g @@ + sequence2 + (required ~label:"algorithm" oid) + (optional ~label:"params" + (choice4 null oid pbkdf2_or_pbe_or_pbes2_params octet_string))) let ecdsa_sig = let f (r, s) = diff --git a/lib/dune b/lib/dune index 87f61722..dfb1bfbb 100644 --- a/lib/dune +++ b/lib/dune @@ -3,6 +3,7 @@ (public_name x509) (private_modules asn_grammars registry authenticator certificate validation public_key private_key crl distinguished_name algorithm - extension pem signing_request general_name host) + extension pem signing_request general_name host rc2 p12) (libraries asn1-combinators rresult fmt ptime cstruct mirage-crypto - mirage-crypto-pk gmap domain-name base64 logs mirage-crypto-ec)) + mirage-crypto-pk gmap domain-name base64 logs mirage-crypto-ec + pbkdf mirage-crypto-rng)) diff --git a/lib/p12.ml b/lib/p12.ml new file mode 100644 index 00000000..b1835b14 --- /dev/null +++ b/lib/p12.ml @@ -0,0 +1,442 @@ +(* partial PKCS12 implementation, as defined in RFC 7292 + - no public/private key mode, only password privacy and integrity + - algorithmidentifier those I need for openssl interop (looking at the p12 I have on my disk) + - require version being 3 + + some definitions from PKCS7 (RFC 2315) are implemented as well, as needed +*) + +type content_info = Asn.oid * Cstruct.t + +type digest_info = Algorithm.t * Cstruct.t + +type mac_data = digest_info * Cstruct.t * int + +type t = Cstruct.t * mac_data + +module Asn = struct + open Asn_grammars + open Asn.S + open Registry + + let encrypted_content_info = + let f (oid, algo, content) = + if Asn.OID.equal PKCS7.data oid then + (algo, content) + else + parse_error "expected OID PKCS7 data" + and g (algo, content) = + (PKCS7.data, algo, content) + in + Asn.S.map f g @@ + sequence3 + (required ~label:"content type" oid) (* here we assume data!? *) + (required ~label:"content encryption algorithm" Algorithm.identifier) + (optional ~label:"encrypted content" (implicit 0 octet_string)) + + let encrypted_data = + let f (v, eci) = + if v = 0 then eci else parse_error "unknown encrypted data version" + and g eci = 0, eci + in + map f g @@ + sequence2 + (required ~label:"version" int) + (required ~label:"encrypted content info" encrypted_content_info) + + let content_info = + let f (oid, data) = + match data with + | None -> parse_error "found no value for content info" + | Some `C1 data when Asn.OID.equal PKCS7.data oid -> `Data data + | Some `C2 eci when Asn.OID.equal PKCS7.encrypted_data oid -> `Encrypted eci + | _ -> parse_error "couldn't match PKCS7 oid with choice" + and g = function + | `Data data -> PKCS7.data, Some (`C1 data) + | `Encrypted eci -> PKCS7.encrypted_data, Some (`C2 eci) + in + map f g @@ + sequence2 + (required ~label:"content type" oid) + (optional ~label:"content" (explicit 0 + (choice2 octet_string encrypted_data))) + + let digest_info = + sequence2 + (required ~label:"digest algorithm" Algorithm.identifier) + (required ~label:"digest" octet_string) + + let mac_data = + sequence3 + (required ~label:"mac" digest_info) + (required ~label:"mac salt" octet_string) + (required ~label:"iterations" int) + + let pfx = + let f (version, content_info, mac_data) = + if version = 3 then + match content_info, mac_data with + | `Data data, Some md -> data, md + | _, None -> parse_error "missing mac_data" + | _, _ -> parse_error "unsupported content_info" + else + parse_error "unsupported pfx version" + and g (content, mac_data) = + (3, `Data content, Some mac_data) + in + map f g @@ + sequence3 + (required ~label:"version" int) + (required ~label:"auth safe" content_info) + (* contentType is signedData in public-key integrity mode and data in + password integrity mode *) + (optional ~label:"mac data" mac_data) (* not present if public keys used *) + + let pfx_of_cs, pfx_to_cs = projections_of Asn.der pfx + + (* payload is a sequence of content_info *) + let authenticated_safe = sequence_of content_info + + let auth_safe_of_cs, auth_safe_to_cs = + projections_of Asn.der authenticated_safe + + let pkcs12_attribute = + sequence2 + (required ~label:"attribute id" oid) + (required ~label:"attribute value" (set_of octet_string)) + + (* here: + key_bag = PKCS8 private key + pkcs8_shrouded_key_bag = encrypted private key info == + sequence2 Algorithm octet_string + cert_bag = + sequence2 + cert_id (PKCS9 cert_types <| 1 (X509) or 2 (SDSI)) + expl 0 cert_value (DER-encoded certificate) + crl_bag = sequence2 crl_id (PKCS9 crl_types <| 1) (expl 0 crl (DER-encoded)) + +^^^---^^^ those we plan to support + + secret_bag = sequence2 secret_type (expl 0 secret_value) + safe_contents_bag = (any of the above) safe_contents (recursive!) + *) + (* since asn1 does not yet support ANY defined BY, we develop a rather + complex grammar covering all supported bags *) + let safe_bag = + let cert_oid, crl_oid = + Asn.OID.(PKCS9.cert_types <| 1, PKCS9.crl_types <| 1) + in + let f (oid, (a, algo, data), attrs) = + match a, algo, data with + | `C1 v, Some a, `C1 data when Asn.OID.equal oid PKCS12.key_bag -> + let key = Private_key.Asn.reparse_private (v, a, data) in + `Private_key key, attrs + | `C2 id, None, `C2 data -> + if Asn.OID.equal oid PKCS12.cert_bag && Asn.OID.equal id cert_oid then + match Certificate.decode_der data with + | Error (`Msg e) -> error (`Parse e) + | Ok cert -> `Certificate cert, attrs + else if Asn.OID.equal oid PKCS12.crl_bag && Asn.OID.equal id crl_oid then + match Crl.decode_der data with + | Error (`Msg e) -> error (`Parse e) + | Ok crl -> `Crl crl, attrs + else + parse_error "crl bag with non-standard crl" + | `C3 algo, None, `C1 data when Asn.OID.equal oid PKCS12.pkcs8_shrouded_key_bag -> + `Encrypted_private_key (algo, data), attrs + | _ -> parse_error "safe bag OID not supported" + and g (v, attrs) = + let oid, d = match v with + | `Encrypted_private_key (algo, data) -> + PKCS12.pkcs8_shrouded_key_bag, (`C3 algo, None, `C1 data) + | `Private_key pk -> + let v, algo, data = Private_key.Asn.unparse_private pk in + PKCS12.key_bag, (`C1 v, Some algo, `C1 data) + | `Certificate cert -> PKCS12.cert_bag, (`C2 cert_oid, None, `C2 (Certificate.encode_der cert)) + | `Crl crl -> PKCS12.crl_bag, (`C2 crl_oid, None, `C2 (Crl.encode_der crl)) + in + (oid, d, attrs) + in + map f g @@ + sequence3 + (required ~label:"bag id" oid) + (required ~label:"bag value" + (explicit 0 + (sequence3 + (required ~label:"fst" (choice3 int oid Algorithm.identifier)) + (optional ~label:"algorithm" Algorithm.identifier) + (required ~label:"data" (choice2 octet_string (explicit 0 octet_string)))))) + (* (explicit 0 (* encrypted private key *) + (sequence2 + (required ~label:"encryption algorithm" Algorithm.identifier) + (required ~label:"encrypted data" octet_string))) *) + (* (explicit 0 (* private key ] *) + (sequence3 + (required ~label:"version" int) + (required ~label:"privateKeyAlgorithm" Algorithm.identifier) + (required ~label:"privateKey" octet_string))) *) + (* (explicit 0 (* cert / crl *) + (sequence2 + (required ~label:"oid" oid) + (required ~label:"data" (explicit 0 octet_string)))) *) + (optional ~label:"bag attributes" (set_of pkcs12_attribute)) + + let safe_contents = sequence_of safe_bag + + let safe_contents_of_cs, safe_contents_to_cs = + projections_of Asn.der safe_contents +end + +let prepare_pw str = + let l = String.length str in + let cs = Cstruct.create ((succ l) * 2) in + for i = 0 to pred l do + Cstruct.set_char cs (succ (i * 2)) (String.get str i) + done; + cs + +let id len purpose = + let b = Cstruct.create len in + let id = match purpose with + | `Encryption -> 1 + | `Iv -> 2 + | `Hmac -> 3 + in + Cstruct.memset b id; + b + +let v = function + | `MD5 | `SHA1 | `SHA224 | `SHA256 -> 512 / 8 + | `SHA384 | `SHA512 -> 1024 / 8 + +let fill ~data ~out = + let len = Cstruct.len out + and l = Cstruct.len data + in + let rec c off = + if off < len then begin + Cstruct.blit data 0 out off (min (len - off) l); + c (off + l) + end + in + c 0 + +let fill_or_empty size data = + let l = Cstruct.len data in + if l = 0 then data + else + let len = size * ((l + size - 1) / size) in + let buf = Cstruct.create len in + fill ~data ~out:buf; + buf + +let pbes algorithm purpose password salt iterations n = + let pw = prepare_pw password + and v = v algorithm + and u = Mirage_crypto.Hash.digest_size algorithm + in + let diversifier = id v purpose in + let salt = fill_or_empty v salt in + let pass = fill_or_empty v pw in + let out = Cstruct.create n in + let rec one off i = + let ai = ref (Mirage_crypto.Hash.digest algorithm (Cstruct.append diversifier i)) in + for _j = 1 to pred iterations do + ai := Mirage_crypto.Hash.digest algorithm !ai; + done; + Cstruct.blit !ai 0 out off (min (n - off) u); + if u >= n - off then () else + (* 6B *) + let b = Cstruct.create v in + fill ~data:!ai ~out:b; + (* 6C *) + let i' = Cstruct.create (Cstruct.len i) in + for j = 0 to pred (Cstruct.len i / v) do + let c = ref 1 in + for k = pred v downto 0 do + let idx = j * v + k in + c := (!c + Cstruct.get_uint8 i idx + Cstruct.get_uint8 b k) land 0xFFFF; + Cstruct.set_uint8 i' idx (!c land 0xFF); + c := !c lsr 8; + done; + done; + one (off + u) i' + in + let i = Cstruct.append salt pass in + one 0 i; + out + +(* TODO PKCS5/7 padding is "k - (l mod k)" i.e. always > 0! + (and rc4 being a stream cipher has no padding!) *) +let unpad x = + (* TODO can there be bad padding in this scheme? *) + let l = Cstruct.len x in + if l > 0 then + let amount = Cstruct.get_uint8 x (pred l) in + let split_point = if l > amount then l - amount else l in + let data, pad = Cstruct.split x split_point in + let good = ref true in + for i = 0 to pred amount do + if Cstruct.get_uint8 pad i <> amount then good := false + done; + if !good then data else x + else + x + +let pad bs x = + let l = Cstruct.len x in + let to_pad = bs - (l mod bs) in + let amount = Cstruct.create to_pad in + Cstruct.memset amount to_pad; + Cstruct.append x amount + +(* there are 3 possibilities to encrypt / decrypt things: + - PKCS12 KDF (see above), with RC2/RC4/DES + - PKCS5 v1 (PBES, PBKDF1) -- not (yet?) supported + - PKCS5 v2 (PBES2, PBKDF2) +*) +let pkcs12_decrypt algo password data = + let open Algorithm in + let open Rresult.R.Infix in + let hash = `SHA1 in + (match algo with + | SHA_RC4_128 (s, i) -> Ok (s, i, 16, 0) + | SHA_RC4_40 (s, i) -> Ok (s, i, 5, 0) + | SHA_3DES_CBC (s, i) -> Ok (s, i, 24, 8) + | SHA_2DES_CBC (s, i) -> Ok (s, i, 16, 8) (* TODO 2des -> 3des keys (if relevant)*) + | SHA_RC2_128_CBC (s, i) -> Ok (s, i, 16, 8) + | SHA_RC2_40_CBC (s, i) -> Ok (s, i, 5, 8) + | _ -> Error (`Msg "unsupported algorithm")) >>= fun (salt, count, key_len, iv_len) -> + let key = pbes hash `Encryption password salt count key_len + and iv = pbes hash `Iv password salt count iv_len + in + (match algo with + | SHA_RC2_40_CBC _ | SHA_RC2_128_CBC _ -> + Ok (Rc2.decrypt_cbc ~effective:(key_len * 8) ~key ~iv data) + | SHA_RC4_40 _ | SHA_RC4_128 _ -> + let open Mirage_crypto.Cipher_stream in + let key = ARC4.of_secret key in + let { ARC4.message ; _ } = ARC4.decrypt ~key data in + Ok message + | SHA_3DES_CBC _ -> + let open Mirage_crypto.Cipher_block in + let key = DES.CBC.of_secret key in + Ok (DES.CBC.decrypt ~key ~iv data) + | _ -> Error (`Msg "encryption algorithm not supported")) >>| fun data -> + unpad data + +let pkcs5_2_decrypt kdf enc password data = + let open Rresult.R.Infix in + (match enc with + | Algorithm.AES128_CBC iv -> Ok (16l, iv) + | Algorithm.AES192_CBC iv -> Ok (24l, iv) + | Algorithm.AES256_CBC iv -> Ok (32l, iv) + | _ -> Error (`Msg "unsupported encryption algorithm")) >>= fun (dk_len, iv) -> + (match kdf with + | Algorithm.PBKDF2 (salt, iterations, _ (* todo handle keylength *), prf) -> + (match Algorithm.to_hmac prf with + | Some prf -> Ok prf + | None -> Error (`Msg "unsupported PRF")) >>| fun prf -> + (salt, iterations, prf) + | _ -> Error (`Msg "expected kdf being pbkdf2")) >>| fun (salt, count, prf) -> + let password = Cstruct.of_string password in + let key = Pbkdf.pbkdf2 ~prf ~password ~salt ~count ~dk_len in + let key = Mirage_crypto.Cipher_block.AES.CBC.of_secret key in + let msg = Mirage_crypto.Cipher_block.AES.CBC.decrypt ~key ~iv data in + unpad msg + +let pkcs5_2_encrypt (mac : [ `SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ]) count algo password data = + let bs = Mirage_crypto.Cipher_block.AES.CBC.block_size in + let iv = Mirage_crypto_rng.generate bs in + let enc, dk_len = + match algo with + | `AES128_CBC -> Algorithm.AES128_CBC iv, 16l + | `AES192_CBC -> Algorithm.AES192_CBC iv, 24l + | `AES256_CBC -> Algorithm.AES256_CBC iv, 32l + in + let password = Cstruct.of_string password in + let salt = Mirage_crypto_rng.generate (Mirage_crypto.Hash.digest_size mac) in + let key = Pbkdf.pbkdf2 ~prf:(mac :> Mirage_crypto.Hash.hash) ~password ~salt ~count ~dk_len in + let key = Mirage_crypto.Cipher_block.AES.CBC.of_secret key in + let padded_data = pad bs data in + let enc_data = + Mirage_crypto.Cipher_block.AES.CBC.encrypt ~key ~iv padded_data + in + let kdf = Algorithm.PBKDF2 (salt, count, None, Algorithm.of_hmac mac) in + Algorithm.PBES2 (kdf, enc), enc_data + +let decrypt algo password data = + let open Algorithm in + match algo with + | SHA_RC4_128 _ | SHA_RC4_40 _ + | SHA_3DES_CBC _ | SHA_2DES_CBC _ + | SHA_RC2_128_CBC _ | SHA_RC2_40_CBC _ -> pkcs12_decrypt algo password data + | PBES2 (kdf, enc) -> pkcs5_2_decrypt kdf enc password data + | _ -> Error (`Msg "unsupported encryption algorithm") + +let password_decrypt password (algo, data) = + match data with + | None -> Error (`Msg "no data to decrypt") + | Some data -> decrypt algo password data + +let verify password (data, ((algorithm, digest), salt, iterations)) = + let open Rresult.R.Infix in + (match Algorithm.to_hash algorithm with + | Some hash -> Ok hash + | None -> Error (`Msg "unsupported hash algorithm")) >>= fun hash -> + let key = + pbes hash `Hmac password salt iterations (Mirage_crypto.Hash.digest_size hash) + in + let computed = Mirage_crypto.Hash.mac hash ~key data in + if Cstruct.equal computed digest then begin + Asn_grammars.err_to_msg (Asn.auth_safe_of_cs data) >>= fun content -> + List.fold_left (fun acc c -> + acc >>= fun acc -> + match c with + | `Data data -> Ok (data :: acc) + | `Encrypted data -> + password_decrypt password data >>| fun data -> + (data :: acc)) + (Ok []) content >>= fun safe_contents -> + List.fold_left (fun acc cs -> + acc >>= fun acc -> + Asn_grammars.err_to_msg (Asn.safe_contents_of_cs cs) >>= fun bags -> + List.fold_left (fun acc bag -> + acc >>= fun acc -> + match bag with + | `Certificate c, _ -> Ok (`Certificate c :: acc) + | `Crl c, _ -> Ok (`Crl c :: acc) + | `Private_key p, _ -> Ok (`Private_key p :: acc) + | `Encrypted_private_key (algo, enc_data), _ -> + decrypt algo password enc_data >>= fun data -> + Asn_grammars.err_to_msg (Private_key.Asn.private_of_cstruct data) >>= fun p -> + Ok (`Decrypted_private_key p :: acc)) + (Ok acc) bags) + (Ok []) safe_contents + end else + Error (`Msg "invalid signature") + +let create ?(mac = `SHA256) ?(algorithm = `AES256_CBC) ?(iterations = 2048) password certificates private_key = + let cert_sc = + Asn.safe_contents_to_cs (List.map (fun c -> `Certificate c, None) certificates) + and priv_sc = + let data = Private_key.Asn.private_to_cstruct private_key in + let algo, data = pkcs5_2_encrypt mac iterations algorithm password data in + Asn.safe_contents_to_cs [ `Encrypted_private_key (algo, data), None ] + in + let cert_sc_enc = + let algo, data = pkcs5_2_encrypt mac iterations algorithm password cert_sc in + algo, Some data + in + let auth_data = + Asn.auth_safe_to_cs [ `Encrypted cert_sc_enc ; `Data priv_sc ] + in + let mac_size = Mirage_crypto.Hash.digest_size mac in + let salt = Mirage_crypto_rng.generate mac_size in + let key = pbes mac `Hmac password salt iterations mac_size in + let digest = Mirage_crypto.Hash.mac mac ~key auth_data in + auth_data, ((Algorithm.of_hash mac, digest), salt, iterations) + +let decode_der cs = Asn_grammars.err_to_msg (Asn.pfx_of_cs cs) + +let encode_der = Asn.pfx_to_cs diff --git a/lib/rc2.ml b/lib/rc2.ml new file mode 100644 index 00000000..6ee2584d --- /dev/null +++ b/lib/rc2.ml @@ -0,0 +1,175 @@ + +let pitable = [| + 0xd9; 0x78; 0xf9; 0xc4; 0x19; 0xdd; 0xb5; 0xed; 0x28; 0xe9; 0xfd; 0x79; 0x4a; 0xa0; 0xd8; 0x9d; + 0xc6; 0x7e; 0x37; 0x83; 0x2b; 0x76; 0x53; 0x8e; 0x62; 0x4c; 0x64; 0x88; 0x44; 0x8b; 0xfb; 0xa2; + 0x17; 0x9a; 0x59; 0xf5; 0x87; 0xb3; 0x4f; 0x13; 0x61; 0x45; 0x6d; 0x8d; 0x09; 0x81; 0x7d; 0x32; + 0xbd; 0x8f; 0x40; 0xeb; 0x86; 0xb7; 0x7b; 0x0b; 0xf0; 0x95; 0x21; 0x22; 0x5c; 0x6b; 0x4e; 0x82; + 0x54; 0xd6; 0x65; 0x93; 0xce; 0x60; 0xb2; 0x1c; 0x73; 0x56; 0xc0; 0x14; 0xa7; 0x8c; 0xf1; 0xdc; + 0x12; 0x75; 0xca; 0x1f; 0x3b; 0xbe; 0xe4; 0xd1; 0x42; 0x3d; 0xd4; 0x30; 0xa3; 0x3c; 0xb6; 0x26; + 0x6f; 0xbf; 0x0e; 0xda; 0x46; 0x69; 0x07; 0x57; 0x27; 0xf2; 0x1d; 0x9b; 0xbc; 0x94; 0x43; 0x03; + 0xf8; 0x11; 0xc7; 0xf6; 0x90; 0xef; 0x3e; 0xe7; 0x06; 0xc3; 0xd5; 0x2f; 0xc8; 0x66; 0x1e; 0xd7; + 0x08; 0xe8; 0xea; 0xde; 0x80; 0x52; 0xee; 0xf7; 0x84; 0xaa; 0x72; 0xac; 0x35; 0x4d; 0x6a; 0x2a; + 0x96; 0x1a; 0xd2; 0x71; 0x5a; 0x15; 0x49; 0x74; 0x4b; 0x9f; 0xd0; 0x5e; 0x04; 0x18; 0xa4; 0xec; + 0xc2; 0xe0; 0x41; 0x6e; 0x0f; 0x51; 0xcb; 0xcc; 0x24; 0x91; 0xaf; 0x50; 0xa1; 0xf4; 0x70; 0x39; + 0x99; 0x7c; 0x3a; 0x85; 0x23; 0xb8; 0xb4; 0x7a; 0xfc; 0x02; 0x36; 0x5b; 0x25; 0x55; 0x97; 0x31; + 0x2d; 0x5d; 0xfa; 0x98; 0xe3; 0x8a; 0x92; 0xae; 0x05; 0xdf; 0x29; 0x10; 0x67; 0x6c; 0xba; 0xc9; + 0xd3; 0x00; 0xe6; 0xcf; 0xe1; 0x9e; 0xa8; 0x2c; 0x63; 0x16; 0x01; 0x3f; 0x58; 0xe2; 0x89; 0xa9; + 0x0d; 0x38; 0x34; 0x1b; 0xab; 0x33; 0xff; 0xb0; 0xbb; 0x48; 0x0c; 0x5f; 0xb9; 0xb1; 0xcd; 0x2e; + 0xc5; 0xf3; 0xdb; 0x47; 0xe5; 0xa5; 0x9c; 0x77; 0x0a; 0xa6; 0x20; 0x68; 0xfe; 0x7f; 0xc1; 0xad +|] + +(* effective is sometimes named t1 *) +let tm effective = + let t8 = (effective + 7) / 8 in + (* RFC says (TM = 255 MOD 2^(8 + effective - 8*T8)) *) + let bits = 8 + effective - 8 * t8 in + (* likely there's a smarter way to do this *) + let rec c acc = function + | 0 -> acc + | n -> c ((acc lsl 1) + 1) (pred n) + in + t8, c 0 bits + +(* L[i] is the i-th byte of the key; K[i] is the i-th 16-bit-word of the key *) +let key_expansion effective key = + (* result is a 128 byte key, where we need the words.. *) + let t = Cstruct.len key in + let l = Array.init 128 (fun idx -> if idx < t then Cstruct.get_uint8 key idx else 0) in + let t8, tm = tm effective in + for i = t to 127 do + l.(i) <- pitable.((l.(i - 1) + l.(i - t)) mod 256) + done; + l.(128 - t8) <- pitable.(l.(128 - t8) land tm); + for i = 127 - t8 downto 0 do + l.(i) <- pitable.(l.(i + 1) lxor l.(i + t8)); + done; + Array.init 64 (fun idx -> l.(2 * idx) + 256 * l.(2 * idx + 1)) + +let mod16 f = 0xFFFF land f + +let rol16 x k = mod16 ((x lsl k) lor (x lsr (16 - k))) + +let ror16 x k = mod16 ((x lsr k) lor (x lsl (16 - k))) + +let not16 x = mod16 (lnot x) + +let s = Array.init 4 (function 0 -> 1 | 1 -> 2 | 2 -> 3 | 3 -> 5 | _ -> assert false) + +let pmod a = + let b = 4 in + let r = a mod b in + if r < 0 then (r + b) mod b else r + +(* only used for encryption which we don't support +let mix r i k j = + r.(i) <- mod16 (r.(i) + k.(j) + r.(pmod (i - 1)) land r.(pmod (i - 2)) + + (not16 r.(pmod (i - 1))) land r.(pmod (i - 3))); + let j = succ j in + r.(i) <- rol16 r.(i) s.(i); + j + +let mix_round r k j = + let j' = mix r 0 k j in + let j'' = mix r 1 k j' in + let j''' = mix r 2 k j'' in + let j'''' = mix r 3 k j''' in + j'''' + +let mash r i k = + r.(i) <- mod16 (r.(i) + k.(r.(pmod (i - 1)) land 63)) + +let mash_round r k = + mash r 0 k; + mash r 1 k; + mash r 2 k; + mash r 3 k + +let encrypt_one ~key ~data = + let r = Array.init 4 (fun idx -> Cstruct.LE.get_uint16 data (idx * 2)) in + let j = 0 in + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + mash_round r key; + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + mash_round r key; + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + let j = mix_round r key j in + let _j = mix_round r key j in + let out = Cstruct.create 8 in + Cstruct.LE.set_uint16 out 0 r.(0); + Cstruct.LE.set_uint16 out 2 r.(1); + Cstruct.LE.set_uint16 out 4 r.(2); + Cstruct.LE.set_uint16 out 6 r.(3); + out +*) + +let r_mix r i k j = + r.(i) <- ror16 r.(i) s.(i); + r.(i) <- mod16 (r.(i) - k.(j) - + (r.(pmod (i - 1)) land r.(pmod (i - 2))) - + (not16 r.(pmod (i - 1)) land (r.(pmod (i - 3))))); + pred j + +let r_mix_round r k j = + let j' = r_mix r 3 k j in + let j'' = r_mix r 2 k j' in + let j''' = r_mix r 1 k j'' in + let j'''' = r_mix r 0 k j''' in + j'''' + +let r_mash r i k = + r.(i) <- mod16 (r.(i) - k.(r.(pmod (i - 1)) land 63)) + +let r_mash_round r k = + r_mash r 3 k; + r_mash r 2 k; + r_mash r 1 k; + r_mash r 0 k + +let decrypt_one ~key ~data ?(off = 0) dst = + let r = Array.init 4 (fun idx -> Cstruct.LE.get_uint16 data (off + idx * 2)) in + let j = 63 in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + r_mash_round r key; + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + r_mash_round r key; + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let j = r_mix_round r key j in + let _j = r_mix_round r key j in + Cstruct.LE.set_uint16 dst (off + 0) r.(0); + Cstruct.LE.set_uint16 dst (off + 2) r.(1); + Cstruct.LE.set_uint16 dst (off + 4) r.(2); + Cstruct.LE.set_uint16 dst (off + 6) r.(3) + +let decrypt_cbc ?(effective = 128) ~key ~iv data = + let block = 8 in + let key = key_expansion effective key in + let l = Cstruct.len data in + let dst = Cstruct.create l in + for i = 0 to pred ((l + pred block) / block) do + decrypt_one ~key ~data ~off:(i * block) dst + done; + Mirage_crypto.Uncommon.Cs.xor_into iv dst block; + Mirage_crypto.Uncommon.Cs.xor_into data (Cstruct.shift dst block) (l - block); + dst diff --git a/lib/registry.ml b/lib/registry.ml index c89a7cdf..84602bce 100644 --- a/lib/registry.ml +++ b/lib/registry.ml @@ -114,17 +114,22 @@ module PKCS5 = struct and pbkdf2 = pkcs5 <| 12 and pbes2 = pkcs5 <| 13 and pbmac1 = pkcs5 <| 14 + + let aes = nist_alg <| 1 + let aes128_cbc = aes <| 2 + and aes192_cbc = aes <| 22 + and aes256_cbc = aes <| 42 end module PKCS7 = struct let pkcs7 = pkcs <| 7 - let data = pkcs7 <| 1 - and signedData = pkcs7 <| 2 - and envelopedData = pkcs7 <| 3 - and signedAndEnvelopedData = pkcs7 <| 4 - and digestedData = pkcs7 <| 5 - and encryptedData = pkcs7 <| 6 + let data = pkcs7 <| 1 + and signed_data = pkcs7 <| 2 + and enveloped_data = pkcs7 <| 3 + and signed_and_enveloped_data = pkcs7 <| 4 + and digested_data = pkcs7 <| 5 + and encrypted_data = pkcs7 <| 6 end module PKCS9 = struct @@ -143,6 +148,29 @@ module PKCS9 = struct and smime_oid_registry = pkcs9 <| 16 and friendly_name = pkcs9 <| 20 and cert_types = pkcs9 <| 22 + and crl_types = pkcs9 <| 23 +end + +module PKCS12 = struct + let pkcs12 = pkcs <| 12 + + let bagtypes = pkcs12 <| 10 <| 1 + + let key_bag = bagtypes <| 1 + and pkcs8_shrouded_key_bag = bagtypes <| 2 + and cert_bag = bagtypes <| 3 + and crl_bag = bagtypes <| 4 + and secret_bag = bagtypes <| 5 + and safe_contents_bag = bagtypes <| 6 + + let pbe_ids = pkcs12 <| 1 + + let pbe_with_SHA_and_128Bit_RC4 = pbe_ids <| 1 + and pbe_with_SHA_and_40Bit_RC4 = pbe_ids <| 2 + and pbe_with_SHA_and_3_KeyTripleDES_CBC = pbe_ids <| 3 + and pbe_with_SHA_and_2_KeyTripleDES_CBC = pbe_ids <| 4 + and pbe_with_SHA_and_128Bit_RC2_CBC = pbe_ids <| 5 + and pbe_with_SHA_and_40Bit_RC2_CBC = pbe_ids <| 6 end module X520 = struct diff --git a/lib/x509.ml b/lib/x509.ml index 0526fa90..55d2790f 100644 --- a/lib/x509.ml +++ b/lib/x509.ml @@ -19,3 +19,5 @@ module Signing_request = Signing_request module CRL = Crl module Authenticator = Authenticator + +module PKCS12 = P12 diff --git a/lib/x509.mli b/lib/x509.mli index b61a2a81..7a649517 100644 --- a/lib/x509.mli +++ b/lib/x509.mli @@ -909,3 +909,33 @@ module Authenticator : sig hash:Mirage_crypto.Hash.hash -> fingerprints:([`host] Domain_name.t * Cstruct.t) list -> t end + +(** PKCS12 archive files *) +module PKCS12 : sig + + (** A PKCS12 encoded archive file, *) + type t + + (** [decode_der buffer] is [t], the PKCS12 archive of [buffer]. *) + val decode_der : Cstruct.t -> (t, [> R.msg ]) result + + (** [encode_der t] is [buf], the PKCS12 encoded archive of [t]. *) + val encode_der : t -> Cstruct.t + + (** [verify password t] verifies and decrypts the PKCS12 archive [t]. The + result is the contents of the archive. *) + val verify : string -> t -> + ([ `Certificate of Certificate.t | `Crl of CRL.t + | `Private_key of Private_key.t | `Decrypted_private_key of Private_key.t ] + list, [> R.msg ]) result + + (** [create ~mac ~algorithm ~iterations password certificates private_key] + constructs a PKCS12 archive with [certificates] and [private_key]. They + are encrypted with [algorithm] (using PBES2, PKCS5v2) and integrity + protected using [mac]. *) + val create : ?mac:[`SHA1 | `SHA224 | `SHA256 | `SHA384 | `SHA512 ] -> + ?algorithm:[ `AES128_CBC | `AES192_CBC | `AES256_CBC ] -> + ?iterations:int -> + string -> Certificate.t list -> Private_key.t -> + t +end diff --git a/tests/dune b/tests/dune index 00f7096c..c7def081 100644 --- a/tests/dune +++ b/tests/dune @@ -1,4 +1,4 @@ (test (name tests) - (deps (source_tree regression) (source_tree testcertificates) (source_tree crl) (source_tree csr)) + (deps (source_tree regression) (source_tree testcertificates) (source_tree crl) (source_tree csr) (source_tree pkcs12)) (libraries x509 alcotest cstruct-unix ptime.clock.os mirage-crypto-pk mirage-crypto-ec mirage-crypto-rng.unix)) diff --git a/tests/pkcs12.ml b/tests/pkcs12.ml new file mode 100644 index 00000000..f246c934 --- /dev/null +++ b/tests/pkcs12.ml @@ -0,0 +1,59 @@ +open X509 + +let cs_mmap file = + Unix_cstruct.of_fd Unix.(openfile file [O_RDONLY] 0) + +let data file = cs_mmap ("./pkcs12/" ^ file) + +let cert = match Certificate.decode_pem (data "certificate.pem") with + | Ok c -> c + | Error _ -> assert false + +let key = match Private_key.decode_pem (data "key.pem") with + | Ok k -> k + | Error _ -> assert false + +let pass = "1234" + +let cert_and_key xs = + match xs with + | [ `Certificate c ; `Decrypted_private_key k ] -> + Alcotest.(check bool __LOC__ true (c = cert && k = key)) + | _ -> Alcotest.fail "expected certificate and key" + +let openssl1 () = + match PKCS12.decode_der (data "ossl.p12") with + | Error _ -> Alcotest.fail "failed to decode ossl.p12" + | Ok data -> + match PKCS12.verify pass data with + | Ok xs -> cert_and_key xs + | Error _ -> Alcotest.fail "failed to verify ossl.p12" + +let openssl2 () = + match PKCS12.decode_der (data "ossl_aes.p12") with + | Error _ -> Alcotest.fail "failed to decode ossl_aes.p12" + | Ok data -> + match PKCS12.verify pass data with + | Ok xs -> cert_and_key xs + | Error _ -> Alcotest.fail "failed to verify ossl_aes.p12" + +let ours () = + match PKCS12.decode_der (data "ours.p12") with + | Error _ -> Alcotest.fail "failed to decode ours.p12" + | Ok data -> + match PKCS12.verify pass data with + | Ok xs -> cert_and_key xs + | Error _ -> Alcotest.fail "failed to verify ours.p12" + +let roundtrip () = + let p12 = PKCS12.create pass [ cert ] key in + match PKCS12.verify pass p12 with + | Ok xs -> cert_and_key xs + | Error _ -> Alcotest.fail "failed roundtrip" + +let tests = [ + "OpenSSL basic", `Quick, openssl1 ; + "OpenSSL AES 256", `Quick, openssl2 ; + "OCaml-X509 AES 256", `Quick, ours ; + "OCaml-X509 create and verify", `Quick, roundtrip ; +] diff --git a/tests/pkcs12/certificate.pem b/tests/pkcs12/certificate.pem new file mode 100644 index 00000000..dedae9f2 --- /dev/null +++ b/tests/pkcs12/certificate.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICDDCCAW+gAwIBAgIIQcOa7kqxp9cwCgYIKoZIzj0EAwQwFjEUMBIGA1UEAwwL +ZXhhbXBsZS5jb20wHhcNMjEwNDA0MTcwMTU3WhcNMjIwNDA0MTcwMTU3WjAWMRQw +EgYDVQQDDAtleGFtcGxlLmNvbTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAXIK +VyKRhKOJjxXQtKJiTX9nM3lZs6qy632NYmG9BwJ74FidW1NYlT0eiN71nMHU9FOH +BZ76AH0ISrbo3hjG7uFzAPMplhTwTlA7IcQoR8FOGjrN0w+H5YJZRtkfYU0hFETU +F4quomVmbrxtcIgFRWLJdf7qciYYJyYc8ZlTZoHpZY02o2QwYjAdBgNVHQ4EFgQU +nku+GxZTewB6/D2bJFQcOkBN4QMwDwYDVR0PAQH/BAUDAwfGADAPBgNVHRMBAf8E +BTADAQH/MB8GA1UdIwQYMBaAFJ5LvhsWU3sAevw9myRUHDpATeEDMAoGCCqGSM49 +BAMEA4GKADCBhgJBfZBX4o5Df/fJUnzmQKo6KFFWlc70VkO3hXH6lUhVRLcT+Ame +6gJUjgYy65GryW4Tx/pFTI7tdX19UDm+kBvgv1sCQRIgxgt/eJ74VsRgt7Br3Smm +px1uULyS4PIGBKT4O4C4bWS1wdzw8ZOlegss1+pkxYYrfJFNJYyBaqY0ScTpvE4F +-----END CERTIFICATE----- +---- diff --git a/tests/pkcs12/key.pem b/tests/pkcs12/key.pem new file mode 100644 index 00000000..847e5426 --- /dev/null +++ b/tests/pkcs12/key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MGACAQAwEAYHKoZIzj0CAQYFK4EEACMESTBHAgEBBEIAtmFgIVel9k9Ivp7S5Mlc +adxdv3KvDHc1j787n4avTUpzk+Aj7g0zxen7UsBOk2q/EGbZbtVFsO4zdOvPqP1+ +m94= +-----END PRIVATE KEY----- diff --git a/tests/pkcs12/ossl.p12 b/tests/pkcs12/ossl.p12 new file mode 100644 index 00000000..f65a26fa Binary files /dev/null and b/tests/pkcs12/ossl.p12 differ diff --git a/tests/pkcs12/ossl_aes.p12 b/tests/pkcs12/ossl_aes.p12 new file mode 100644 index 00000000..3b170f25 Binary files /dev/null and b/tests/pkcs12/ossl_aes.p12 differ diff --git a/tests/pkcs12/ours.p12 b/tests/pkcs12/ours.p12 new file mode 100644 index 00000000..bdb95a36 Binary files /dev/null and b/tests/pkcs12/ours.p12 differ diff --git a/tests/tests.ml b/tests/tests.ml index 355f5a2a..cd222cd1 100644 --- a/tests/tests.ml +++ b/tests/tests.ml @@ -4,6 +4,7 @@ let suites = "Host names", Regression.hostname_tests ; "Revoke", Revoke.revoke_tests ; "CRL", Crltests.crl_tests ; + "PKCS12", Pkcs12.tests ; ] diff --git a/x509.opam b/x509.opam index 1ad0e558..bc1afd77 100644 --- a/x509.opam +++ b/x509.opam @@ -22,17 +22,18 @@ depends: [ "mirage-crypto-pk" "mirage-crypto-pk" {with-test & >= "0.8.10"} "mirage-crypto-ec" + "mirage-crypto-rng" "rresult" "fmt" {>= "0.8.7"} "alcotest" {with-test} "cstruct-unix" {with-test & >= "3.0.0"} - "mirage-crypto-rng" {with-test} "gmap" {>= "0.3.0"} "domain-name" {>= "0.3.0"} "logs" + "pbkdf" ] build: [ - ["dune" "subst"] {pinned} + ["dune" "subst"] {dev} ["dune" "build" "-p" name "-j" jobs] ["dune" "runtest" "-p" name "-j" jobs] {with-test} ] @@ -45,5 +46,5 @@ authority. Authorities must be exchanged over a second channel to establish the trust relationship. This library implements most parts of RFC5280 and RFC6125. The Public Key Cryptography Standards (PKCS) defines encoding and decoding (in ASN.1 DER and PEM format), which is also implemented by this library - -namely PKCS 1, PKCS 7, PKCS 8, PKCS 9 and PKCS 10. +namely PKCS 1, PKCS 5, PKCS 7, PKCS 8, PKCS 9, PKCS 10, and PKCS 12. """