/**
 *      @file    matrixsslKeys.c
 *      @version $Format:%h%d$
 *
 *      The session and authentication management portions of the MatrixSSL library.
 */
/*
 *      Copyright (c) 2013-2018 INSIDE Secure Corporation
 *      Copyright (c) PeerSec Networks, 2002-2011
 *      All Rights Reserved
 *
 *      The latest version of this code is available at http://www.matrixssl.org
 *
 *      This software is open source; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This General Public License does NOT permit incorporating this software
 *      into proprietary programs.  If you are unable to comply with the GPL, a
 *      commercial license for this software may be purchased from INSIDE at
 *      http://www.insidesecure.com/
 *
 *      This program is distributed in 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, write to the Free Software
 *      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *      http://www.gnu.org/copyleft/gpl.html
 */
/******************************************************************************/
#ifndef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 200112L
#endif

#ifndef _DEFAULT_SOURCE
# define _DEFAULT_SOURCE
#endif

#include "osdep_stdio.h"
#include "matrixsslImpl.h"

#if defined(USE_CA_CERTIFICATES) || defined(USE_IDENTITY_CERTIFICATES)

# ifdef USE_ROT_CRYPTO
#  include "../crypto-rot/rotCommon.h"
# endif

# ifdef USE_CERT_PARSE
static
psRes_t handleAuthFailDate(matrixSslLoadKeysOpts_t *opts)
{
#  ifdef POSIX /* TODO - implement date check on WIN32, etc. */
    if (opts && (opts->flags & LOAD_KEYS_OPT_ALLOW_OUT_OF_DATE_CERT_PARSE))
    {
        /* Caller deems this as OK. */
        psTraceInfo("Warning: loaded an out-of-date cert\n");
        return PS_SUCCESS;
    }

    psTraceErrr("Error: Tried to load an out-of-date cert\n");
    return PS_CERT_AUTH_FAIL_EXTENSION;
#  endif /* POSIX */
    return PS_SUCCESS;
}
# endif

static
psRes_t checkAuthFailFlags(psX509Cert_t *leafCert,
        matrixSslLoadKeysOpts_t *opts)
{
# ifndef USE_CERT_PARSE
    return PS_SUCCESS;
# else
    if (leafCert->authFailFlags == 0)
    {
        return PS_SUCCESS; /* All OK. */
    }

    switch (leafCert->authFailFlags)
    {
    case PS_CERT_AUTH_FAIL_DATE_FLAG:
        return handleAuthFailDate(opts);
    default:
        psAssert(PS_FALSE);
        psTraceIntInfo("checkAuthFailFlags: add handling for flag: %u\n",
                leafCert->authFailFlags);
        return PS_FAILURE;
        break;
    }

    return PS_SUCCESS;
#  endif /* USE_CERT_PARSE */
}

/* On client add server cert trust anchors, and on server, add trusted client
   certificate issuers. Inputs: either capath or cacert + cacert_len. */
static psRes_t
matrixSslAddTrustAnchors(sslKeys_t *keys,
        const char *capath,
        const unsigned char *cacert,
        psSizeL_t cacert_len,
        matrixSslLoadKeysOpts_t *opts)
{
    psRes_t err = PS_SUCCESS;

    /* Not necessary to store binary representations of CA certs */
    int32 flags = 0;

    flags |= CERT_STORE_DN_BUFFER;
    flags |= CERT_ALLOW_BUNDLE_PARTIAL_PARSE;

# if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
#  ifdef MATRIX_USE_FILE_SYSTEM
    if (capath)
    {
        err = psX509ParseCertFile(keys->pool, capath, &keys->CAcerts, flags);
    }
    else
#  endif  /* MATRIX_USE_FILE_SYSTEM */
    {
        if (cacert == NULL || cacert_len == 0)
        {
            return PS_SUCCESS;
        }
        err = psX509ParseCertData(keys->pool,
                cacert,
                cacert_len,
                &keys->CAcerts,
                flags);
    }
    if (err == 0)
    {
        psTraceErrr("Failed to load any CA certs.\n");
        return PS_PARSE_FAIL;
    }
    else if (err > 0)
    {
        psTraceIntInfo("Loaded %d CA certs\n", err);
        err = checkAuthFailFlags(keys->CAcerts, opts);
        if (err != PS_SUCCESS)
        {
            psX509FreeCert(keys->CAcerts);
            keys->CAcerts = NULL;
            return err;
        }

        return PS_SUCCESS;
    }
    else
    {
        /* Generic load failure path. Other paths have returned. */
    }

    psTraceStrInfo("Failed to load CA cert file: %s\n", (char *)capath);
# endif
    return err;
}
#else /* CA or IDENTITY CERTIFICATES */
# define matrixSslAddTrustAnchors(k,capath,cacert,cacert_len,opts) (0)
# define checkAuthFailFlags(leafCert,opts) (0)
#endif /* CA or IDENTITY CERTIFICATES */

/* Have seen cases where the PKCS#12 files are not in a child-to-parent order.
   This function is also used in TLS 1.3, where the spec allows chains to be
   sent in arbitrary order. */
void matrixSslReorderCertChain(psX509Cert_t *a_cert)
{
#ifdef USE_CERT_PARSE
    psX509Cert_t *prevCert = NULL;
    psX509Cert_t *nextCert = NULL;
    psX509Cert_t *currCert = a_cert;

    while (currCert)
    {
        nextCert = currCert->next;
        while (nextCert && Memcmp(currCert->issuer.hash, nextCert->subject.hash,
                   SHA1_HASH_SIZE) != 0)
        {
            prevCert = nextCert;
            nextCert = nextCert->next;

            if (nextCert && Memcmp(currCert->issuer.hash,
                    nextCert->subject.hash, SHA1_HASH_SIZE) == 0)
            {
                prevCert->next = nextCert->next;
                nextCert->next = currCert->next;
                currCert->next = nextCert;
                break;
            }
        }
        currCert = currCert->next;
    }
#else
    return;
#endif
}


