(* 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
