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

#include <string.h>
#include "../internal.h"
#include "internal.h"

#define SIGNER_IN_PROVIDED_CERTS 2
#define SIGNER_IN_OCSP_CERTS 1
#define SIGNER_NOT_FOUND 0

// Set up |X509_STORE_CTX| to verify signer and returns cert chain if verify is
// OK. A |OCSP_RESPID| can be identified either by name or its keyhash.
// https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.2.3
static X509 *ocsp_find_signer_sk(STACK_OF(X509) *certs, OCSP_RESPID *id) {
  if (certs == NULL || id == NULL) {
    return NULL;
  }

  // Easy if lookup by name.
  if (id->type == V_OCSP_RESPID_NAME) {
    return X509_find_by_subject(certs, id->value.byName);
  }

  // Lookup by key hash.
  unsigned char tmphash[SHA_DIGEST_LENGTH], *keyhash;

  // If key hash isn't SHA1 length then forget it.
  if (id->value.byKey == NULL || id->value.byKey->length != SHA_DIGEST_LENGTH) {
    return NULL;
  }
  keyhash = id->value.byKey->data;
  // Calculate hash of each key and compare.
  X509 *cert;
  for (size_t i = 0; i < sk_X509_num(certs); i++) {
    cert = sk_X509_value(certs, i);
    if (X509_pubkey_digest(cert, EVP_sha1(), tmphash, NULL)) {
      if (OPENSSL_memcmp(keyhash, tmphash, SHA_DIGEST_LENGTH) == 0) {
        return cert;
      }
    }
  }

  return NULL;
}

// Find signer in cert stack or |OCSP_BASICRESP|'s cert stack.
static int ocsp_find_signer(X509 **psigner, OCSP_BASICRESP *bs,
                            STACK_OF(X509) *certs, unsigned long flags) {
  if (psigner == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  X509 *signer;
  OCSP_RESPID *rid = bs->tbsResponseData->responderId;
  // look for signer in certs stack.
  signer = ocsp_find_signer_sk(certs, rid);
  if (signer != NULL) {
    *psigner = signer;
    return SIGNER_IN_PROVIDED_CERTS;
  }

  // look in certs stack the responder may have included in |OCSP_BASICRESP|,
  // unless the flags contain |OCSP_NOINTERN|.
  signer = ocsp_find_signer_sk(bs->certs, rid);
  if (signer != NULL && !IS_OCSP_FLAG_SET(flags, OCSP_NOINTERN)) {
    *psigner = signer;
    return SIGNER_IN_OCSP_CERTS;
  }
  // Maybe lookup from store if by subject name.

  *psigner = NULL;
  return SIGNER_NOT_FOUND;
}

// check if public key in signer matches key in |OCSP_BASICRESP|.
static int ocsp_verify_key(OCSP_BASICRESP *bs, X509 *signer) {
  if (signer == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  EVP_PKEY *skey = X509_get_pubkey(signer);
  if (skey == NULL) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_SIGNER_KEY);
    return -1;
  }
  int ret =
      ASN1_item_verify(ASN1_ITEM_rptr(OCSP_RESPDATA), bs->signatureAlgorithm,
                       bs->signature, bs->tbsResponseData, skey);
  EVP_PKEY_free(skey);
  if (ret <= 0) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SIGNATURE_FAILURE);
  }

  return ret;
}

// Set untrusted certificate stack from |OCSP_BASICRESP|.
static int ocsp_setup_untrusted(OCSP_BASICRESP *bs, STACK_OF(X509) *certs,
                                STACK_OF(X509) **untrusted,
                                unsigned long flags) {
  if (untrusted == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }
  if (IS_OCSP_FLAG_SET(flags, OCSP_NOCHAIN)) {
    *untrusted = NULL;
  } else if (bs->certs && certs) {
    *untrusted = sk_X509_dup(bs->certs);
    for (size_t i = 0; i < sk_X509_num(certs); i++) {
      if (!sk_X509_push(*untrusted, sk_X509_value(certs, i))) {
        return -1;
      }
    }
  } else if (certs != NULL) {
    *untrusted = sk_X509_dup(certs);
  } else {
    *untrusted = sk_X509_dup(bs->certs);
  }
  return 1;
}


