/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>
 *  Copyright (C) 2010-2026 Sentyron B.V. <openvpn@sentyron.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, see <https://www.gnu.org/licenses/>.
 */

/**
 * @file
 * Control Channel Verification Module mbed TLS backend
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "syshead.h"

#if defined(ENABLE_CRYPTO_MBEDTLS)

#include <mbedtls/version.h>

#if MBEDTLS_VERSION_NUMBER < 0x04000000
#include "crypto_mbedtls_legacy.h"
#include <mbedtls/bignum.h>
#include <mbedtls/sha1.h>
#else
#include "crypto_mbedtls.h"
#endif

#include "mbedtls_compat.h"

#include "ssl_verify.h"
#include <mbedtls/asn1.h>
#include <mbedtls/error.h>
#include <mbedtls/oid.h>

#define MAX_SUBJECT_LENGTH 256

int
verify_callback(void *session_obj, mbedtls_x509_crt *cert, int cert_depth, uint32_t *flags)
{
    struct tls_session *session = (struct tls_session *)session_obj;
    struct gc_arena gc = gc_new();

    ASSERT(cert);
    ASSERT(session);

    session->verified = false;

    /* Remember certificate hash */
    struct buffer cert_fingerprint = x509_get_sha256_fingerprint(cert, &gc);
    cert_hash_remember(session, cert_depth, &cert_fingerprint);

    if (session->opt->verify_hash_no_ca)
    {
        /*
         * If we decide to verify the peer certificate based on the fingerprint
         * we ignore wrong dates and the certificate not being trusted.
         * Any other problem with the certificate (wrong key, bad cert,...)
         * will still trigger an error.
         * Clearing these flags relies on verify_cert will later rejecting a
         * certificate that has no matching fingerprint.
         */
        uint32_t flags_ignore = MBEDTLS_X509_BADCERT_NOT_TRUSTED | MBEDTLS_X509_BADCERT_EXPIRED
                                | MBEDTLS_X509_BADCERT_FUTURE;
        *flags = *flags & ~flags_ignore;
    }

    /* did peer present cert which was signed by our root cert? */
    if (*flags != 0)
    {
        int ret = 0;
        char errstr[512] = { 0 };
        char *subject = x509_get_subject(cert, &gc);
        char *serial = backend_x509_get_serial(cert, &gc);

        ret = mbedtls_x509_crt_verify_info(errstr, sizeof(errstr) - 1, "", *flags);
        if (ret <= 0
            && !checked_snprintf(errstr, sizeof(errstr), "Could not retrieve error string, flags=%" PRIx32, *flags))
        {
            errstr[0] = '\0';
        }
        else
        {
            chomp(errstr);
        }

        if (subject)
        {
            msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, subject=%s, serial=%s: %s", cert_depth,
                subject, serial ? serial : "<not available>", errstr);
        }
        else
        {
            msg(D_TLS_ERRORS,
                "VERIFY ERROR: depth=%d, (could not extract X509 "
                "subject string from certificate): %s",
                cert_depth, errstr);
        }

        /* Leave flags set to non-zero to indicate that the cert is not ok */
    }
    else if (SUCCESS != verify_cert(session, cert, cert_depth))
    {
        *flags |= MBEDTLS_X509_BADCERT_OTHER;
    }

    gc_free(&gc);

    /*
     * PolarSSL/mbed TLS-1.2.0+ expects 0 on anything except fatal errors.
     */
    return 0;
}

/* not supported for mbedTLS yet */
bool
x509_username_field_ext_supported(const char *fieldname)
{
    return false;
}

result_t
backend_x509_get_username(char *cn, size_t cn_len, char *x509_username_field, mbedtls_x509_crt *cert)
{
    mbedtls_x509_name *name;

    ASSERT(cn != NULL);

    name = &cert->subject;

    /* Find common name */
    while (name != NULL)
    {
        if (0 == memcmp(name->oid.p, MBEDTLS_OID_AT_CN, MBEDTLS_OID_SIZE(MBEDTLS_OID_AT_CN)))
        {
            break;
        }

        name = name->next;
    }

    /* Not found, return an error if this is the peer's certificate */
    if (name == NULL)
    {
        return FAILURE;
    }

    /* Found, extract CN */
    if (cn_len > name->val.len)
    {
        memcpy(cn, name->val.p, name->val.len);
        cn[name->val.len] = '\0';
    }
    else
    {
        memcpy(cn, name->val.p, cn_len);
        cn[cn_len - 1] = '\0';
    }

    return SUCCESS;
}