#if defined(USE_IDENTITY_CERTIFICATES)

/******************************************************************************/
/*
    Validate the cert chain and the private key for the material passed
    to matrixSslReadKeys.  Good to catch any user certifiate errors as
    soon as possible

    When the client private key is stored externally, skip all tests
    involving the private key, since MatrixSSL does not have direct
    access to the key.
 */
static psRes_t verifyReadKeyPair(psPool_t *pool, sslIdentity_t *p, void *poolUserPtr)
{
#  ifndef USE_EXT_CERTIFICATE_VERIFY_SIGNING
    /* Not allowed to have a certificate with no matching private key or
       private key with no cert to match with */
    if (p->cert != NULL && p->privKey.type == 0)
    {
        psTraceErrr("No private key given to matrixSslReadKeys cert\n");
        return PS_CERT_AUTH_FAIL;
    }
    if (p->privKey.type != 0 && p->cert == NULL)
    {
        psTraceErrr("No cert given with private key to matrixSslReadKeys\n");
        return PS_CERT_AUTH_FAIL;
    }
#  endif /* USE_EXT_CERTIFICATE_VERIFY_SIGNING */

   /*
     If this is a chain, we can validate it here with psX509AuthenticateCert
     Don't check the error return code from this call because the chaining
     usage restrictions will test parent-most cert for self-signed.

     But we can look at 'authStatus' on all but the final cert to see if the
     rest looks good
   */
    if (p->cert != NULL && p->cert->next != NULL)
    {
#   ifdef USE_CERT_PARSE
        psX509Cert_t *tmp, *found = NULL;
        (void)psX509AuthenticateCert(pool, p->cert, NULL, &found, NULL, poolUserPtr);
        for (tmp = p->cert; tmp && tmp->next; tmp = tmp->next)
        {
            if (tmp->authStatus != PS_TRUE)
            {
                psTraceErrr("Failed to authenticate cert chain\n");
                return PS_CERT_AUTH_FAIL;
            }
        }
#   endif
#   ifndef USE_EXT_CERTIFICATE_VERIFY_SIGNING
#    if defined(USE_RSA) && defined(USE_CERT_PARSE)
        if (p->privKey.type == PS_RSA)
        {
            if (psRsaCmpPubKey(&p->privKey.key.rsa,
                               &p->cert->publicKey.key.rsa) < 0)
            {
                psTraceErrr("Private key doesn't match cert\n");
                return PS_CERT_AUTH_FAIL;
            }
        }
#    endif /* USE_RSA */
#   endif  /* !USE_EXT_CERTIFICATE_VERIFY_SIGNING */
    }
    return PS_SUCCESS;
}


/* Create Identity */
sslIdentity_t *
matrixSslMakeIdentity(psPool_t *pool, psPubKey_t idkey, psX509Cert_t *cert)
{
    sslIdentity_t *identity;

    if ((identity = psCalloc(pool, 1, sizeof(*identity))) != NULL)
    {
        identity->privKey = idkey;
        identity->cert = cert;
        identity->next = NULL;
    }
    return identity;
}

/* Free Identity */
void
matrixSslFreeIdentity(psPool_t *pool, sslIdentity_t *identity)
{
    if (identity)
    {
        psX509FreeCert(identity->cert);
        psClearPubKey(&identity->privKey);
        psFree(identity, pool);
    }
}

/* Clear Identity Assignment from SSL keys */
void
matrixSslDelIdentity(sslKeys_t *keys, sslIdentity_t *identity)
{
    sslIdentity_t **p;
    for (p = &(keys->identity); *p && *p != identity; p = &((*p)->next))
        ;
    if (*p)
    {
        psAssert(*p == identity);
        *p = (*p)->next;
    }
}

/* Add Identity Assignment to SSL keys - they keys will take ownership of the
   identity, and thus assigned identity needs to be removed from keys before
   being deallocated. Function returns the 'identity' argument, and can't
   fail.  */
sslIdentity_t *
matrixSslAddIdentity(sslKeys_t *keys, sslIdentity_t *identity)
{
# if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
    sslIdentity_t **p;

    /* Push the new key at the end of the list, so that routines using the
       keys can easily prefer the first one added. */
    for (p = &(keys->identity); *p; p = &((*p)->next))
        ;
    *p = identity;

    psTraceInfo("Adding identity with key type: ");
    psTracePrintPubKeyTypeAndSize(NULL, &identity->privKey);

    return identity;
# else
    return NULL;
# endif
}

/* Create Identity from the keypair, and corresponding certificate chain. This
   function will verify that certificate chain is properly ordered, and that
   the subject certificate matches the subject key. */
sslIdentity_t *
matrixSslCreateIdentity(sslKeys_t *keys, psPubKey_t idkey, psX509Cert_t *cert)
{
    sslIdentity_t *identity;

    matrixSslReorderCertChain(cert);
    identity = matrixSslMakeIdentity(keys->pool, idkey, cert);
    if (identity == NULL)
    {
        return NULL;
    }

    /* Validate the public key from the certificate matches the identity key
     * provided. */
    if (verifyReadKeyPair(keys->pool, identity, keys->poolUserPtr) < PS_SUCCESS)
    {
        psTraceInfo("Cert parse success but material didn't validate\n");
        matrixSslFreeIdentity(keys->pool, identity);
        return NULL;
    }
    /* Push the new key at the end of the list, so that routines using the
       keys can easily prefer the first one added. */
    return matrixSslAddIdentity(keys, identity);
}

