/****************************************************************************
 * apps/crypto/controlse/mbedtls_extension.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/* Copyright The Mbed TLS Contributors
 * SPDX-License-Identifier: Apache-2.0
 *
 * The source code in this file is based on
 * mbedtls_x509write_crt_der() method from x509write_crt.c in Mbed TLS
 */

/* Copyright 2023 NXP */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#include <mbedtls/asn1write.h>
#include <mbedtls/error.h>
#include <mbedtls/oid.h>
#include <mbedtls/x509_crt.h>
#include <nuttx/crypto/se05x.h>
#include <sys/ioctl.h>
#include <string.h>

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static int x509_write_time(FAR unsigned char **p, FAR unsigned char *start,
                           FAR const char *t, size_t size)
{
  int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
  size_t len = 0;

  /* write MBEDTLS_ASN1_UTC_TIME if year < 2050 (2 bytes shorter) */

  if (t[0] == '2' && t[1] == '0' && t[2] < '5')
    {
      MBEDTLS_ASN1_CHK_ADD(
          len, mbedtls_asn1_write_raw_buffer(
                   p, start, (FAR const unsigned char *)t + 2, size - 2));
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(p, start, len));
      MBEDTLS_ASN1_CHK_ADD(
          len, mbedtls_asn1_write_tag(p, start, MBEDTLS_ASN1_UTC_TIME));
    }
  else
    {
      MBEDTLS_ASN1_CHK_ADD(len,
                           mbedtls_asn1_write_raw_buffer(
                             p, start, (FAR const unsigned char *)t, size));
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(p, start, len));
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_tag(
                                  p, start, MBEDTLS_ASN1_GENERALIZED_TIME));
    }

  return ((int)len);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int mbedtls_x509write_crt_der_se05x(FAR mbedtls_x509write_cert *ctx,
                                    FAR unsigned char *buf, size_t size,
                                    int se05x_fd, uint32_t private_key_id)
{
  int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
  FAR const char *sig_oid;
  size_t sig_oid_len = 0;
  FAR unsigned char *c;
  FAR unsigned char *c2;
  unsigned char hash[64];
  unsigned char sig[MBEDTLS_PK_SIGNATURE_MAX_SIZE];
  size_t sub_len = 0;
  size_t pub_len = 0;
  size_t sig_and_oid_len = 0;
  size_t sig_len;
  size_t len = 0;
  mbedtls_pk_type_t pk_alg;

  /* Prepare data to be signed at the end of the target buffer */

  c = buf + size;

  /* Signature algorithm needed in TBS, and later for actual signature */

  /* There's no direct way of extracting a signature algorithm
   * (represented as an element of mbedtls_pk_type_t) from a PK instance.
   */

  if (mbedtls_pk_can_do(ctx->issuer_key, MBEDTLS_PK_RSA))
    {
      pk_alg = MBEDTLS_PK_RSA;
    }
  else if (mbedtls_pk_can_do(ctx->issuer_key, MBEDTLS_PK_ECDSA))
    {
      pk_alg = MBEDTLS_PK_ECDSA;
    }
  else
    {
      return (MBEDTLS_ERR_X509_INVALID_ALG);
    }

  if ((ret = mbedtls_oid_get_oid_by_sig_alg(pk_alg, ctx->md_alg, &sig_oid,
                                            &sig_oid_len)) != 0)
    {
      return (ret);
    }

  /* Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension */

  /* Only for v3 */

  if (ctx->version == MBEDTLS_X509_CRT_VERSION_3)
    {
      MBEDTLS_ASN1_CHK_ADD(
          len, mbedtls_x509_write_extensions(&c, buf, ctx->extensions));
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, len));
      MBEDTLS_ASN1_CHK_ADD(len,
                           mbedtls_asn1_write_tag(&c, buf,
                                                  MBEDTLS_ASN1_CONSTRUCTED |
                                                    MBEDTLS_ASN1_SEQUENCE));
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, len));
      MBEDTLS_ASN1_CHK_ADD(
          len, mbedtls_asn1_write_tag(&c, buf,
                                      MBEDTLS_ASN1_CONTEXT_SPECIFIC |
                                          MBEDTLS_ASN1_CONSTRUCTED | 3));
    }

  /*  SubjectPublicKeyInfo  */

  MBEDTLS_ASN1_CHK_ADD(
      pub_len, mbedtls_pk_write_pubkey_der(ctx->subject_key, buf, c - buf));
  c -= pub_len;
  len += pub_len;

  /*  Subject  ::=  Name  */

  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_x509_write_names(&c, buf, ctx->subject));

  /*  Validity ::= SEQUENCE {
   *       notBefore      Time,
   *       notAfter       Time }
   */

  sub_len = 0;

  MBEDTLS_ASN1_CHK_ADD(sub_len,
                       x509_write_time(&c, buf, ctx->not_after,
                                       MBEDTLS_X509_RFC5280_UTC_TIME_LEN));

  MBEDTLS_ASN1_CHK_ADD(sub_len,
                       x509_write_time(&c, buf, ctx->not_before,
                                       MBEDTLS_X509_RFC5280_UTC_TIME_LEN));

  len += sub_len;
  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, sub_len));
  MBEDTLS_ASN1_CHK_ADD(
      len, mbedtls_asn1_write_tag(
               &c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE));

  /*  Issuer  ::=  Name  */

  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_x509_write_names(&c, buf, ctx->issuer));

  /*  Signature   ::=  AlgorithmIdentifier  */

  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_algorithm_identifier(
                                &c, buf, sig_oid, strlen(sig_oid), 0));

  /*  Serial   ::=  INTEGER
   *
   * Written data is:
   * - "ctx->serial_len" bytes for the raw serial buffer
   *   - if MSb of "serial" is 1, then prepend an extra 0x00 byte
   * - 1 byte for the length
   * - 1 byte for the TAG
   */

  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_raw_buffer(&c, buf,
                                            ctx->serial, ctx->serial_len));
  if (*c & 0x80)
    {
      if (c - buf < 1)
        {
          return MBEDTLS_ERR_X509_BUFFER_TOO_SMALL;
        }

      *(--c) = 0x0;
      len++;
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf,
                                                       ctx->serial_len + 1));
    }
  else
    {
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf,
                                                       ctx->serial_len));
    }

  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_tag(&c, buf,
                                                   MBEDTLS_ASN1_INTEGER));

  /*  Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }  */

  /* Can be omitted for v1 */

  if (ctx->version != MBEDTLS_X509_CRT_VERSION_1)
    {
      sub_len = 0;
      MBEDTLS_ASN1_CHK_ADD(sub_len,
                           mbedtls_asn1_write_int(&c, buf, ctx->version));
      len += sub_len;
      MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, sub_len));
      MBEDTLS_ASN1_CHK_ADD(
          len, mbedtls_asn1_write_tag(&c, buf,
                                      MBEDTLS_ASN1_CONTEXT_SPECIFIC |
                                          MBEDTLS_ASN1_CONSTRUCTED | 0));
    }

  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, len));
  MBEDTLS_ASN1_CHK_ADD(
      len, mbedtls_asn1_write_tag(
               &c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE));

  /* Make signature
   */

  /* Compute hash of CRT. */

  if ((ret = mbedtls_md(mbedtls_md_info_from_type(ctx->md_alg), c, len,
                        hash)) != 0)
    {
      return (ret);
    }

    {
      struct se05x_signature_s args = {
          .key_id = private_key_id,
          .algorithm = SE05X_ALGORITHM_SHA256,
          .tbs = {
            .buffer = hash,
            .buffer_size = 32,
            .buffer_content_size = 32
          },
          .signature = {.buffer = sig, .buffer_size = sizeof(sig)},
      };

      ret = ioctl(se05x_fd, SEIOC_CREATE_SIGNATURE, &args);
      if (ret != 0)
        {
          return ret;
        }

      sig_len = args.signature.buffer_content_size;
    }

  /* Move CRT to the front of the buffer to have space
   * for the signature.
   */

  memmove(buf, c, len);
  c = buf + len;

  /* Add signature at the end of the buffer,
   * making sure that it doesn't underflow
   * into the CRT buffer.
   */

  c2 = buf + size;
  MBEDTLS_ASN1_CHK_ADD(
      sig_and_oid_len,
      mbedtls_x509_write_sig(&c2, c, sig_oid, sig_oid_len, sig, sig_len));

  /* Memory layout after this step:
   *
   * buf       c=buf+len                c2            buf+size
   * [CRT0,...,CRTn, UNUSED, ..., UNUSED, SIG0, ..., SIGm]
   */

  /* Move raw CRT to just before the signature. */

  c = c2 - len;
  memmove(c, buf, len);

  len += sig_and_oid_len;
  MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, len));
  MBEDTLS_ASN1_CHK_ADD(
      len, mbedtls_asn1_write_tag(
               &c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE));

  return ((int)len);
}