#if MBEDTLS_VERSION_NUMBER >= 0x04000000
/* Mbed TLS 4 has no function to print the certificate serial number and does
 * not expose the bignum functions anymore. So in order to write the serial
 * number as a decimal string, we implement bignum % 10 and bignum / 10. */
static char
bignum_mod_10(const uint8_t *bignum, size_t bignum_length)
{
    int result = 0;
    for (size_t i = 0; i < bignum_length; i++)
    {
        result = (result * 256) % 10;
        result = (result + bignum[i]) % 10;
    }
    return (char)result;
}

/* Divide bignum by 10 rounded down, in place. */
static void
bignum_div_10(uint8_t *bignum, size_t *bignum_length)
{
    /*
     * Some intuition for the algorithm below:
     *
     * We want to calculate
     *
     *     (bignum[0] * 256^n + bignum[1] * 256^(n-1) + ... + bignum[n]) / 10.
     *
     * Let remainder = bignum[0] % 10 and carry = remainder * 256.
     * Then we can write the above as
     *
     *     (bignum[0] / 10) * 256^n
     *       + ((carry + bignum[1]) * 256^(n-1) + ... + bignum[n]) / 10.
     *
     * So now we have the first byte of our result. The second byte will be
     * (carry + bignum[1]) / 10. Note that this fits into one byte because
     * 0 <= remainder < 10. We calculate the next remainder and carry as
     * remainder = (carry + bignum[1]) % 10 and carry = remainder * 256 and
     * move on to the next byte until we are done.
     */
    size_t new_length = 0;
    int carry = 0;
    for (size_t i = 0; i < *bignum_length; i++)
    {
        uint8_t next_byte = (uint8_t)((bignum[i] + carry) / 10);
        int remainder = (bignum[i] + carry) % 10;
        carry = remainder * 256;

        /* Write the byte unless it's a leading zero. */
        if (new_length != 0 || next_byte != 0)
        {
            bignum[new_length++] = next_byte;
        }
    }
    *bignum_length = new_length;
}

/* Write the decimal representation of bignum to out, if enough space is available.
 * Returns the number of bytes needed in out, or 0 on error. To calculate the
 * necessary buffer size, the function can be called with out = NULL. */
static size_t
write_bignum(char *out, size_t out_size, const uint8_t *bignum, size_t bignum_length)
{
    if (bignum_length == 0)
    {
        /* We want out to be "0". */
        if (out != NULL)
        {
            if (out_size >= 2)
            {
                out[0] = '0';
                out[1] = '\0';
            }
            else if (out_size > 0)
            {
                out[0] = '\0';
            }
        }
        return 2;
    }

    uint8_t *bignum_copy = malloc(bignum_length);
    if (bignum_copy == NULL)
    {
        return 0;
    }
    memcpy(bignum_copy, bignum, bignum_length);

    size_t bytes_needed = 0;
    size_t bytes_written = 0;
    while (bignum_length > 0)
    {
        /* We're writing the digits in reverse order. We put them in the right order later. */
        char digit = bignum_mod_10(bignum_copy, bignum_length);
        if (out != NULL && bytes_written < out_size - 1)
        {
            out[bytes_written++] = '0' + (char)digit;
        }
        bytes_needed += 1;
        bignum_div_10(bignum_copy, &bignum_length);
    }

    if (out != NULL)
    {
        if (bytes_written == bytes_needed)
        {
            /* We had space for all digits. Now reverse them. */
            for (size_t i = 0; i < bytes_written / 2; i++)
            {
                char tmp = out[i];
                out[i] = out[bytes_written - 1 - i];
                out[bytes_written - 1 - i] = tmp;
            }
            out[bytes_written] = '\0';
        }
        else if (out_size > 0)
        {
            out[0] = '\0';
        }
    }
    bytes_needed += 1;

    free(bignum_copy);
    return bytes_needed;
}
#endif /* MBEDTLS_VERSION_NUMBER >= 0x04000000 */