/* Load keypair from memory buffer to 'keys' */
static psRes_t sslLoadKeyPair(psPool_t *pool,
        psPubKey_t *key,
        int keytype,
        const char *keypass,
        const unsigned char *keydata,
        psSizeL_t keydata_len)
{
    psRes_t err = PS_SUCCESS;
# ifdef USE_PRIVATE_KEY_PARSING
    unsigned char *unarmored;
    psSizeL_t unarmored_len;
    int32_t rc;

    if (keydata == NULL || keydata_len == 0)
    {
        psTraceInfo("sslLoadKeyPair(): no key material");
        key->type = PS_NOKEY;
        key->keysize = 0;
	return PS_SUCCESS;
    }
    rc = psPemTryDecode(pool,
            keydata,
            keydata_len,
            PEM_TYPE_KEY,
            keypass,
            &unarmored,
            &unarmored_len);
    if (rc != PS_SUCCESS)
    {
        /* Not PEM or PEM decoding not supported. Try DER. */
        unarmored = (unsigned char*)keydata;
        unarmored_len = keydata_len;
    }

    psInitPubKey(pool, key, keytype);
    switch (keytype)
    {
#  ifdef USE_RSA
    case PS_RSA:
        err = psRsaParsePkcs1PrivKey(pool,
                unarmored,
                unarmored_len,
                &key->key.rsa);
        if (err < 0)
        {
#   ifdef USE_PKCS8
            /* Attempt a PKCS#8 but mem parse doesn't take password */
            err = psPkcs8ParsePrivBin(pool,
                    unarmored,
                    unarmored_len,
                    NULL,
                    key);
            if (err < 0)
            {
                goto out;
            }
#   else
            goto out;
#   endif
        }
        key->keysize = psRsaSize(&key->key.rsa);
        break;
#  endif /* USE_RSA */

#  ifdef USE_ECC
    case PS_ECC:
        err = psEccParsePrivKey(pool,
                unarmored,
                unarmored_len,
                &key->key.ecc,
                NULL);
        if (err < 0)
        {
#   ifdef USE_PKCS8
            /* Attempt a PKCS#8 but mem parse doesn't take password */
            err = psPkcs8ParsePrivBin(pool,
                    unarmored,
                    unarmored_len,
                    NULL,
                    key);
            if (err < 0)
            {
                goto out;
            }
#   else
            goto out;
#   endif
        }
        key->keysize = psEccSize(&key->key.ecc);
        break;
#  endif /* USE_ECC */

#   ifdef USE_ED25519
    case PS_ED25519:
        err = psEd25519ParsePrivKey(pool,
                unarmored,
                unarmored_len,
                &key->key.ed25519);
        if (err < 0)
        {
            goto out;
        }
        key->keysize = 32;
#   endif /* USE_ED25519 */
    } /* end switch */

  out:
    if (unarmored != keydata)
        psFree(unarmored, pool);
# endif
    return err;
}

/* Load a X509 certificate (chain) from given PEM or binary DER encoded
   data */
static psRes_t sslLoadCert(psPool_t *pool,
        psX509Cert_t **cert,
        const unsigned char *data,
        psSizeL_t data_len,
        matrixSslLoadKeysOpts_t *opts)
{
    psRes_t rc = PS_SUCCESS;

    *cert = NULL;
    if (data == NULL || data_len == 0)
    {
        return PS_SUCCESS;
    }

    rc = psX509ParseCertData(pool,
            data,
            data_len,
            cert,
            (CERT_STORE_UNPARSED_BUFFER|CERT_STORE_DN_BUFFER));
    if (rc < PS_SUCCESS)
    {
        psX509FreeCert((*cert));
        return rc;
    }

    rc = checkAuthFailFlags(*cert, opts);
    if (rc != PS_SUCCESS)
    {
        psX509FreeCert((*cert));
        return rc;
    }

    return rc;
}

psRes_t psRotSetupIdentityKey(matrixSslLoadKeysOpts_t *opts,
        psPubKey_t *idKey)
{
# ifdef USE_ROT_CRYPTO
    int32_t err;
#  ifdef USE_ROT_ECC
    const psEccCurve_t *curve;
#  endif

    switch (opts->key_type)
    {
#  ifdef USE_ROT_ECC
    case PS_ECC:
        idKey->type = PS_ECC;
        idKey->key.ecc.rotKeyType = ps_ecc_key_type_ecdsa;

        idKey->key.ecc.privAsset = opts->privAsset;
        psTraceIntInfo("Using ECDSA private key asset: %u\n",
                idKey->key.ecc.privAsset);
        err = getEccParamById(opts->privAssetCurveId,
                &curve);
        psTraceStrInfo("  (%s curve)\n", curve->name);
        idKey->key.ecc.curve = curve;
        if (err < 0)
        {
            psTraceErrr("getEccParamById failed\n");
            return err;
        }
        idKey->keysize = curve->size * 2;
        err = psRotLoadCurve(curve->curveId,
                &idKey->key.ecc.domainAsset);
        if (err < 0)
        {
            psTraceErrr("psRotLoadCurve failed\n");
            return err;
        }
        /* Do not take ownership of this asset - we are not responsible
           for freeing. */
        idKey->key.ecc.longTermPrivAsset = PS_TRUE;
        break;
#  endif
#  ifdef USE_ROT_RSA
    case PS_RSA:
        idKey->type = PS_RSA;
        idKey->key.rsa.privSigAsset = opts->privAsset;
        psTraceIntInfo("Using RSA private key asset: %u\n",
                idKey->key.rsa.privSigAsset);
        idKey->keysize = opts->privAssetModulusNBytes;
        idKey->key.rsa.size = opts->privAssetModulusNBytes;
        /* Do not take ownership of this asset - we are not responsible
           for freeing. */
        idKey->key.rsa.longTermPrivAsset = PS_TRUE;
        break;
#  endif
    default:
        psTraceErrr("Unknown key type in psRotSetupIdentityKey\n");
        return PS_UNSUPPORTED_FAIL;
    }
    return PS_SUCCESS;

# else
    (void)opts;
    (void)idKey;
    psTraceErrr("USE_ROT_CRYPTO needed for psRotSetupIdentityKey\n");
    return PS_UNSUPPORTED_FAIL;
# endif /* USE_ROT_CRYPTO */
}

