// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include "../asn1/internal.h"
#include "internal.h"

OCSP_ONEREQ *OCSP_request_add0_id(OCSP_REQUEST *req, OCSP_CERTID *cid) {
  OCSP_ONEREQ *one = OCSP_ONEREQ_new();
  if (one == NULL) {
    return NULL;
  }
  // Reassign |OCSP_CERTID| allocated by OCSP_ONEREQ_new().
  OCSP_CERTID_free(one->reqCert);
  one->reqCert = cid;
  if (req != NULL && !sk_OCSP_ONEREQ_push(req->tbsRequest->requestList, one)) {
    one->reqCert = NULL;  // do not free on error
    OCSP_ONEREQ_free(one);
    return NULL;
  }
  return one;
}

int OCSP_request_set1_name(OCSP_REQUEST *req, X509_NAME *nm) {
  GENERAL_NAME *gen = GENERAL_NAME_new();
  if (gen == NULL) {
    return 0;
  }
  if (!X509_NAME_set(&gen->d.directoryName, nm)) {
    GENERAL_NAME_free(gen);
    return 0;
  }
  gen->type = GEN_DIRNAME;
  GENERAL_NAME_free(req->tbsRequest->requestorName);
  req->tbsRequest->requestorName = gen;
  return 1;
}

int OCSP_request_add1_cert(OCSP_REQUEST *req, X509 *cert) {
  if (req->optionalSignature == NULL) {
    req->optionalSignature = OCSP_SIGNATURE_new();
  }
  OCSP_SIGNATURE *sig = req->optionalSignature;
  if (sig == NULL) {
    return 0;
  }
  if (cert == NULL) {
    return 1;
  }

  if (sig->certs == NULL && (sig->certs = sk_X509_new_null()) == NULL) {
    return 0;
  }

  if (!sk_X509_push(sig->certs, cert)) {
    return 0;
  }
  // sk_X509_push takes ownership.
  X509_up_ref(cert);
  return 1;
}

int OCSP_request_sign(OCSP_REQUEST *req, X509 *signer, EVP_PKEY *key,
                      const EVP_MD *dgst, STACK_OF(X509) *certs,
                      unsigned long flags) {
  if (req->optionalSignature != NULL) {
    // OpenSSL lets an |OCSP_REQUEST| be signed twice, regardless of whether
    // an |optionalSignature| exists. Signing an OCSP Request twice creates
    // a dangling |OCSP_SIGNATURE| pointer with no clear way of recovering
    // it. We disallow this behavior and only allow the |OCSP_REQUEST| to be
    // signed once. There's no indication or use case detailed in the RFC
    // that the OCSP request can or should be signed twice.
    // https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.2
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_OCSP_REQUEST_DUPLICATE_SIGNATURE);
    goto err;
  }

  if (!OCSP_request_set1_name(req, X509_get_subject_name(signer))) {
    goto err;
  }
  req->optionalSignature = OCSP_SIGNATURE_new();
  if (req->optionalSignature == NULL) {
    goto err;
  }
  if (key != NULL) {
    if (!X509_check_private_key(signer, key)) {
      OPENSSL_PUT_ERROR(OCSP, OCSP_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE);
      goto err;
    }
    if (!ASN1_item_sign(ASN1_ITEM_rptr(OCSP_REQINFO),
                        req->optionalSignature->signatureAlgorithm, NULL,
                        req->optionalSignature->signature, req->tbsRequest, key,
                        dgst)) {
      goto err;
    }
  }

  if (!IS_OCSP_FLAG_SET(flags, OCSP_NOCERTS)) {
    if (!OCSP_request_add1_cert(req, signer)) {
      goto err;
    }
    for (size_t i = 0; i < sk_X509_num(certs); i++) {
      if (!OCSP_request_add1_cert(req, sk_X509_value(certs, i))) {
        goto err;
      }
    }
  }

  return 1;
err:
  OCSP_SIGNATURE_free(req->optionalSignature);
  req->optionalSignature = NULL;
  return 0;
}

int OCSP_response_status(OCSP_RESPONSE *resp) {
  if (resp == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }
  return ASN1_ENUMERATED_get(resp->responseStatus);
}

OCSP_BASICRESP *OCSP_response_get1_basic(OCSP_RESPONSE *resp) {
  if (resp == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return NULL;
  }

  OCSP_RESPBYTES *rb = resp->responseBytes;
  if (rb == NULL) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_RESPONSE_DATA);
    return NULL;
  }
  if (OBJ_obj2nid(rb->responseType) != NID_id_pkix_OCSP_basic) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NOT_BASIC_RESPONSE);
    return NULL;
  }
  return ASN1_item_unpack(rb->response, ASN1_ITEM_rptr(OCSP_BASICRESP));
}

OCSP_SINGLERESP *OCSP_resp_get0(OCSP_BASICRESP *bs, size_t idx) {
  if (bs == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return NULL;
  }
  if (bs->tbsResponseData == NULL) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_RESPONSE_DATA);
    return NULL;
  }
  return sk_OCSP_SINGLERESP_value(bs->tbsResponseData->responses, idx);
}