char *
backend_x509_get_serial(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
    char *buf = NULL;
    size_t buflen = 0;

#if MBEDTLS_VERSION_NUMBER < 0x04000000
    mbedtls_mpi serial_mpi = { 0 };

    /* Transform asn1 integer serial into mbed TLS MPI */
    mbedtls_mpi_init(&serial_mpi);
    if (!mbed_ok(mbedtls_mpi_read_binary(&serial_mpi, cert->serial.p, cert->serial.len)))
    {
        msg(M_WARN, "Failed to retrieve serial from certificate.");
        goto end;
    }

    /* Determine decimal representation length, allocate buffer */
    mbedtls_mpi_write_string(&serial_mpi, 10, NULL, 0, &buflen);
    buf = gc_malloc(buflen, true, gc);

    /* Write MPI serial as decimal string into buffer */
    if (!mbed_ok(mbedtls_mpi_write_string(&serial_mpi, 10, buf, buflen, &buflen)))
    {
        msg(M_WARN, "Failed to write serial to string.");
        buf = NULL;
        goto end;
    }

end:
    mbedtls_mpi_free(&serial_mpi);
    return buf;
#else
    buflen = write_bignum(NULL, 0, cert->serial.p, cert->serial.len);
    if (buflen == 0)
    {
        msg(M_WARN, "Failed to write serial to string.");
        return NULL;
    }
    buf = gc_malloc(buflen, true, gc);
    if (write_bignum(buf, buflen, cert->serial.p, cert->serial.len) != buflen)
    {
        msg(M_WARN, "Failed to write serial to string.");
        return NULL;
    }
    return buf;
#endif /* MBEDTLS_VERSION_NUMBER < 0x04000000 */
}

char *
backend_x509_get_serial_hex(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
    char *buf = NULL;
    size_t len = cert->serial.len * 3 + 1;

    buf = gc_malloc(len, true, gc);

    if (mbedtls_x509_serial_gets(buf, len - 1, &cert->serial) < 0)
    {
        buf = NULL;
    }

    return buf;
}

result_t
backend_x509_write_pem(openvpn_x509_cert_t *cert, const char *filename)
{
    /* mbed TLS does not make it easy to write a certificate in PEM format.
     * The only way is to directly access the DER encoded raw certificate
     * and PEM encode it ourselves */

    struct gc_arena gc = gc_new();
    /* just do a very loose upper bound for the base64 based PEM encoding
     * using 3 times the space for the base64 and 100 bytes for the
     * headers and footer */
    struct buffer pem = alloc_buf_gc(cert->raw.len * 3 + 100, &gc);

    struct buffer der = { 0 };
    buf_set_read(&der, cert->raw.p, cert->raw.len);

    if (!crypto_pem_encode("CERTIFICATE", &pem, &der, &gc))
    {
        goto err;
    }

    if (!buffer_write_file(filename, &pem))
    {
        goto err;
    }

    gc_free(&gc);
    return SUCCESS;
err:
    msg(D_TLS_DEBUG_LOW, "Error writing X509 certificate to file %s", filename);
    gc_free(&gc);
    return FAILURE;
}

#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#endif

static struct buffer
x509_get_fingerprint(const mbedtls_md_info_t *md_info, mbedtls_x509_crt *cert, struct gc_arena *gc)
{
    const size_t md_size = mbedtls_md_get_size(md_info);
    struct buffer fingerprint = alloc_buf_gc(md_size, gc);
    mbedtls_md(md_info, cert->raw.p, cert->raw.len, BPTR(&fingerprint));
    ASSERT(buf_inc_len(&fingerprint, md_size));
    return fingerprint;
}

#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif

struct buffer
x509_get_sha1_fingerprint(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
    return x509_get_fingerprint(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), cert, gc);
}

struct buffer
x509_get_sha256_fingerprint(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
    return x509_get_fingerprint(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), cert, gc);
}

char *
x509_get_subject(mbedtls_x509_crt *cert, struct gc_arena *gc)
{
    char tmp_subject[MAX_SUBJECT_LENGTH] = { 0 };
    char *subject = NULL;

    int ret = 0;

    ret = mbedtls_x509_dn_gets(tmp_subject, MAX_SUBJECT_LENGTH - 1, &cert->subject);
    if (ret > 0)
    {
        /* Allocate the required space for the subject */
        subject = string_alloc(tmp_subject, gc);
    }

    return subject;
}