psRes_t
matrixSslCreateIdentityFromData(sslKeys_t *keys,
        const unsigned char *cert,
        psSizeL_t cert_len,
        int32 keytype,
        const char *keypass,
        const unsigned char *keydata,
        psSizeL_t keydata_len,
        matrixSslLoadKeysOpts_t *opts)
{
    psPubKey_t idkey;
    psX509Cert_t *idcert = NULL;
    int32 err;
    sslIdentity_t *id;
    psRes_t rc;

    memset(&idkey, 0, sizeof(idkey));

    Memset(&idkey, 0, sizeof idkey); /* Zeroize idkey. */

    err = sslLoadCert(keys->pool, &idcert, cert, cert_len, opts);
    if (err < PS_SUCCESS)
    {
        return err;
    }

    if (opts && opts->privAsset != 0)
    {
        rc = psRotSetupIdentityKey(opts, &idkey);
        if (rc < 0)
        {
            psTraceErrr("psRotLoadIdentity failed\n");
            psX509FreeCert(idcert);
            return rc;
        }
    }
    else
    {
        err = sslLoadKeyPair(keys->pool,
                &idkey,
                keytype,
                keypass,
                keydata,
                keydata_len);
        if (err < PS_SUCCESS)
        {
            psX509FreeCert(idcert);
            return err;
        }
    }

    id = matrixSslCreateIdentity(keys, idkey, idcert);
    if (id == NULL)
    {
        return PS_FAILURE;
    }

    return PS_SUCCESS;
}

static psRes_t matrixSslLoadKeyMaterialMem(sslKeys_t *keys,
        const unsigned char *certBuf,
        int32 certLen,
        const unsigned char *privBuf,
        int32 privLen,
        const unsigned char *CAbuf,
        int32 CAlen,
        int32 privKeyType,
        matrixSslLoadKeysOpts_t *opts)
{
    psRes_t err;

    if (keys == NULL
        || (certBuf == NULL && privBuf == NULL && CAbuf == NULL))
    {
        return PS_ARG_FAIL;
    }

# ifdef USE_ROT_CRYPTO
    if (privKeyType != PS_ECC && privKeyType != PS_RSA)
    {
        psTraceErrr("Only ECDSA/RSA auth keys supported by crypto-rot\n");
        return PS_ARG_FAIL;
    }
    if (opts && opts->privAsset != VAL_ASSETID_INVALID)
    {
        /* The asset ID overrides the plaintext key. */
        privBuf = NULL;
        privLen = 0;
    }
# endif /* USE_ROT_CRYPTO */

    err = matrixSslAddTrustAnchors(keys, NULL, CAbuf, CAlen, opts);
    if (err < PS_SUCCESS)
    {
        return err;
    }

    if ((privBuf != NULL && certBuf != NULL) ||
            (opts != NULL && opts->privAsset != 0))
    {
        err = matrixSslCreateIdentityFromData(keys,
                certBuf,
                certLen,
                privKeyType,
                NULL,
                privBuf,
                privLen,
                opts);
        if (err < PS_SUCCESS)
        {
# if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
            psX509FreeCert(keys->CAcerts);
            keys->CAcerts = NULL;
# endif
            return PS_CERT_AUTH_FAIL;
        }
    }

    return PS_SUCCESS;
}

int32_t matrixSslLoadKeysMem(sslKeys_t *keys,
        const unsigned char *certBuf,
        int32 certLen,
        const unsigned char *privBuf,
        int32 privLen,
        const unsigned char *CAbuf,
        int32 CAlen,
        matrixSslLoadKeysOpts_t *opts)
{
    int32_t keytype = 0;
    int32_t rc = PS_FAILURE;

    if (opts)
    {
        keytype = opts->key_type;
    }

    if (privBuf == NULL)
    {
        keytype = 1;
    }

    /* Note: previous versions used the constants 1 for RSA,
       2 for ECC and 3 for EDDSA. */
    switch (keytype)
    {
    case PS_RSA:
        rc = matrixSslLoadKeyMaterialMem(keys,
                certBuf,
                certLen,
                privBuf,
                privLen,
                CAbuf,
                CAlen,
                PS_RSA,
                opts);
        break;
    case PS_ECC:
        rc = matrixSslLoadKeyMaterialMem(keys,
                certBuf,
                certLen,
                privBuf,
                privLen,
                CAbuf,
                CAlen,
                PS_ECC,
                opts);
        break;
    case PS_ED25519:
        rc = matrixSslLoadKeyMaterialMem(keys,
                certBuf,
                certLen,
                privBuf,
                privLen,
                CAbuf,
                CAlen,
                PS_ED25519,
                opts);
        break;
    case 0:
        {
            int32 try[] = { PS_RSA, PS_ECC, PS_ED25519, -1}, i;
            for (i = 0; try[i] != -1; i++)
            {
                rc = matrixSslLoadKeyMaterialMem(
                        keys,
                        certBuf, certLen, privBuf, privLen,
                        NULL, 0,
                        try[i], opts);
                if (rc == PS_SUCCESS)
                {
                    break;
                }
            }
            if (CAbuf && CAlen > 0)
            {
                rc = matrixSslLoadKeyMaterialMem(
                        keys, NULL, 0, NULL, 0,
                        CAbuf, CAlen, 0, opts);
            }
        }
        break;
    default:
        /* Unknown key type */
        rc = PS_FAILURE;
    }
    return rc;
}

#ifdef USE_RSA
int32 matrixSslLoadRsaKeysMemExt(sslKeys_t *keys,
        const unsigned char *certBuf,
        int32 certLen,
        const unsigned char *privBuf,
        int32 privLen,
        const unsigned char *CAbuf,
        int32 CAlen,
        matrixSslLoadKeysOpts_t *opts)
{
    return matrixSslLoadKeyMaterialMem(keys,
            certBuf,
            certLen,
            privBuf,
            privLen,
            CAbuf,
            CAlen,
            PS_RSA,
            opts);
}

