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

#include <openssl/mem.h>
#include <string.h>

#include "internal.h"

OCSP_CERTID *OCSP_cert_to_id(const EVP_MD *dgst, const X509 *subject,
                             const X509 *issuer) {
  if (issuer == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return NULL;
  }

  const X509_NAME *iname;
  const ASN1_INTEGER *serial;
  ASN1_BIT_STRING *ikey;

  if (dgst == NULL) {
    dgst = EVP_sha1();
  }
  if (subject != NULL) {
    iname = X509_get_issuer_name(subject);
    serial = X509_get0_serialNumber(subject);
  } else {
    iname = X509_get_subject_name(issuer);
    serial = NULL;
  }
  ikey = X509_get0_pubkey_bitstr(issuer);
  return OCSP_cert_id_new(dgst, iname, ikey, serial);
}

OCSP_CERTID *OCSP_cert_id_new(const EVP_MD *dgst, const X509_NAME *issuerName,
                              const ASN1_BIT_STRING *issuerKey,
                              const ASN1_INTEGER *serialNumber) {
  if (dgst == NULL || issuerName == NULL || issuerKey == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return NULL;
  }

  int nid;
  unsigned int i;
  X509_ALGOR *alg;
  unsigned char md[EVP_MAX_MD_SIZE];
  OCSP_CERTID *cid = OCSP_CERTID_new();
  if (cid == NULL) {
    return NULL;
  }

  alg = cid->hashAlgorithm;
  ASN1_OBJECT_free(alg->algorithm);
  nid = EVP_MD_type(dgst);
  if (nid == NID_undef) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_UNKNOWN_NID);
    goto err;
  }
  alg->algorithm = OBJ_nid2obj(nid);
  if (alg->algorithm == NULL) {
    goto err;
  }
  alg->parameter = ASN1_TYPE_new();
  if (alg->parameter == NULL) {
    goto err;
  }
  alg->parameter->type = V_ASN1_NULL;

  if (!X509_NAME_digest(issuerName, dgst, md, &i)) {
    OPENSSL_PUT_ERROR(OCSP, OCSP_R_DIGEST_ERR);
    goto err;
  }
  if (!(ASN1_OCTET_STRING_set(cid->issuerNameHash, md, i))) {
    goto err;
  }
  // Calculate the issuerKey hash, excluding tag and length
  if (!EVP_Digest(issuerKey->data, issuerKey->length, md, &i, dgst, NULL)) {
    goto err;
  }
  if (!(ASN1_OCTET_STRING_set(cid->issuerKeyHash, md, i))) {
    goto err;
  }
  // Only copy |serialNumber| if available. This may be empty.
  if (serialNumber != NULL) {
    if (ASN1_STRING_copy(cid->serialNumber, serialNumber) == 0) {
      goto err;
    }
  }
  return cid;

err:
  OCSP_CERTID_free(cid);
  return NULL;
}

int OCSP_id_issuer_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b) {
  if (a == NULL || b == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  if (a->hashAlgorithm == NULL || b->hashAlgorithm == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  int ret = OBJ_cmp(a->hashAlgorithm->algorithm, b->hashAlgorithm->algorithm);
  if (ret != 0) {
    return ret;
  }
  ret = ASN1_OCTET_STRING_cmp(a->issuerNameHash, b->issuerNameHash);
  if (ret != 0) {
    return ret;
  }
  ret = ASN1_OCTET_STRING_cmp(a->issuerKeyHash, b->issuerKeyHash);
  return ret;
}

int OCSP_id_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b) {
  if (a == NULL || b == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return -1;
  }

  // Compare OCSP issuer name and key
  int ret = OCSP_id_issuer_cmp(a, b);
  if (ret != 0) {
    return ret;
  }
  // Compare certificate serialNumber
  ret = ASN1_INTEGER_cmp(a->serialNumber, b->serialNumber);
  return ret;
}

int OCSP_parse_url(const char *url, char **phost, char **pport, char **ppath,
                   int *pssl) {
  if (url == NULL || phost == NULL || pport == NULL || ppath == NULL ||
      pssl == NULL) {
    OPENSSL_PUT_ERROR(OCSP, ERR_R_PASSED_NULL_PARAMETER);
    return 0;
  }

  char *parser, *buffer;
  char *host = NULL;
  char *port = NULL;

  *phost = NULL;
  *pport = NULL;
  *ppath = NULL;

  // Duplicate into the buffer since the contents are going to be changed.
  buffer = OPENSSL_strdup(url);
  if (buffer == NULL) {
    goto mem_err;
  }

  // Check for initial colon
  parser = strchr(buffer, ':');
  if (parser == NULL) {
    goto parse_err;
  }
  *(parser++) = '\0';

  // Set default ports for http and https. If a port is specified later, this
  // will be overwritten. |pssl| will be set to true, if https is being used.
  if (strncmp(buffer, "https", 5) == 0) {
    *pssl = 1;
    port = (char *)"443";
  } else if (strncmp(buffer, "http", 4) == 0) {
    *pssl = 0;
    port = (char *)"80";
  } else {
    goto parse_err;
  }

  // Check for double slash.
  if ((parser[0] != '/') || (parser[1] != '/')) {
    goto parse_err;
  }
  parser += 2;
  host = parser;

  // Check for trailing part of path.
  parser = strchr(parser, '/');
  if (parser == NULL) {
    // Default is "/" if there is no trailing path in the URL.
    *ppath = OPENSSL_strdup("/");
  } else {
    *ppath = OPENSSL_strdup(parser);
    // Set start of path to 0 so hostname is valid.
    *parser = '\0';
  }
  if (*ppath == NULL) {
    goto mem_err;
  }

  parser = host;
  // Checks if the host is an ipv6 host address.
  if (host[0] == '[') {
    host++;
    parser = strchr(host, ']');
    if (parser == NULL) {
      goto parse_err;
    }
    *parser = '\0';
    parser++;
  }

  // Look for optional ':' for port number.
  if ((parser = strchr(parser, ':'))) {
    *parser = 0;
    port = parser + 1;
  }

  *pport = OPENSSL_strdup(port);
  if (*pport == NULL) {
    goto mem_err;
  }

  *phost = OPENSSL_strdup(host);
  if (*phost == NULL) {
    goto mem_err;
  }
  OPENSSL_free(buffer);
  return 1;

mem_err:
  OPENSSL_PUT_ERROR(OCSP, ERR_R_MALLOC_FAILURE);
  goto err;
parse_err:
  OPENSSL_PUT_ERROR(OCSP, OCSP_R_ERROR_PARSING_URL);
err:
  OPENSSL_free(buffer);
  OPENSSL_free(*ppath);
  *ppath = NULL;
  OPENSSL_free(*pport);
  *pport = NULL;
  OPENSSL_free(*phost);
  *phost = NULL;
  return 0;
}