static void
do_setenv_x509(struct env_set *es, const char *name, char *value, int depth)
{
    char *name_expand;
    size_t name_expand_size;

    string_mod(value, CC_ANY, CC_CRLF, '?');
    msg(D_X509_ATTR, "X509 ATTRIBUTE name='%s' value='%s' depth=%d", name, value, depth);
    name_expand_size = 64 + strlen(name);
    name_expand = (char *)malloc(name_expand_size);
    check_malloc_return(name_expand);
    snprintf(name_expand, name_expand_size, "X509_%d_%s", depth, name);
    setenv_str(es, name_expand, value);
    free(name_expand);
}

static char *
asn1_buf_to_c_string(const mbedtls_asn1_buf *orig, struct gc_arena *gc)
{
    size_t i;
    char *val;

    if (!(orig->tag == MBEDTLS_ASN1_UTF8_STRING || orig->tag == MBEDTLS_ASN1_PRINTABLE_STRING
          || orig->tag == MBEDTLS_ASN1_IA5_STRING))
    {
        /* Only support C-string compatible types */
        return string_alloc("ERROR: unsupported ASN.1 string type", gc);
    }

    for (i = 0; i < orig->len; ++i)
    {
        if (orig->p[i] == '\0')
        {
            return string_alloc("ERROR: embedded null value", gc);
        }
    }
    val = gc_malloc(orig->len + 1, false, gc);
    memcpy(val, orig->p, orig->len);
    val[orig->len] = '\0';
    return val;
}

static void
do_setenv_name(struct env_set *es, const struct x509_track *xt, const mbedtls_x509_crt *cert,
               int depth, struct gc_arena *gc)
{
    const mbedtls_x509_name *xn;
    for (xn = &cert->subject; xn != NULL; xn = xn->next)
    {
        const char *xn_short_name = NULL;
        if (0 == mbedtls_oid_get_attr_short_name(&xn->oid, &xn_short_name)
            && 0 == strcmp(xt->name, xn_short_name))
        {
            char *val_str = asn1_buf_to_c_string(&xn->val, gc);
            do_setenv_x509(es, xt->name, val_str, depth);
        }
    }
}

void
x509_track_add(const struct x509_track **ll_head, const char *name, msglvl_t msglevel,
               struct gc_arena *gc)
{
    struct x509_track *xt;
    ALLOC_OBJ_CLEAR_GC(xt, struct x509_track, gc);
    if (*name == '+')
    {
        xt->flags |= XT_FULL_CHAIN;
        ++name;
    }
    xt->name = name;
    xt->next = *ll_head;
    *ll_head = xt;
}

void
x509_setenv_track(const struct x509_track *xt, struct env_set *es, const int depth,
                  mbedtls_x509_crt *cert)
{
    struct gc_arena gc = gc_new();
    while (xt)
    {
        if (depth == 0 || (xt->flags & XT_FULL_CHAIN))
        {
            if (0 == strcmp(xt->name, "SHA1") || 0 == strcmp(xt->name, "SHA256"))
            {
                /* Fingerprint is not part of X509 structure */
                struct buffer cert_hash;
                char *fingerprint;

                if (0 == strcmp(xt->name, "SHA1"))
                {
                    cert_hash = x509_get_sha1_fingerprint(cert, &gc);
                }
                else
                {
                    cert_hash = x509_get_sha256_fingerprint(cert, &gc);
                }

                fingerprint =
                    format_hex_ex(BPTR(&cert_hash), BLEN(&cert_hash), 0, 1 | FHE_CAPS, ":", &gc);
                do_setenv_x509(es, xt->name, fingerprint, depth);
            }
            else
            {
                do_setenv_name(es, xt, cert, depth, &gc);
            }
        }
        xt = xt->next;
    }
    gc_free(&gc);
}

/*
 * Save X509 fields to environment, using the naming convention:
 *
 * X509_{cert_depth}_{name}={value}
 */
