//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012- OpenVPN Inc.
//
//    SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
//
//

#pragma once

#include <cstring>
#include <string>
#include <vector>

#include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/oid.h>

#include "openvpn/common/hexstr.hpp"

#define MBEDTLS_MAX_SUBJECT_LENGTH 256

namespace openvpn::MbedTLSPKI {

/**
 *  Retrieve the complete X.509 Certificate Subject field
 *
 *  OpenSSL supports two ways of representing the subject line.  The old
 *  format is deprecated, but there might be code expecting this old format.
 *  The old format looks like this:
 *
 *      /C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Server/emailAddress=me@myhost.mydomain
 *
 *  The new format is UTF-8 compliant and has a different formatting scheme:
 *
 *      C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Server,
 *emailAddress=me@myhost.mydomain
 *
 *  This mbed TLS implementation supports generating a subject line formatted
 *  as the deprecated OpenSSL format.  This is the default behaviour, to
 *  preserve OpenSSL compatibility with existing OpenVPN code.
 *
 * @param cert         Pointer to a native mbed TLS X509 object containing the
 *                     certificate
 * @param new_format   (optional, default: false) Which format to use,
 *                     true indicates the new format
 *
 * @return Returns a std::string containing the complete certificate subject.
 *         If it was not possible to retrieve the subject, and empty string
 *         is returned.
 */
static std::string x509_get_subject(const mbedtls_x509_crt *cert,
                                    bool new_format = false)
{
    if (!new_format)
    {
        // Try to return the x509 subject formatted like the OpenSSL
        // X509_NAME_oneline method.  Only attributes matched in the switch
        // statements below will be rendered.  All other attributes will be
        // ignored.

        std::string ret;
        for (const mbedtls_x509_name *name = &cert->subject;
             name != nullptr;
             name = name->next)
        {
            const char *key = nullptr;
            if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid))
                key = "CN";
            else if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_COUNTRY, &name->oid))
                key = "C";
            else if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_LOCALITY, &name->oid))
                key = "L";
            else if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_STATE, &name->oid))
                key = "ST";
            else if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_ORGANIZATION, &name->oid))
                key = "O";
            else if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_ORG_UNIT, &name->oid))
                key = "OU";
            else if (!MBEDTLS_OID_CMP(MBEDTLS_OID_PKCS9_EMAIL, &name->oid))
                key = "emailAddress";

            // make sure that key is defined and value has no embedded nulls
            if (key && !string::embedded_null((const char *)name->val.p, name->val.len))
                ret += "/" + std::string(key)
                       + "=" + std::string((const char *)name->val.p, name->val.len);
        }
        return ret;
    }

    char tmp_subj[MBEDTLS_MAX_SUBJECT_LENGTH] = {0};
    int ret = mbedtls_x509_dn_gets(tmp_subj,
                                   MBEDTLS_MAX_SUBJECT_LENGTH - 1,
                                   &cert->subject);
    return (ret > 0 ? std::string(tmp_subj) : std::string(""));
}

/**
 *  Retrieves just the common name of the X.509 Certificate subject field
 *
 * @param cert     Pointer to a native mbedTLS X509 object containing the
 *                 certificate
 *
 * @return Returns the contents of the extracted field on success.  The
 *         resulting string may be empty if the extraction failed or the field
 *         is empty.
 */
static std::string x509_get_common_name(const mbedtls_x509_crt *cert)
{
    const mbedtls_x509_name *name = &cert->subject;

    // find common name
    while (name != nullptr)
    {
        if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid))
        {
            break;
        }
        name = name->next;
    }

    return (name ? std::string((const char *)name->val.p, name->val.len)
                 : std::string(""));
}

} // namespace openvpn::MbedTLSPKI
