/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file contains prototypes for the public SSL functions.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nss.h"
#include "pk11func.h"
#include "ssl.h"
#include "sslimpl.h"

struct {
    sslEphemeralKeyPair *keyPair;
    PRCallOnceType once;
} gECDHEKeyPairs[SSL_NAMED_GROUP_COUNT];

typedef struct sslSocketAndGroupArgStr {
    const sslNamedGroupDef *group;
    const sslSocket *ss;
} sslSocketAndGroupArg;

/* Function to clear out the ECDHE keys. */
static SECStatus
ssl_CleanupECDHEKeys(void *appData, void *nssData)
{
    unsigned int i;

    for (i = 0; i < SSL_NAMED_GROUP_COUNT; i++) {
        if (gECDHEKeyPairs[i].keyPair) {
            ssl_FreeEphemeralKeyPair(gECDHEKeyPairs[i].keyPair);
        }
    }
    memset(gECDHEKeyPairs, 0, sizeof(gECDHEKeyPairs));
    return SECSuccess;
}

/* Only run the cleanup once. */
static PRCallOnceType cleanupECDHEKeysOnce;
static PRStatus
ssl_SetupCleanupECDHEKeysOnce(void)
{
    SECStatus rv = NSS_RegisterShutdown(ssl_CleanupECDHEKeys, NULL);
    return (rv != SECSuccess) ? PR_FAILURE : PR_SUCCESS;
}

/* This creates a key pair for each of the supported EC groups.  If that works,
 * we assume that the token supports that group.  Since this is relatively
 * expensive, this is only done for the first socket that is used.  That means
 * that if tokens are added or removed, then this will not pick up any changes.
 */
static PRStatus
ssl_CreateStaticECDHEKeyPair(void *arg)
{
    const sslSocketAndGroupArg *typed_arg = (sslSocketAndGroupArg *)arg;
    const sslNamedGroupDef *group = typed_arg->group;
    const sslSocket *ss = typed_arg->ss;
    unsigned int i = group - ssl_named_groups;
    SECStatus rv;

    PORT_Assert(group->keaType == ssl_kea_ecdh);
    PORT_Assert(i < SSL_NAMED_GROUP_COUNT);
    rv = ssl_CreateECDHEphemeralKeyPair(ss, group,
                                        &gECDHEKeyPairs[i].keyPair);
    if (rv != SECSuccess) {
        gECDHEKeyPairs[i].keyPair = NULL;
        SSL_TRC(5, ("%d: SSL[-]: disabling group %d",
                    SSL_GETPID(), group->name));
    }

    return PR_SUCCESS;
}

void
ssl_FilterSupportedGroups(sslSocket *ss)
{
    unsigned int i;
    PRStatus prv;
    sslSocketAndGroupArg arg = { NULL, ss };

    prv = PR_CallOnce(&cleanupECDHEKeysOnce, ssl_SetupCleanupECDHEKeysOnce);
    PORT_Assert(prv == PR_SUCCESS);
    if (prv != PR_SUCCESS) {
        return;
    }

    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
        PRUint32 policy;
        SECStatus srv;
        unsigned int index;
        const sslNamedGroupDef *group = ss->namedGroupPreferences[i];
        if (!group) {
            continue;
        }

        srv = NSS_GetAlgorithmPolicy(group->oidTag, &policy);
        if (srv == SECSuccess && !(policy & NSS_USE_ALG_IN_SSL_KX)) {
            ss->namedGroupPreferences[i] = NULL;
            continue;
        }

        if (group->assumeSupported) {
            continue;
        }

        if (group->name == ssl_grp_kem_xyber768d00) {
#ifdef NSS_DISABLE_KYBER
            ss->namedGroupPreferences[i] = NULL;
#endif
            continue;
        }

        /* For EC groups, we have to test that a key pair can be created. This
         * is gross, and expensive, so only do it once. */
        index = group - ssl_named_groups;
        PORT_Assert(index < SSL_NAMED_GROUP_COUNT);

        arg.group = group;
        prv = PR_CallOnceWithArg(&gECDHEKeyPairs[index].once,
                                 ssl_CreateStaticECDHEKeyPair,
                                 (void *)&arg);
        PORT_Assert(prv == PR_SUCCESS);
        if (prv != PR_SUCCESS) {
            continue;
        }

        if (!gECDHEKeyPairs[index].keyPair) {
            ss->namedGroupPreferences[i] = NULL;
        }
    }
}

/*
 * Creates the static "ephemeral" public and private ECDH keys used by server in
 * ECDHE_RSA and ECDHE_ECDSA handshakes when we reuse the same key.
 */
SECStatus
ssl_CreateStaticECDHEKey(sslSocket *ss, const sslNamedGroupDef *ecGroup)
{
    sslEphemeralKeyPair *keyPair;
    /* We index gECDHEKeyPairs by the named group.  Pointer arithmetic! */
    unsigned int index = ecGroup - ssl_named_groups;
    PRStatus prv;
    sslSocketAndGroupArg arg = { ecGroup, ss };

    prv = PR_CallOnceWithArg(&gECDHEKeyPairs[index].once,
                             ssl_CreateStaticECDHEKeyPair,
                             (void *)&arg);
    PORT_Assert(prv == PR_SUCCESS);
    if (prv != PR_SUCCESS) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }

    keyPair = gECDHEKeyPairs[index].keyPair;
    if (!keyPair) {
        /* Attempting to use a key pair for an unsupported group. */
        PORT_Assert(keyPair);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return SECFailure;
    }

    keyPair = ssl_CopyEphemeralKeyPair(keyPair);
    if (!keyPair)
        return SECFailure;

    PORT_Assert(PR_CLIST_IS_EMPTY(&ss->ephemeralKeyPairs));
    PR_APPEND_LINK(&keyPair->link, &ss->ephemeralKeyPairs);
    return SECSuccess;
}