int32 matrixSslLoadRsaKeysMem(sslKeys_t *keys,
        const unsigned char *certBuf,
        int32 certLen,
        const unsigned char *privBuf,
        int32 privLen,
        const unsigned char *CAbuf,
        int32 CAlen)
{
    return matrixSslLoadKeyMaterialMem(keys,
            certBuf,
            certLen,
            privBuf,
            privLen,
            CAbuf,
            CAlen,
            PS_RSA,
            NULL);
}
#endif /* USE_RSA */

#ifdef USE_ECC
int32 matrixSslLoadEcKeysMemExt(sslKeys_t *keys,
        const unsigned char *certBuf,
        int32 certLen,
        const unsigned char *privBuf,
        int32 privLen,
        const unsigned char *CAbuf,
        int32 CAlen,
        matrixSslLoadKeysOpts_t *opts)
{
    return matrixSslLoadKeyMaterialMem(keys,
            certBuf,
            certLen,
            privBuf,
            privLen,
            CAbuf,
            CAlen,
            PS_ECC,
            opts);
}

int32 matrixSslLoadEcKeysMem(sslKeys_t *keys,
        const unsigned char *certBuf,
        int32 certLen,
        const unsigned char *privBuf,
        int32 privLen,
        const unsigned char *CAbuf,
        int32 CAlen)
{
    return matrixSslLoadKeyMaterialMem(keys,
            certBuf,
            certLen,
            privBuf,
            privLen,
            CAbuf,
            CAlen,
            PS_ECC,
            NULL);
}
#endif /* USE_ECC */

#ifdef USE_PKCS12

int32 matrixSslLoadPkcs12Mem(sslKeys_t *keys,
        const unsigned char *p12Buf,
        int32 p12Len,
        const unsigned char *importPass,
        int32 ipasslen,
        const unsigned char *macPass,
        int32 mpasslen,
        int32 flags)
{
    unsigned char *mPass;
    psPool_t *pool;
    int32 rc;
    psX509Cert_t *cert;
    psPubKey_t idkey;
    sslIdentity_t *id;

    Memset(&idkey, 0, sizeof idkey); /* Zeroize idkey. */

    if (keys == NULL)
    {
        return PS_ARG_FAIL;
    }
    pool = keys->pool;
    PS_POOL_USED(pool);

    Memset(&idkey, 0, sizeof(idkey));

    if (macPass == NULL)
    {
        mPass = (unsigned char *) importPass;
        mpasslen = ipasslen;
    }
    else
    {
        mPass = (unsigned char *) macPass;
    }
    rc = psPkcs12ParseMem(pool,
            &cert,
            &idkey,
            p12Buf,
            p12Len,
            flags,
            (unsigned char *) importPass,
            ipasslen,
            mPass,
            mpasslen);
    if (rc < 0)
    {
        psX509FreeCert(cert);
        psClearPubKey(&idkey);
        return rc;
    }

    id = matrixSslCreateIdentity(keys, idkey, cert);
    if (id == NULL)
    {
        return PS_FAILURE;
    }

    return PS_SUCCESS;
}
# endif /* USE_PKCS12 */

#endif /* USE_IDENTITY_CERTIFICATES */


#ifdef REQUIRE_DH_PARAMS
int32 matrixSslLoadDhParamsMem(sslKeys_t *keys,
        const unsigned char *dhBin,
        int32 dhBinLen)
{
    if (keys == NULL)
    {
        return PS_ARG_FAIL;
    }
    return psPkcs3ParseDhParamBin(keys->pool,
            (unsigned char *) dhBin,
            dhBinLen,
            &keys->dhParams);
}
#endif /* REQUIRE_DH_PARAMS */


#ifdef USE_ECC
/* User is specifying EC curves that are supported so check that against the
    keys they are supporting */

static struct {
    int curveId;
    int flag;
} matrixCurveIdFlag[] = {
    { 19, IS_SECP192R1 },
    { 21, IS_SECP224R1 },
    { 23, IS_SECP256R1 },
    { 24, IS_SECP384R1 },
    { 25, IS_SECP521R1 },
    { 26, IS_BRAIN256R1 },
    { 27, IS_BRAIN384R1 },
    { 28, IS_BRAIN512R1 },
    { 255, IS_BRAIN224R1 },
    { 0, 0 }
};

int32 curveIdToFlag(int32 id)
 {
   int i;
   for (i = 0; i < sizeof(matrixCurveIdFlag)/ sizeof(matrixCurveIdFlag[0]); i++)
   {
       if (matrixCurveIdFlag[i].curveId == id)
           return matrixCurveIdFlag[i].flag;
   }
   return 0;
 }

psRes_t psTestUserEcID(int32 id, int32 ecFlags)
{
   int i;
   for (i = 0; i < sizeof(matrixCurveIdFlag)/ sizeof(matrixCurveIdFlag[0]); i++)
   {
       if (matrixCurveIdFlag[i].curveId == id)
       {
           if (!(ecFlags & matrixCurveIdFlag[i].flag))
               return PS_FAILURE;
           else
               return PS_SUCCESS;
       }
   }
   return PS_UNSUPPORTED_FAIL;
}