int OCSP_resp_find(OCSP_BASICRESP *bs, OCSP_CERTID *id, int last) {
  if (bs == NULL || id == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }
  if (bs->tbsResponseData == NULL) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_RESPONSE_DATA);
    return -1;
  }

  // |OCSP_SINGLERESP| stack is in |OCSP_BASICRESP| responseData, we look for
  // |OCSP_CERTID| in here.
  STACK_OF(OCSP_SINGLERESP) *sresp = bs->tbsResponseData->responses;
  OCSP_SINGLERESP *single;

  if (last < 0) {
    last = 0;
  } else {
    last++;
  }
  for (size_t i = last; i < sk_OCSP_SINGLERESP_num(sresp); i++) {
    single = sk_OCSP_SINGLERESP_value(sresp, i);
    if (!OCSP_id_cmp(id, single->certId)) {
      return i;
    }
  }
  return -1;
}

int OCSP_single_get0_status(OCSP_SINGLERESP *single, int *reason,
                            ASN1_GENERALIZEDTIME **revtime,
                            ASN1_GENERALIZEDTIME **thisupd,
                            ASN1_GENERALIZEDTIME **nextupd) {
  if (single == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }
  OCSP_CERTSTATUS *cst = single->certStatus;
  if (cst == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }
  int status = cst->type;

  // If certificate status is revoked, we look up certificate revocation time
  // and reason.
  if (status == V_OCSP_CERTSTATUS_REVOKED) {
    OCSP_REVOKEDINFO *rev = cst->value.revoked;
    if (rev != NULL) {
      if (revtime != NULL) {
        *revtime = rev->revocationTime;
      }
      if (reason != NULL) {
        if (rev->revocationReason) {
          *reason = ASN1_ENUMERATED_get(rev->revocationReason);
        } else {
          *reason = -1;
        }
      }
    }
  }
  // Send back when certificate was last updated and when is the next update
  // time.
  if (thisupd != NULL) {
    *thisupd = single->thisUpdate;
  }
  if (nextupd != NULL) {
    *nextupd = single->nextUpdate;
  }
  return status;
}

int OCSP_resp_find_status(OCSP_BASICRESP *bs, OCSP_CERTID *id, int *status,
                          int *reason, ASN1_GENERALIZEDTIME **revtime,
                          ASN1_GENERALIZEDTIME **thisupd,
                          ASN1_GENERALIZEDTIME **nextupd) {
  if (bs == NULL || id == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  // we look for index of equivalent |OCSP_CERTID| of issuer certificate in
  // |OCSP_BASICRESP|
  int single_idx = OCSP_resp_find(bs, id, -1);
  if (single_idx < 0) {
    return 0;
  }
  OCSP_SINGLERESP *single = OCSP_resp_get0(bs, single_idx);

  // Extract the update time and revocation status of certificate sent back
  // from OCSP responder
  int single_status =
      OCSP_single_get0_status(single, reason, revtime, thisupd, nextupd);
  if (status != NULL) {
    *status = single_status;
  }
  return 1;
}

int OCSP_check_validity(ASN1_GENERALIZEDTIME *thisUpdate,
                        ASN1_GENERALIZEDTIME *nextUpdate,
                        long drift_num_seconds, long max_age_seconds) {
  int ret = 1;
  int64_t t_tmp;
  int64_t t_now = time(NULL);

  // Check |thisUpdate| is valid and not more than |drift_num_seconds| in the
  // future.
  if (!ASN1_GENERALIZEDTIME_check(thisUpdate)) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_ERROR_IN_THISUPDATE_FIELD);
    ret = 0;
  } else {
    t_tmp = t_now + drift_num_seconds;
    if (X509_cmp_time_posix(thisUpdate, t_tmp) > 0) {
      OPENSSL_PUT_ERROR(OCSP, OCSP_R_STATUS_NOT_YET_VALID);
      ret = 0;
    }

    // If |max_num_seconds| is specified, check that |thisUpdate| is not more
    // than |max_num_seconds| in the past.
    if (max_age_seconds >= 0) {
      t_tmp = t_now - max_age_seconds;
      if (X509_cmp_time_posix(thisUpdate, t_tmp) < 0) {
        OPENSSL_PUT_ERROR(OCSP, OCSP_R_STATUS_TOO_OLD);
        ret = 0;
      }
    }
  }

  // If |nextUpdate| field is empty, we have validated everything we can at
  // this point.
  if (nextUpdate == NULL) {
    return ret;
  }

  // Check |nextUpdate| is valid and not more than |drift_num_seconds| in the
  // past.
  if (!ASN1_GENERALIZEDTIME_check(nextUpdate)) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_ERROR_IN_NEXTUPDATE_FIELD);
    ret = 0;
  } else {
    t_tmp = t_now - drift_num_seconds;
    if (X509_cmp_time_posix(nextUpdate, t_tmp) < 0) {
      OPENSSL_PUT_ERROR(OCSP, OCSP_R_STATUS_EXPIRED);
      ret = 0;
    }
  }

  // Also don't allow |nextUpdate| to precede |thisUpdate|.
  if (ASN1_STRING_cmp(nextUpdate, thisUpdate) < 0) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NEXTUPDATE_BEFORE_THISUPDATE);
    ret = 0;
  }

  return ret;
}

int OCSP_resp_count(OCSP_BASICRESP *bs) {
  if (bs == NULL) {
    return -1;
  }
  return (int)sk_OCSP_SINGLERESP_num(bs->tbsResponseData->responses);
}

const OCSP_CERTID *OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP *single) {
  return single->certId;
}
