(* https://tools.ietf.org/html/rfc6960 *)
let version_v1 = 0
(*
CertID ::= SEQUENCE {
hashAlgorithm AlgorithmIdentifier,
issuerNameHash OCTET STRING, -- Hash of issuer's DN
issuerKeyHash OCTET STRING, -- Hash of issuer's public key
serialNumber CertificateSerialNumber }
*)
type cert_id = {
hashAlgorithm: Algorithm.t;
issuerNameHash: string;
issuerKeyHash: string;
serialNumber: string;
}
let create_cert_id ?(hash=`SHA1) issuer serialNumber =
let hashAlgorithm = Algorithm.of_hash hash in
let module Hash = (val (Digestif.module_of_hash' (hash :> Digestif.hash'))) in
let issuerNameHash =
Certificate.subject issuer
|> Distinguished_name.encode_der
|> Hash.(fun x -> to_raw_string (digest_string x))
in
let issuerKeyHash =
Public_key.fingerprint ~hash (Certificate.public_key issuer)
in
{hashAlgorithm;issuerNameHash;issuerKeyHash;serialNumber}
let cert_id_serial {serialNumber;_} = serialNumber
let pp_cert_id ppf {hashAlgorithm;issuerNameHash;issuerKeyHash;serialNumber} =
Fmt.pf ppf "CertID @[<1>{@ algo=%a;@ issuerNameHash=%a;@ issuerKeyHash=%a;@ serialNumber=%a@ }@]"
Algorithm.pp hashAlgorithm
Ohex.pp issuerNameHash
Ohex.pp issuerKeyHash
Ohex.pp serialNumber
module Asn_common = struct
open Asn.S
let cert_id =
let f (hashAlgorithm, issuerNameHash, issuerKeyHash, serialNumber) =
{hashAlgorithm; issuerNameHash; issuerKeyHash; serialNumber;}
in
let g {hashAlgorithm;issuerNameHash;issuerKeyHash;serialNumber;} =
(hashAlgorithm, issuerNameHash, issuerKeyHash, serialNumber)
in
map f g @@
sequence4
(required ~label:"hashAlgorithm" Algorithm.identifier)
(required ~label:"issuerNameHash" octet_string)
(required ~label:"issuerKeyHash" octet_string)
(required ~label:"serialNumber" Asn_grammars.serial)
end
let ( let* ) = Result.bind
module Request = struct
(*
Request ::= SEQUENCE {
reqCert CertID,
singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL }
*)
type request = {
reqCert: cert_id;
singleRequestExtensions: Extension.t option;
}
let create_request ?singleRequestExtensions reqCert =
{reqCert;singleRequestExtensions}
let pp_request ppf {reqCert;singleRequestExtensions;} =
Fmt.pf ppf "Request @[<1>{@ reqCert=%a;@ singleRequestExtensions=%a;@ }@]"
pp_cert_id reqCert
(Fmt.option ~none:(Fmt.any "None") Extension.pp) singleRequestExtensions
(*
TBSRequest ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
requestorName [1] EXPLICIT GeneralName OPTIONAL,
requestList SEQUENCE OF Request,
requestExtensions [2] EXPLICIT Extensions OPTIONAL }
*)
type tbs_request = {
requestorName: General_name.b option;
requestList: request list;
requestExtensions: Extension.t option;
}
let create_tbs_request ?requestorName ?requestExtensions requests =
{ requestorName ; requestList=requests ; requestExtensions }
let pp_tbs_request ppf { requestorName ; requestList ; requestExtensions } =
let pp_general_name ppf x =
let open General_name in
match x with
| B (k, v) -> General_name.pp_k k ppf v
in
Fmt.pf ppf "TBSRequest @[<1>{@ requestorName=%a;@ requestList=[@ %a@ ];@ requestExtensions=%a@ }@]"
(Fmt.option ~none:(Fmt.any "None") pp_general_name) requestorName
(Fmt.list ~sep:Fmt.semi pp_request) requestList
(Fmt.option ~none:(Fmt.any "None") Extension.pp) requestExtensions
(*
Signature ::= SEQUENCE {
signatureAlgorithm AlgorithmIdentifier,
signature BIT STRING,
certs [0] EXPLICIT SEQUENCE OF Certificate
OPTIONAL}
*)
type signature = {
signatureAlgorithm: Algorithm.t;
signature: string;
certs: Certificate.t list option;
}
let pp_signature ppf {signatureAlgorithm;signature;certs;} =
Fmt.pf ppf "Signature @[<1>{@ signatureAlgorithm=%a;@ signature=%a;@ certs=%a}@]"
Algorithm.pp signatureAlgorithm
Ohex.pp signature
(Fmt.option ~none:(Fmt.any "None") @@
Fmt.brackets @@
Fmt.list ~sep:Fmt.semi Certificate.pp) certs
(*
OCSPRequest ::= SEQUENCE {
tbsRequest TBSRequest,
optionalSignature [0] EXPLICIT Signature OPTIONAL }
*)
type req = {
tbsRequest: tbs_request;
optionalSignature: signature option;
}
type t = {
raw : string ;
asn : req ;
}
let pp ppf { asn = { tbsRequest ; optionalSignature } ; _ } =
Fmt.pf ppf "OCSPRequest @[<1>{@ tbsRequest=%a;@ optionalSignature=%a@ }@]"
pp_tbs_request tbsRequest
(Fmt.option ~none:(Fmt.any "None") pp_signature) optionalSignature
let cert_ids { asn = { tbsRequest = { requestList ; _ } ; _ } ; _ } =
let cert_ids = List.map (fun {reqCert;_} -> reqCert) requestList in
cert_ids
let requestor_name { asn = { tbsRequest = { requestorName ; _ } ; _ } ; _ } =
requestorName
module Asn_ = Asn
module Asn = struct
open Asn_grammars
open Asn.S
let request =
let f (reqCert, singleRequestExtensions) =
{reqCert; singleRequestExtensions}
in
let g {reqCert; singleRequestExtensions} =
(reqCert, singleRequestExtensions)
in
map f g @@
sequence2
(required ~label:"reqCert" Asn_common.cert_id)
(optional ~label:"singleRequestExtensions" @@ explicit 0
Extension.Asn.extensions_der)
let tbs_request =
let f (version, requestorName, requestList, requestExtensions) =
match version with
| Some v when v <> version_v1 ->
Asn.S.parse_error "unsupported version %d" v
| _ ->
{ requestorName ; requestList ; requestExtensions }
in
let g { requestorName ; requestList ; requestExtensions } =
(None, requestorName, requestList, requestExtensions)
in
map f g @@
sequence4
(optional ~label:"version" @@ explicit 0 int)
(optional ~label:"requestorName" @@
explicit 1 General_name.Asn.general_name)
(required ~label:"requestList" @@ sequence_of request)
(optional ~label:"requestExtensions" @@ Extension.Asn.extensions_der)
let tbs_request_of_str,tbs_request_to_str =
projections_of Asn.der tbs_request
let signature =
let f (signatureAlgorithm,signature,certs) =
let certs = match certs with
| None -> None
| Some certs ->
let encode cert =
let raw = Certificate.Asn.certificate_to_octets cert in
Certificate.{raw; asn=cert}
in
Some (List.map encode certs)
in
{signatureAlgorithm;signature;certs}
in
let g {signatureAlgorithm;signature;certs} =
let certs = match certs with
| None -> None
| Some certs ->
Some (List.map (fun Certificate.{asn;_} -> asn) certs)
in
(signatureAlgorithm,signature,certs)
in
map f g @@
sequence3
(required ~label:"signatureAlgorithm" Algorithm.identifier)
(required ~label:"signature" bit_string_octets)
(optional ~label:"certs" @@ explicit 0 @@
sequence_of Certificate.Asn.certificate)
let ocsp_request =
let f (tbsRequest,optionalSignature) =
{tbsRequest;optionalSignature;}
in
let g {tbsRequest;optionalSignature;} =
(tbsRequest,optionalSignature)
in
map f g @@
sequence2
(required ~label:"tbsRequest" tbs_request)
(optional ~label:"optionalSignature" signature)
let (ocsp_request_of_octets, ocsp_request_to_octets) =
projections_of Asn.der ocsp_request
end
let decode_der raw =
let* asn = Asn.ocsp_request_of_octets raw in
Ok { asn ; raw }
let encode_der { raw ; _ } = raw
let create ?certs ?digest ?requestor_name:requestorName ?key cert_ids =
let requestList = List.map create_request cert_ids in
let tbsRequest = {
requestorName;
requestList;
requestExtensions=None;
}
in
let* optionalSignature =
match key with
| None -> Ok None
| Some key ->
let digest = Signing_request.default_digest digest key in
let scheme = Key_type.x509_default_scheme (Private_key.key_type key) in
let signatureAlgorithm = Algorithm.of_signature_algorithm scheme digest in
let tbs_der = Asn.tbs_request_to_str tbsRequest in
let* signature = Private_key.sign digest ~scheme key (`Message tbs_der) in
Ok (Some { signature ; signatureAlgorithm ; certs; })
in
let asn = { tbsRequest ; optionalSignature } in
let raw = Asn.ocsp_request_to_octets asn in
Ok { raw ; asn }
let validate { asn ; raw } ?(allowed_hashes = Validation.sha2) pub =
match asn.optionalSignature with
| None -> Error `No_signature
| Some sign ->
let tbs_raw = Validation.raw_cert_hack raw in
let dn =
let cn = "OCSP" in
[ Distinguished_name.(Relative_distinguished_name.singleton (CN cn)) ]
in
Validation.validate_raw_signature dn allowed_hashes tbs_raw
sign.signatureAlgorithm sign.signature pub
end
module Response = struct
(* OCSPResponseStatus ::= ENUMERATED {
* successful (0), -- Response has valid confirmations
* malformedRequest (1), -- Illegal confirmation request
* internalError (2), -- Internal error in issuer
* tryLater (3), -- Try again later
* -- (4) is not used
* sigRequired (5), -- Must sign the request
* unauthorized (6) -- Request unauthorized
* } *)
type status = [
| `Successful
| `MalformedRequest
| `InternalError
| `TryLater
| `SigRequired
| `Unauthorized
]
let status_to_int = function
| `Successful -> 0
| `MalformedRequest -> 1
| `InternalError -> 2
| `TryLater -> 3
| `SigRequired -> 5
| `Unauthorized -> 6
let status_of_int = function
| 0 -> `Successful
| 1 -> `MalformedRequest
| 2 -> `InternalError
| 3 -> `TryLater
| 5 -> `SigRequired
| 6 -> `Unauthorized
| x -> Asn.S.parse_error "Unknown status %d" x
let pp_status ppf = function
| `Successful -> Fmt.string ppf "Successful"
| `MalformedRequest -> Fmt.string ppf "MalformedRequest"
| `InternalError -> Fmt.string ppf "InternalError"
| `TryLater -> Fmt.string ppf "TryLater"
| `SigRequired -> Fmt.string ppf "SigRequired"
| `Unauthorized -> Fmt.string ppf "Unauthorized"
(* RevokedInfo ::= SEQUENCE {
* revocationTime GeneralizedTime,
* revocationReason [0] EXPLICIT CRLReason OPTIONAL } *)
type revoked_info = Ptime.t * Extension.reason option
let pp_revoked_info ppf (revocationTime,revocationReason) =
Fmt.pf ppf "RevokedInfo @[<1>{@ revocationTime=%a;@ revocationReason=%a;@ }@]"
Ptime.pp revocationTime
(Fmt.option ~none:(Fmt.any "None") @@ Extension.pp_reason)
revocationReason
(* CertStatus ::= CHOICE {
* good [0] IMPLICIT NULL,
* revoked [1] IMPLICIT RevokedInfo,
* unknown [2] IMPLICIT UnknownInfo } *)
type cert_status = [
| `Good
| `Revoked of revoked_info
| `Unknown
]
let pp_cert_status ppf = function
| `Good -> Fmt.pf ppf "Good"
| `Revoked info -> Fmt.pf ppf "Revoked of %a" pp_revoked_info info
| `Unknown -> Fmt.pf ppf "Unknown"
(* SingleResponse ::= SEQUENCE {
* certID CertID,
* certStatus CertStatus,
* thisUpdate GeneralizedTime,
* nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
* singleExtensions [1] EXPLICIT Extensions OPTIONAL } *)
type single_response = {
certID: cert_id;
certStatus: cert_status;
thisUpdate: Ptime.t;
nextUpdate: Ptime.t option;
singleExtensions: Extension.t option;
}
let create_single_response ?next_update:nextUpdate
?single_extensions:singleExtensions
certID certStatus thisUpdate =
{certID;certStatus;thisUpdate;nextUpdate;singleExtensions;}
let pp_single_response ppf {certID;certStatus;thisUpdate;nextUpdate;singleExtensions;} =
Fmt.pf ppf "SingleResponse @[<1>{@ certID=%a;@ certStatus=%a;@ thisUpdate=%a;@ nextUpdate=%a;@ singleExtensions=%a;@ }@]"
pp_cert_id certID
pp_cert_status certStatus
Ptime.pp thisUpdate
(Fmt.option ~none:(Fmt.any "None") @@ Ptime.pp) nextUpdate
(Fmt.option ~none:(Fmt.any "None") @@ Extension.pp) singleExtensions
let single_response_cert_id {certID;_} = certID
let single_response_status {certStatus;_} = certStatus
(* ResponderID ::= CHOICE {
* byName [1] Name,
* byKey [2] KeyHash }
* KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
(excluding the tag and length fields)
*)
type responder_id = [
| `ByName of Distinguished_name.t
| `ByKey of string
]
let create_responder_id pubkey =
let pubkey_fp = Public_key.fingerprint ~hash:`SHA1 pubkey in
`ByKey pubkey_fp
let pp_responder_id ppf = function
| `ByName dn -> Fmt.pf ppf "ByName %a" Distinguished_name.pp dn
| `ByKey hash -> Fmt.pf ppf "ByKey %a" Ohex.pp hash
(* ResponseData ::= SEQUENCE {
* version [0] EXPLICIT Version DEFAULT v1,
* responderID ResponderID,
* producedAt GeneralizedTime,
* responses SEQUENCE OF SingleResponse,
* responseExtensions [1] EXPLICIT Extensions OPTIONAL } *)
type response_data = {
responderID: responder_id;
producedAt: Ptime.t;
responses: single_response list;
responseExtensions: Extension.t option;
}
let pp_response_data ppf { responderID ; producedAt ; responses ; responseExtensions } =
Fmt.pf ppf "ResponseData @[<1>{@ responderID=%a;@ producedAt=%a;@ responses=%a;@ responseExtensions=%a@ }@]"
pp_responder_id responderID
Ptime.pp producedAt
(Fmt.list ~sep:Fmt.semi @@ pp_single_response) responses
(Fmt.option ~none:(Fmt.any "None") @@ Extension.pp) responseExtensions
(* BasicOCSPResponse ::= SEQUENCE {
* tbsResponseData ResponseData,
* signatureAlgorithm AlgorithmIdentifier,
* signature BIT STRING,
* certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } *)
type basic_ocsp_response = {
tbsResponseData: response_data;
signatureAlgorithm: Algorithm.t;
signature: string;
certs: Certificate.t list option;
}
let pp_basic_ocsp_response ppf {tbsResponseData;signatureAlgorithm;signature;certs;} =
Fmt.pf ppf "BasicOCSPResponse @[<1>{@ tbsResponseData=%a;@ signatureAlgorithm=%a;@ signature=%a;@ certs=%a@ }@]"
pp_response_data tbsResponseData
Algorithm.pp signatureAlgorithm
Ohex.pp signature
(Fmt.option ~none:(Fmt.any "None") @@
Fmt.list ~sep:Fmt.semi @@ Certificate.pp) certs
(* ResponseBytes ::= SEQUENCE {
* responseType OBJECT IDENTIFIER,
* response OCTET STRING } *)
(* OCSPResponse ::= SEQUENCE {
* responseStatus OCSPResponseStatus,
* responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } *)
type t = {
responseStatus: status;
responseBytes: (Asn.oid * basic_ocsp_response * string) option;
}
let pp ppf {responseStatus;responseBytes;} =
Fmt.pf ppf "OCSPResponse @[<1>{@ responseStatus=%a;@ responseBytes=%a@ }@]"
pp_status responseStatus
(Fmt.option ~none:(Fmt.any "None") @@
Fmt.pair ~sep:Fmt.comma Asn.OID.pp pp_basic_ocsp_response)
(match responseBytes with None -> None | Some (a, b, _) -> Some (a, b))
let status {responseStatus;_} = responseStatus
let responder_id = function
| {responseBytes=Some (_, {tbsResponseData={responderID;_};_}, _);_} ->
Ok responderID
| _ -> Error (`Msg "this response has no responseBytes")
let responses = function
| {responseBytes=Some (_, {tbsResponseData={responses;_};_}, _);_} ->
Ok responses
| _ -> Error (`Msg "this response has no responseBytes")
module Asn = struct
open Asn_grammars
open Asn.S
open Registry
let status : status Asn.t =
enumerated status_of_int status_to_int
let revoked_info =
sequence2
(required ~label:"revocationTime" generalized_time_no_frac_s)
(optional ~label:"revocationReason" @@ explicit 0 @@
Extension.Asn.reason_enumerated)
let cert_status : cert_status Asn.t =
let f = function
| `C1 () -> `Good
| `C2 ri -> `Revoked ri
| `C3 () -> `Unknown
in
let g = function
| `Good -> `C1 ()
| `Revoked ri -> `C2 ri
| `Unknown -> `C3 ()
in
map f g @@
choice3
(implicit 0 @@ null)
(implicit 1 @@ revoked_info)
(implicit 2 @@ null)
let single_response =
let f (certID,certStatus,thisUpdate,nextUpdate,singleExtensions) =
{certID;certStatus;thisUpdate;nextUpdate;singleExtensions;}
in
let g {certID;certStatus;thisUpdate;nextUpdate;singleExtensions;} =
(certID,certStatus,thisUpdate,nextUpdate,singleExtensions)
in
map f g @@
sequence5
(required ~label:"certID" @@ Asn_common.cert_id)
(required ~label:"certStatus" @@ cert_status)
(required ~label:"thisUpdate" @@ generalized_time_no_frac_s)
(optional ~label:"nextUpdate" @@ explicit 0 @@
generalized_time_no_frac_s)
(optional ~label:"singleExtensions" @@ explicit 1 @@
Extension.Asn.extensions_der)
let responder_id : responder_id Asn.t =
let f = function
| `C1 dn -> `ByName dn
| `C2 hash -> `ByKey hash
in
let g = function
| `ByName dn -> `C1 dn
| `ByKey hash -> `C2 hash
in
map f g @@
choice2 (explicit 1 Distinguished_name.Asn.name) (explicit 2 octet_string)
let response_data =
let f (version, responderID, producedAt, responses, responseExtensions) =
match version with
| Some v when v <> version_v1 ->
Asn.S.parse_error "unsupported version %d" v
| _ -> { responderID ; producedAt ; responses ; responseExtensions }
in
let g { responderID ; producedAt ; responses ; responseExtensions } =
(None, responderID, producedAt, responses, responseExtensions)
in
map f g @@
sequence5
(optional ~label:"version" @@ explicit 0 @@ int)
(required ~label:"responderID" responder_id)
(required ~label:"producedAt" generalized_time_no_frac_s)
(required ~label:"responses" @@ sequence_of single_response)
(optional ~label:"responseExtensions" @@ explicit 1 @@
Extension.Asn.extensions_der)
let response_data_of_str, response_data_to_str =
projections_of Asn.der response_data
let basic_ocsp_response =
let f (tbsResponseData,signatureAlgorithm,signature,certs) =
let certs = match certs with
| None -> None
| Some certs ->
let encode cert =
let raw = Certificate.Asn.certificate_to_octets cert in
Certificate.{raw; asn=cert}
in
Some (List.map encode certs)
in
{tbsResponseData;signatureAlgorithm;signature;certs}
in
let g {tbsResponseData;signatureAlgorithm;signature;certs} =
let certs = match certs with
| None -> None
| Some certs ->
Some (List.map (fun Certificate.{asn;_} -> asn) certs)
in
(tbsResponseData,signatureAlgorithm,signature,certs)
in
map f g @@
sequence4
(required ~label:"tbsResponseData" response_data)
(required ~label:"signatureAlgorithm" Algorithm.identifier)
(required ~label:"signature" bit_string_octets)
(optional ~label:"certs" @@ explicit 0 @@
sequence_of Certificate.Asn.certificate)
let basic_ocsp_response_of_str,basic_ocsp_response_to_str =
projections_of Asn.der basic_ocsp_response
let ocsp_basic_oid = Cert_extn.Private_internet_extensions.ad_ocsp_basic
let ocsp_response =
let f = function
| `Successful, None ->
parse_error "Successful status requires responseBytes"
| `Successful, Some (oid, response) ->
if Asn.OID.equal ocsp_basic_oid oid then
match basic_ocsp_response_of_str response with
| Error e -> error e
| Ok basic_response ->
{responseStatus=`Successful;
responseBytes=Some (oid, basic_response, response)}
else
parse_error "expected OID ad_ocsp_basic"
| (`InternalError
| `MalformedRequest
| `SigRequired
| `TryLater
|`Unauthorized) as s, None ->
{responseStatus=s;responseBytes=None}
| _, Some _ -> parse_error "Only Successful status supports non empty responseBytes"
in
let g {responseStatus;responseBytes} =
let responseBytes = match responseBytes with
| Some (oid, _basic_response, response) -> Some (oid, response)
| None -> None
in
(responseStatus,responseBytes)
in
map f g @@
sequence2
(required ~label:"responseStatus" status)
(optional ~label:"responseBytes" @@ explicit 0 @@
sequence2
(required ~label:"responseType" oid)
(required ~label:"response" octet_string))
let ocsp_response_of_str, ocsp_response_to_str =
projections_of Asn.der ocsp_response
end
let decode_der = Asn.ocsp_response_of_str
let encode_der = Asn.ocsp_response_to_str
let create_basic_ocsp_response ?digest ?certs
?response_extensions:responseExtensions key responderID producedAt
responses =
let digest = Signing_request.default_digest digest key in
let scheme = Key_type.x509_default_scheme (Private_key.key_type key) in
let signatureAlgorithm = Algorithm.of_signature_algorithm scheme digest in
let tbsResponseData = {
responderID;
producedAt;
responses;
responseExtensions;
} in
let resp_der = Asn.response_data_to_str tbsResponseData in
let* signature = Private_key.sign digest ~scheme key (`Message resp_der) in
Ok { tbsResponseData ; signatureAlgorithm ; signature;certs }
let create_success ?digest ?certs ?response_extensions
private_key responderID producedAt responses =
let* response =
create_basic_ocsp_response
?digest ?certs ?response_extensions private_key
responderID producedAt responses
in
let raw_resp = Asn.basic_ocsp_response_to_str response in
let responseBytes = Some (Asn.ocsp_basic_oid, response, raw_resp) in
Ok { responseStatus = `Successful ; responseBytes }
let create status =
let status = match status with
| `MalformedRequest -> `MalformedRequest
| `InternalError -> `InternalError
| `TryLater -> `TryLater
| `SigRequired -> `SigRequired
| `Unauthorized -> `Unauthorized
in
{responseStatus=status;responseBytes=None}
let validate t ?(allowed_hashes = Validation.sha2) ?now pub =
match t.responseBytes with
| None -> Error `No_signature
| Some (_oid, response, raw_resp) ->
let resp_der = Validation.raw_cert_hack raw_resp in
let dn =
let cn = "OCSP" in
[ Distinguished_name.(Relative_distinguished_name.singleton (CN cn)) ]
in
let* () =
Validation.validate_raw_signature dn allowed_hashes resp_der
response.signatureAlgorithm response.signature pub
in
match now with
| None -> Ok ()
| Some now ->
if
List.for_all (fun single_resp ->
Ptime.is_later ~than:single_resp.thisUpdate now &&
match single_resp.nextUpdate with
| None -> true
| Some until -> Ptime.is_earlier ~than:until now)
response.tbsResponseData.responses
then
Ok ()
else
Error `Time_invalid
end