int32 psTestUserEc(int32 ecFlags, const sslKeys_t *keys)
{
    const psEccKey_t *eccKey;
    int goodEccCount = 0;
    int otherKeyCount = 0;
    psRes_t res;
# ifdef USE_CERT_PARSE
    psX509Cert_t *cert;
# endif /* USE_CERT_PARSE */

# ifdef USE_IDENTITY_CERTIFICATES
    sslIdentity_t *p;

    for (p = keys->identity; p; p = p->next)
    {
        if (p->privKey.type != PS_ECC)
        {
            otherKeyCount++;
            continue;
        }
        eccKey = &p->privKey.key.ecc;
        res = psTestUserEcID(eccKey->curve->curveId, ecFlags);
        if (res < 0)
        {
            continue;
        }
#  ifdef USE_CERT_PARSE
        for (cert = p->cert; cert; cert = cert->next)
        {
            if (cert->publicKey.type == PS_ECC)
            {
                eccKey = &cert->publicKey.key.ecc;
                res = psTestUserEcID(eccKey->curve->curveId, ecFlags);
                if (res < 0)
                {
                    break;
                }
            }
        }
        if (p->cert && !cert)
        {
            /* had certs, and did not break out from the loop above -
               something fruitful */
            goodEccCount++;
        }
#  else /* USE_CERT_PARSE */
        goodEccCount++;
#  endif /* USE_CERT_PARSE */
    }
# endif  /* USE_IDENTITY_CERTIFICATES */

# if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
#   ifdef USE_CERT_PARSE
    for (cert = keys->CAcerts; cert != NULL; cert = cert->next)
    {
        if (cert->publicKey.type == PS_ECC)
        {
            eccKey = &cert->publicKey.key.ecc;
            res = PS_FAILURE;
            if (eccKey->curve)
            {
                res = psTestUserEcID(eccKey->curve->curveId, ecFlags);
            }
            if (res == PS_SUCCESS)
            {
                goodEccCount++;
            }
        }
    }
#   endif /* USE_CERT_PARSE */
# endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
    return goodEccCount > 0 || otherKeyCount > 0;
}

/**
    Generate and cache an ephemeral ECC key for later use in ECDHE key exchange.
    @param[out] keys Keys structure to hold ephemeral keys
    @param[in] curve ECC curve to generate key on, or NULL to generate for all
        supported curves.
    @param[in] hwCtx Context for hardware crypto.
 */
int32_t matrixSslGenEphemeralEcKey(sslKeys_t *keys,
        psEccKey_t *ecc,
        const psEccCurve_t *curve,
        void *hwCtx)
{
#  if ECC_EPHEMERAL_CACHE_USAGE > 0
    psTime_t t;
#  endif
    int32_t rc = PS_FAILURE;

    if (keys == NULL  || curve == NULL)
    {
        return PS_ARG_FAIL;
    }

#  if ECC_EPHEMERAL_CACHE_USAGE > 0
    psGetTime(&t, keys->poolUserPtr);
    psLockMutex(&keys->cache.lock);
    if (keys->cache.eccPrivKey.curve != curve)
    {
        psTraceStrInfo("Generating ephemeral %s key (new curve)\n",
            curve->name);
        goto L_REGEN;
    }
    if (keys->cache.eccPrivKeyUse > ECC_EPHEMERAL_CACHE_USAGE)
    {
        psTraceStrInfo("Generating ephemeral %s key (usage exceeded)\n",
            curve->name);
        goto L_REGEN;
    }
    if (psDiffMsecs(keys->cache.eccPrivKeyTime, t, keys->poolUserPtr) >
        (1000 * ECC_EPHEMERAL_CACHE_SECONDS))
    {
        psTraceStrInfo("Generating ephemeral %s key (time exceeded)\n",
            curve->name);
        goto L_REGEN;
    }
    keys->cache.eccPrivKeyUse++;
    rc = PS_SUCCESS;
    if (ecc)
    {
        rc = psEccCopyKey(ecc, &keys->cache.eccPrivKey);
    }
    psUnlockMutex(&keys->cache.lock);
    return rc;

L_REGEN:
    if (keys->cache.eccPrivKeyUse)
    {
        /* We use eccPrivKeyUse == 0 as a flag to note the key not allocated */
        psEccClearKey(&keys->cache.eccPrivKey);
        keys->cache.eccPrivKeyUse = 0;
    }
#   ifdef USE_ROT_ECC
    keys->cache.eccPrivKey.rotKeyType = ps_ecc_key_type_ecdhe;
#   endif
    rc = psEccGenKey(keys->pool, &keys->cache.eccPrivKey, curve, hwCtx);
    if (rc < 0)
    {
        psUnlockMutex(&keys->cache.lock);
        return rc;
    }
    keys->cache.eccPrivKeyTime = t;
    keys->cache.eccPrivKeyUse = 1;
    rc = PS_SUCCESS;
    if (ecc)
    {
        rc = psEccCopyKey(ecc, &keys->cache.eccPrivKey);
    }
    psUnlockMutex(&keys->cache.lock);
    return rc;
#  else
    /* Not using ephemeral caching. */
    if (ecc)
    {
#   ifdef USE_ROT_ECC
        ecc->rotKeyType = ps_ecc_key_type_ecdhe;
#   endif
        psTraceStrInfo("Generating ephemeral %s key (new curve)\n",
                curve->name);
        rc = psEccGenKey(keys->pool, ecc, curve, hwCtx);
        return rc;
    }
    rc = PS_SUCCESS;
    return rc;
#  endif /* ECC_EPHEMERAL_CACHE_USAGE > 0 */
}
#endif /* USE_ECC */

#ifdef MATRIX_USE_FILE_SYSTEM
# ifdef USE_PKCS12
/******************************************************************************/
/*
    File should be a binary .p12 or .pfx
 */
int32 matrixSslLoadPkcs12(sslKeys_t *keys,
        const unsigned char *certFile,
        const unsigned char *importPass,
        int32 ipasslen,
        const unsigned char *macPass,
        int32 mpasslen,
        int32 flags)
{
    unsigned char *mPass;
    psPool_t *pool;
    int32 rc;
    psX509Cert_t *cert;
    psPubKey_t idkey;
    sslIdentity_t *id;

    if (keys == NULL)
    {
        return PS_ARG_FAIL;
    }
    pool = keys->pool;
    PS_POOL_USED(pool);

    Memset(&idkey, 0, sizeof(idkey));

    if (macPass == NULL)
    {
        mPass = (unsigned char *) importPass;
        mpasslen = ipasslen;
    }
    else
    {
        mPass = (unsigned char *) macPass;
    }

    rc = psPkcs12Parse(pool,
            &cert,
            &idkey,
            certFile,
            flags,
            (unsigned char *) importPass,
            ipasslen,
            mPass,
            mpasslen);
    if (rc < 0)
    {
        if (cert)
        {
            psX509FreeCert(cert);
        }
        psClearPubKey(&idkey);
        return rc;
    }

    id = matrixSslCreateIdentity(keys, idkey, cert);
    if (id == NULL)
    {
        return PS_FAILURE;
    }

    return PS_SUCCESS;
}
/******************************************************************************/
# endif /* USE_PKCS12 */