static int ocsp_verify_signer(X509 *signer, X509_STORE *st,
                              STACK_OF(X509) *untrusted,
                              STACK_OF(X509) **chain) {
  if (signer == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  // Set up |X509_STORE_CTX| with |*signer|, |*st|, and |*untrusted|.
  X509_STORE_CTX *ctx = X509_STORE_CTX_new();
  int ret = -1;

  if (ctx == NULL) {
    goto end;
  }
  if (!X509_STORE_CTX_init(ctx, st, signer, untrusted)) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_X509_LIB);
    goto end;
  }
  if (!X509_STORE_CTX_set_purpose(ctx, X509_PURPOSE_OCSP_HELPER)) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_X509_LIB);
    goto end;
  }

  // Verify |X509_STORE_CTX| and return certificate chain.
  ret = X509_verify_cert(ctx);
  if (ret <= 0) {
    int err = X509_STORE_CTX_get_error(ctx);
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_CERTIFICATE_VERIFY_ERROR);
    ERR_add_error_data(2, "Verify error: ", X509_verify_cert_error_string(err));
    goto end;
  }
  if (chain != NULL) {
    *chain = X509_STORE_CTX_get1_chain(ctx);
  }

end:
  X509_STORE_CTX_free(ctx);
  return ret;
}

// Check the issuer certificate IDs for equality. If the issuer IDs all match
// then we just need to check equality against one of them. If there is a
// mismatch with the same algorithm then there's no point trying to match any
// certificates against the issuer, and |*ret| will be set to NULL.
static int ocsp_check_ids(STACK_OF(OCSP_SINGLERESP) *sresp, OCSP_CERTID **ret) {
  if (sresp == NULL || ret == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  OCSP_CERTID *tmpid, *cid;
  size_t idcount = sk_OCSP_SINGLERESP_num(sresp);
  if (idcount == 0) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_RESPONSE_CONTAINS_NO_REVOCATION_DATA);
    return -1;
  }
  cid = sk_OCSP_SINGLERESP_value(sresp, 0)->certId;

  *ret = NULL;
  for (size_t i = 1; i < idcount; i++) {
    tmpid = sk_OCSP_SINGLERESP_value(sresp, i)->certId;
    // Check to see if IDs match.
    if (OCSP_id_issuer_cmp(cid, tmpid) != 0) {
      // If algorithm mismatch, let caller deal with it instead.
      if (OBJ_cmp(tmpid->hashAlgorithm->algorithm,
                  cid->hashAlgorithm->algorithm) != 0) {
        return 1;
      }
      return 0;
    }
  }

  // All IDs match: only need to check one ID.
  *ret = cid;
  return 1;
}