void
x509_setenv(struct env_set *es, int cert_depth, mbedtls_x509_crt *cert)
{
    unsigned char c;
    const mbedtls_x509_name *name;
    char s[128] = { 0 };

    name = &cert->subject;

    while (name != NULL)
    {
        char name_expand[64 + 8];
        const char *shortname;

        if (0 == mbedtls_oid_get_attr_short_name(&name->oid, &shortname))
        {
            snprintf(name_expand, sizeof(name_expand), "X509_%d_%s", cert_depth, shortname);
        }
        else
        {
            snprintf(name_expand, sizeof(name_expand), "X509_%d_\?\?", cert_depth);
        }

        size_t i;
        for (i = 0; i < name->val.len; i++)
        {
            if (i >= sizeof(s) - 1)
            {
                break;
            }

            c = name->val.p[i];
            if (c < 32 || c == 127 || (c > 128 && c < 160))
            {
                s[i] = '?';
            }
            else
            {
                s[i] = c;
            }
        }
        s[i] = '\0';

        /* Check both strings, set environment variable */
        string_mod(name_expand, CC_PRINT, CC_CRLF, '_');
        string_mod((char *)s, CC_PRINT, CC_CRLF, '_');
        setenv_str_incr(es, name_expand, (char *)s);

        name = name->next;
    }
}

/* Dummy function because Netscape certificate types are not supported in OpenVPN with mbedtls.
 * Returns SUCCESS if usage is NS_CERT_CHECK_NONE, FAILURE otherwise. */
result_t
x509_verify_ns_cert_type(mbedtls_x509_crt *cert, const int usage)
{
    if (usage == NS_CERT_CHECK_NONE)
    {
        return SUCCESS;
    }

    return FAILURE;
}

result_t
x509_verify_cert_ku(mbedtls_x509_crt *cert, const unsigned int *const expected_ku, size_t expected_len)
{
    msg(D_HANDSHAKE, "Validating certificate key usage");

    if (!mbedtls_x509_crt_has_ext_type(cert, MBEDTLS_X509_EXT_KEY_USAGE))
    {
        msg(D_TLS_ERRORS, "ERROR: Certificate does not have key usage extension");
        return FAILURE;
    }

    if (expected_ku[0] == OPENVPN_KU_REQUIRED)
    {
        /* Extension required, value checked by TLS library */
        return SUCCESS;
    }

    result_t fFound = FAILURE;
    for (size_t i = 0; SUCCESS != fFound && i < expected_len; i++)
    {
        if (expected_ku[i] != 0 && 0 == mbedtls_x509_crt_check_key_usage(cert, expected_ku[i]))
        {
            fFound = SUCCESS;
        }
    }

    if (fFound != SUCCESS)
    {
        msg(D_TLS_ERRORS, "ERROR: Certificate has invalid key usage, expected one of:");
        for (size_t i = 0; i < expected_len && expected_ku[i]; i++)
        {
            msg(D_TLS_ERRORS, " * %04x", expected_ku[i]);
        }
    }

    return fFound;
}

result_t
x509_verify_cert_eku(mbedtls_x509_crt *cert, const char *const expected_oid)
{
    result_t fFound = FAILURE;

    if (!mbedtls_x509_crt_has_ext_type(cert, MBEDTLS_X509_EXT_EXTENDED_KEY_USAGE))
    {
        msg(D_HANDSHAKE, "Certificate does not have extended key usage extension");
    }
    else
    {
        mbedtls_x509_sequence *oid_seq = &(cert->ext_key_usage);

        msg(D_HANDSHAKE, "Validating certificate extended key usage");
        while (oid_seq != NULL)
        {
            mbedtls_x509_buf *oid = &oid_seq->buf;
            char oid_num_str[1024];
            const char *oid_str;

            if (0 == mbedtls_oid_get_extended_key_usage(oid, &oid_str))
            {
                msg(D_HANDSHAKE, "++ Certificate has EKU (str) %s, expects %s", oid_str,
                    expected_oid);
                if (!strcmp(expected_oid, oid_str))
                {
                    fFound = SUCCESS;
                    break;
                }
            }

            if (0 < mbedtls_oid_get_numeric_string(oid_num_str, sizeof(oid_num_str), oid))
            {
                msg(D_HANDSHAKE, "++ Certificate has EKU (oid) %s, expects %s", oid_num_str,
                    expected_oid);
                if (!strcmp(expected_oid, oid_num_str))
                {
                    fFound = SUCCESS;
                    break;
                }
            }
            oid_seq = oid_seq->next;
        }
    }

    return fFound;
}

bool
tls_verify_crl_missing(const struct tls_options *opt)
{
    if (opt->crl_file && !(opt->ssl_flags & SSLF_CRL_VERIFY_DIR)
        && (opt->ssl_ctx->crl == NULL || opt->ssl_ctx->crl->version == 0))
    {
        return true;
    }
    return false;
}

#endif /* #if defined(ENABLE_CRYPTO_MBEDTLS) */