# if defined(USE_RSA) || defined(USE_ECC)

static psRes_t matrixSslLoadKeyMaterial(sslKeys_t *keys,
        const char *certFile,
        const char *privFile,
        const char *privPass,
        const char *CAfile,
        int32 privKeyType,
        matrixSslLoadKeysOpts_t *opts)
{
    int32 err = PS_UNSUPPORTED_FAIL;

    if (keys == NULL)
    {
        return PS_ARG_FAIL;
    }

    err = matrixSslAddTrustAnchors(keys, CAfile, NULL, 0, opts);
    if (err < PS_SUCCESS)
    {
        return err;
    }
#  if defined(USE_IDENTITY_CERTIFICATES)
    if (privFile && certFile)
    {
        unsigned char *idkey, *cert;
        psSizeL_t idkey_len, cert_len;
        psPool_t *pool = keys->pool;

        err = psGetFileBuf(pool, privFile, &idkey, &idkey_len);
        if (err == PS_SUCCESS)
        {
            err = psGetFileBuf(pool, certFile, &cert, &cert_len);
            if (err == PS_SUCCESS)
            {
                err = matrixSslCreateIdentityFromData(keys,
                        cert,
                        cert_len,
                        privKeyType,
                        privPass,
                        idkey,
                        idkey_len,
                        opts);
                psFree(cert, pool);
            }
            psFree(idkey, pool);
        }
    }
#  endif /* USE_IDENTITY_CERTIFICATES */
    return err;
}
#endif /* USE_RSA || USE_ECC */

# ifdef USE_RSA
/******************************************************************************/
PSPUBLIC int32  matrixSslLoadRsaKeysExt(sslKeys_t *keys,
        const char *certFile,
        const char *privFile,
        const char *privPass,
        const char *trustedCAFile,
        matrixSslLoadKeysOpts_t *opts)
{
    return matrixSslLoadKeyMaterial(keys,
            certFile,
            privFile,
            privPass,
            trustedCAFile,
            PS_RSA,
            opts);
}

int32 matrixSslLoadRsaKeys(sslKeys_t *keys,
        const char *certFile,
        const char *privFile,
        const char *privPass,
        const char *CAfile)
{
    return matrixSslLoadKeyMaterial(keys,
            certFile,
            privFile,
            privPass,
            CAfile,
            PS_RSA,
            NULL);
}
# endif /* USE_RSA */

# ifdef USE_ECC
/******************************************************************************/
int32 matrixSslLoadEcKeysExt(sslKeys_t *keys,
        const char *certFile,
        const char *privFile,
        const char *privPass,
        const char *CAfile,
        matrixSslLoadKeysOpts_t *opts)
{
    return matrixSslLoadKeyMaterial(keys,
            certFile,
            privFile,
            privPass,
            CAfile,
            PS_ECC,
            opts);
}

int32 matrixSslLoadEcKeys(sslKeys_t *keys,
        const char *certFile,
        const char *privFile,
        const char *privPass,
        const char *CAfile)
{
    return matrixSslLoadKeyMaterial(keys,
            certFile,
            privFile,
            privPass,
            CAfile,
            PS_ECC,
            NULL);
}
# endif /* USE_ECC */

# if defined(USE_IDENTITY_CERTIFICATES)
psRes_t matrixSslLoadKeys(sslKeys_t *keys,
        const char *certFile,
        const char *privFile,
        const char *privPass,
        const char *CAfile,
        matrixSslLoadKeysOpts_t *opts)
{
    int32_t keytype = 0;
    int32_t rc = PS_FAILURE;

    if (opts)
    {
        keytype = opts->key_type;
    }

# ifdef USE_ALWAYS_ALLOW_OUT_OF_DATE_CERT_PARSE
    opts.flags |= LOAD_KEYS_OPT_ALLOW_OUT_OF_DATE_CERT_PARSE;
# endif

    if (privFile == NULL)
    {
        keytype = 1;
    }

    switch (keytype)
    {
    case PS_RSA:
        rc = matrixSslLoadKeyMaterial(keys,
                certFile,
                privFile,
                privPass,
                CAfile,
                PS_RSA,
                opts);
        break;
    case PS_ECC:
        rc = matrixSslLoadKeyMaterial(keys,
                certFile,
                privFile,
                privPass,
                CAfile,
                PS_ECC,
                opts);
        break;
    case PS_ED25519:
        rc = matrixSslLoadKeyMaterial(keys,
                certFile,
                privFile,
                privPass,
                CAfile,
                PS_ED25519,
                opts);
        break;
    case 0:
        {
            int32 try[] = { PS_RSA, PS_ECC, PS_ED25519, -1}, i;
            for (i = 0; try[i] != -1; i++)
            {
                rc = matrixSslLoadKeyMaterial(
                        keys,
                        certFile, privFile, privPass,
                        NULL, try[i], opts);
                if (rc == PS_SUCCESS)
                {
                    break;
                }
            }
            if (CAfile)
            {
                rc = matrixSslLoadKeyMaterial(
                        keys, NULL, NULL, NULL, CAfile, 0, opts);
            }
        }
        break;

    default:
        rc = PS_FAILURE;
        break;
    }

    return rc;
}
# endif

# ifdef REQUIRE_DH_PARAMS
/******************************************************************************/
/*
    User level API to assign the DH parameter file to the server application.
 */
int32 matrixSslLoadDhParams(sslKeys_t *keys, const char *paramFile)
{
    if (keys == NULL)
    {
        return PS_ARG_FAIL;
    }
    return psPkcs3ParseDhParamFile(keys->pool, (char *) paramFile, &keys->dhParams);
}
# endif /* REQUIRE_DH_PARAMS */