// Returns -1 on fatal error, 0 if there is no match and 1 if there is a
// match.
static int ocsp_match_issuerid(X509 *cert, OCSP_CERTID *cid,
                               STACK_OF(OCSP_SINGLERESP) *sresp) {
  if (cert == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  // If only one ID to match then do it.
  if (cid) {
    const EVP_MD *dgst;
    X509_NAME *iname;
    unsigned char md[EVP_MAX_MD_SIZE];
    // Set up message digest for comparison.
    dgst = EVP_get_digestbyobj(cid->hashAlgorithm->algorithm);
    if (dgst == NULL) {
      OPENSSL_PUT_ERROR(OCSP, OCSP_R_UNKNOWN_MESSAGE_DIGEST);
      return -1;
    }
    size_t mdlen = EVP_MD_size(dgst);
    iname = X509_get_subject_name(cert);
    if (!X509_NAME_digest(iname, dgst, md, NULL)) {
      return -1;
    }

    // Compare message digest with |OCSP_CERTID|
    if (cid->issuerNameHash->length >= 0 && cid->issuerKeyHash->length >= 0) {
      if (((size_t)cid->issuerNameHash->length != mdlen) ||
          (size_t)cid->issuerKeyHash->length != mdlen) {
        return 0;
      }
    }
    if (0 != OPENSSL_memcmp(md, cid->issuerNameHash->data, mdlen)) {
      return 0;
    }
    if (1 != X509_pubkey_digest(cert, dgst, md, NULL)) {
      return -1;
    }
    if (0 != OPENSSL_memcmp(md, cid->issuerKeyHash->data, mdlen)) {
      return 0;
    }
    return 1;

  }
  // We have to match the whole stack recursively, if |cid| is NULL. This
  // means that the issuer certificate's hash algorithm did not match with
  // the rest of the |certId|s in the |OCSP_SINGLERESP| stack. (Issuer
  // certificate is taken from the root of the |OCSP_SINGLERESP| stack).
  // We'll have to find a match from the signer or responder CA certificate
  // (both are certificates in the certificate chain) in the
  // |OCSP_SINGLERESP| stack instead.
  else {
    int ret;
    OCSP_CERTID *tmpid;
    for (size_t i = 0; i < sk_OCSP_SINGLERESP_num(sresp); i++) {
      tmpid = sk_OCSP_SINGLERESP_value(sresp, i)->certId;
      ret = ocsp_match_issuerid(cert, tmpid, NULL);
      if (ret <= 0) {
        return ret;
      }
    }
    return 1;
  }
}

static int ocsp_check_delegated(X509 *x) {
  if (x == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  if ((X509_get_extension_flags(x) & EXFLAG_XKUSAGE) &&
      (X509_get_extended_key_usage(x) & XKU_OCSP_SIGN)) {
    return 1;
  }
  OPENSSL_PUT_ERROR(OCSP, OCSP_R_MISSING_OCSPSIGNING_USAGE);
  return 0;
}

// check OCSP responder issuer and ids
static int ocsp_check_issuer(OCSP_BASICRESP *bs, STACK_OF(X509) *chain) {
  if (chain == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  STACK_OF(OCSP_SINGLERESP) *sresp = bs->tbsResponseData->responses;
  OCSP_CERTID *caid = NULL;
  int ret;

  if (sk_X509_num(chain) <= 0) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_CERTIFICATES_IN_CHAIN);
    return -1;
  }

  // See if the issuer IDs match.
  ret = ocsp_check_ids(sresp, &caid);

  // If ID mismatch or other error then return.
  if (ret <= 0) {
    return ret;
  }

  X509 *signer, *sca;
  signer = sk_X509_value(chain, 0);
  // Check to see if OCSP responder CA matches request CA.
  if (sk_X509_num(chain) > 1) {
    sca = sk_X509_value(chain, 1);
    ret = ocsp_match_issuerid(sca, caid, sresp);
    if (ret < 0) {
      return ret;
    }
    if (ret != 0) {
      // If matches, then check extension flags.
      if (ocsp_check_delegated(signer)) {
        return 1;
      }
      return 0;
    }
  }

  // Otherwise check if OCSP request signed directly by request CA.
  return ocsp_match_issuerid(signer, caid, sresp);
}

int OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs, X509_STORE *st,
                      unsigned long flags) {
  if (bs == NULL || st == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  X509 *signer;
  STACK_OF(X509) *chain = NULL;
  STACK_OF(X509) *untrusted = NULL;

  // Look for signer certificate.
  int ret = ocsp_find_signer(&signer, bs, certs, flags);
  if (ret <= SIGNER_NOT_FOUND) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SIGNER_CERTIFICATE_NOT_FOUND);
    goto end;
  }
  if ((ret == SIGNER_IN_PROVIDED_CERTS) &&
      IS_OCSP_FLAG_SET(flags, OCSP_TRUSTOTHER)) {
    // We skip verification if the flag to trust |certs| is set and the signer
    // is found within that stack.
    flags |= OCSP_NOVERIFY;
  }

  // Check if public key in signer matches key in |OCSP_BASICRESP|.
  ret = ocsp_verify_key(bs, signer);
  if (ret <= 0) {
    goto end;
  }
  if (!IS_OCSP_FLAG_SET(flags, OCSP_NOVERIFY)) {
    // Verify signer and if valid, check the certificate chain.
    ret = ocsp_setup_untrusted(bs, certs, &untrusted, flags);
    if (ret <= 0) {
      goto end;
    }
    ret = ocsp_verify_signer(signer, st, untrusted, &chain);
    if (ret <= 0) {
      goto end;
    }

    // At this point we have a valid certificate chain, need to verify it
    // against the OCSP issuer criteria.
    ret = ocsp_check_issuer(bs, chain);

    // If a certificate chain is not verifiable against the OCSP issuer
    // criteria, we try to check for explicit trust.
    if (ret == 0) {
      // Easy case: explicitly trusted. Get root CA and check for explicit
      // trust.
      if (IS_OCSP_FLAG_SET(flags, OCSP_NOEXPLICIT)) {
        goto end;
      }
      X509 *root_cert = sk_X509_value(chain, sk_X509_num(chain) - 1);
      if (X509_check_trust(root_cert, NID_OCSP_sign, 0) != X509_TRUST_TRUSTED) {
        OPENSSL_PUT_ERROR(OCSP, OCSP_R_ROOT_CA_NOT_TRUSTED);
        ret = 0;
        goto end;
      }
      ret = 1;
    }
  }