#endif /* MATRIX_USE_FILE_SYSTEM */

#if defined(USE_OCSP_RESPONSE) && defined(USE_SERVER_SIDE_SSL)
int32_t matrixSslLoadOCSPResponse(sslKeys_t *keys,
    const unsigned char *OCSPResponseBuf, psSize_t OCSPResponseBufLen)
{
    psPool_t *pool;

    if (keys == NULL || OCSPResponseBuf == NULL || OCSPResponseBufLen == 0)
    {
        return PS_ARG_FAIL;
    }
    pool = keys->pool;
    PS_POOL_USED(pool);

    /* Overwrite/Update any response being set */
    if (keys->OCSPResponseBuf != NULL)
    {
        psFree(keys->OCSPResponseBuf, pool);
        keys->OCSPResponseBufLen = 0;
    }

    keys->OCSPResponseBufLen = OCSPResponseBufLen;
    if ((keys->OCSPResponseBuf = psMalloc(pool, OCSPResponseBufLen)) == NULL)
    {
        return PS_MEM_FAIL;
    }

    Memcpy(keys->OCSPResponseBuf, OCSPResponseBuf, OCSPResponseBufLen);
    return PS_SUCCESS;
}
#endif /* USE_OCSP_RESPONSE && USE_SERVER_SIDE_SSL */

/******************************************************************************/


/******************************************************************************/
/*
    Must call to allocate the key structure now.  After which, LoadRsaKeys,
    LoadDhParams and/or LoadPskKey can be called

    Memory info:
    Caller must free keys with matrixSslDeleteKeys on function success
    Caller does not need to free keys on function failure
 */
int32_t matrixSslNewKeys(sslKeys_t **keys, void *memAllocUserPtr)
{
    psPool_t *pool = NULL;
    sslKeys_t *lkeys;

#if  defined(USE_ECC) || defined(REQUIRE_DH_PARAMS)
    int32_t rc;
#endif

    lkeys = psMalloc(pool, sizeof(sslKeys_t));
    if (lkeys == NULL)
    {
        return PS_MEM_FAIL;
    }
    Memset(lkeys, 0x0, sizeof(sslKeys_t));
    lkeys->pool = pool;
    lkeys->poolUserPtr = memAllocUserPtr;

#if  defined(USE_ECC) || defined(REQUIRE_DH_PARAMS)
    rc = psCreateMutex(&lkeys->cache.lock, 0);
    if (rc < 0)
    {
        psFree(lkeys, pool);
        return rc;
    }
#endif
    *keys = lkeys;
    return PS_SUCCESS;
}

/******************************************************************************/
/*
    This will free the struct and any key material that was loaded via:
        matrixSslLoadRsaKeys
        matrixSslLoadEcKeys
        matrixSslLoadDhParams
        matrixSslLoadPsk
        matrixSslLoadOCSPResponse
 */
void matrixSslDeleteKeys(sslKeys_t *keys)
{
#ifdef USE_PSK_CIPHER_SUITE
    psPsk_t *psk, *next;
#endif /* USE_PSK_CIPHER_SUITE */
#if defined(USE_STATELESS_SESSION_TICKETS) && defined(USE_SERVER_SIDE_SSL)
    psSessionTicketKeys_t *tick, *nextTick;
#endif

    if (keys == NULL)
    {
        return;
    }
# ifdef USE_IDENTITY_CERTIFICATES
    {
        sslIdentity_t *p, *next;
        for (p = keys->identity; p != NULL; p = next)
        {
            next = p->next;
            psX509FreeCert(p->cert);
            psClearPubKey(&p->privKey);
            psFree(p, keys->pool);
        }
    }
# endif /* USE_IDENTITY_CERTIFICATES */

# ifndef USE_ONLY_PSK_CIPHER_SUITE
# if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
    if (keys->CAcerts)
    {
        psX509FreeCert(keys->CAcerts);
    }
# endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
#endif  /* !USE_ONLY_PSK_CIPHER_SUITE */

#ifdef REQUIRE_DH_PARAMS
    psPkcs3ClearDhParams(&keys->dhParams);
#endif /* REQUIRE_DH_PARAMS */

#ifdef USE_PSK_CIPHER_SUITE
    if (keys->pskKeys)
    {
        psk = keys->pskKeys;
        while (psk)
        {
            psFree(psk->pskKey, keys->pool);
            psFree(psk->pskId, keys->pool);
            next = psk->next;
            psFree(psk, keys->pool);
            psk = next;
        }
    }
#endif /* USE_PSK_CIPHER_SUITE */
#ifdef USE_TLS_1_3
    tls13FreePsk(keys->tls13PskKeys, keys->pool);
#endif /* USE_TLS_1_3 */

#if defined(USE_STATELESS_SESSION_TICKETS) && defined(USE_SERVER_SIDE_SSL)
    if (keys->sessTickets)
    {
        tick = keys->sessTickets;
        while (tick)
        {
            nextTick = tick->next;
            psFree(tick, keys->pool);
            tick = nextTick;
        }
    }
#endif

#if defined(USE_ECC) || defined(REQUIRE_DH_PARAMS)
    psDestroyMutex(&keys->cache.lock);
# ifdef USE_ECC
    if (keys->cache.eccPrivKeyUse > 0)
    {
        psEccClearKey(&keys->cache.eccPrivKey);
        psEccClearKey(&keys->cache.eccPubKey);
    }
# endif
    /* Remainder of structure is cleared below */
#endif

#if defined(USE_OCSP_RESPONSE) && defined(USE_SERVER_SIDE_SSL)
    if (keys->OCSPResponseBuf != NULL)
    {
        psFree(keys->OCSPResponseBuf, keys->pool);
        keys->OCSPResponseBufLen = 0;
    }
#endif

    memzero_s(keys, sizeof(sslKeys_t));
    psFree(keys, NULL);
}