end:
  sk_X509_pop_free(chain, X509_free);
  sk_X509_free(untrusted);
  return ret;
}

// ocsp_req_find_signer assigns |*psigner| to the signing certificate and
// returns |SIGNER_IN_OCSP_CERTS| if it was found within |req| or returns
// |SIGNER_IN_TRUSTED_CERTS| if it was found within |certs|. It returns
// |SIGNER_NOT_FOUND| if the signing certificate is not found.
static int ocsp_req_find_signer(X509 **psigner, OCSP_REQUEST *req,
                                X509_NAME *nm, STACK_OF(X509) *certs,
                                unsigned long flags) {
  X509 *signer = NULL;
  if (!IS_OCSP_FLAG_SET(flags, OCSP_NOINTERN)) {
    signer = X509_find_by_subject(req->optionalSignature->certs, nm);
    if (signer != NULL) {
      *psigner = signer;
      return SIGNER_IN_OCSP_CERTS;
    }
  }

  signer = X509_find_by_subject(certs, nm);
  if (signer != NULL) {
    *psigner = signer;
    return SIGNER_IN_PROVIDED_CERTS;
  }
  return SIGNER_NOT_FOUND;
}

int OCSP_request_verify(OCSP_REQUEST *req, STACK_OF(X509) *certs,
                        X509_STORE *store, unsigned long flags) {
  GUARD_PTR(req);
  GUARD_PTR(req->tbsRequest);
  GUARD_PTR(store);

  // Check if |req| has signature to check against.
  if (req->optionalSignature == NULL) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_REQUEST_NOT_SIGNED);
    return 0;
  }

  GENERAL_NAME *gen = req->tbsRequest->requestorName;
  if (gen == NULL || gen->type != GEN_DIRNAME) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_UNSUPPORTED_REQUESTORNAME_TYPE);
    return 0;
  }

  // Find |signer| from |certs| or |req->optionalSignature->certs| against criteria.
  X509 *signer = NULL;
  int signer_status =
      ocsp_req_find_signer(&signer, req, gen->d.directoryName, certs, flags);
  if (signer_status <= SIGNER_NOT_FOUND || signer == NULL) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SIGNER_CERTIFICATE_NOT_FOUND);
    return 0;
  }
  if (signer_status == SIGNER_IN_PROVIDED_CERTS &&
      IS_OCSP_FLAG_SET(flags, OCSP_TRUSTOTHER)) {
    // We skip certificate verification if the flag to trust |certs| is set and
    // the signer is found within that stack.
    flags |= OCSP_NOVERIFY;
  }

  // Validate |req|'s signature.
  EVP_PKEY *skey = X509_get0_pubkey(signer);
  if (skey == NULL) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_NO_SIGNER_KEY);
    return 0;
  }
  if (ASN1_item_verify(ASN1_ITEM_rptr(OCSP_REQINFO),
                       req->optionalSignature->signatureAlgorithm,
                       req->optionalSignature->signature, req->tbsRequest,
                       skey) <= 0) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_SIGNATURE_FAILURE);
    return 0;
  }

  // Set up |ctx| and start doing the actual verification.
  X509_STORE_CTX *ctx = X509_STORE_CTX_new();
  if (ctx == NULL) {
    return 0;
  }

  // Validate the signing certificate.
  int ret = 0;
  if (!IS_OCSP_FLAG_SET(flags, OCSP_NOVERIFY)) {
    // Initialize and set purpose of |ctx| for verification.
    if (1 != X509_STORE_CTX_init(ctx, store, signer, NULL) ||
        1 != X509_STORE_CTX_set_purpose(ctx, X509_PURPOSE_OCSP_HELPER)) {
      OPENSSL_PUT_ERROR(OCSP, ERR_R_X509_LIB);
      goto end;
    }
    if (!IS_OCSP_FLAG_SET(flags, OCSP_NOCHAIN)) {
      X509_STORE_CTX_set_chain(ctx, req->optionalSignature->certs);
    }

    // Do the verification.
    if (X509_verify_cert(ctx) <= 0) {
      int err = X509_STORE_CTX_get_error(ctx);
      OPENSSL_PUT_ERROR(OCSP, OCSP_R_CERTIFICATE_VERIFY_ERROR);
      ERR_add_error_data(2,
                         "Verify error:", X509_verify_cert_error_string(err));
      goto end;
    }
  }
  ret = 1;

end:
  X509_STORE_CTX_free(ctx);
  return ret;
}
