/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * SSL3 Protocol
 *
 * 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/. */

/* ECC code moved here from ssl3con.c */

#include "nss.h"
#include "cert.h"
#include "ssl.h"
#include "cryptohi.h" /* for DSAU_ stuff */
#include "keyhi.h"
#include "secder.h"
#include "secitem.h"

#include "sslimpl.h"
#include "sslproto.h"
#include "sslerr.h"
#include "prtime.h"
#include "prinrval.h"
#include "prerror.h"
#include "pratom.h"
#include "prthread.h"
#include "prinit.h"

#include "pk11func.h"
#include "secmod.h"

#include <stdio.h>

#ifndef NSS_DISABLE_ECC

#ifndef PK11_SETATTRS
#define PK11_SETATTRS(x, id, v, l) \
    (x)->type = (id);              \
    (x)->pValue = (v);             \
    (x)->ulValueLen = (l);
#endif

#define SSL_GET_SERVER_PUBLIC_KEY(sock, type)                                          \
    (ss->serverCerts[type].serverKeyPair ? ss->serverCerts[type].serverKeyPair->pubKey \
                                         : NULL)

#define SSL_IS_CURVE_NEGOTIATED(curvemsk, curveName) \
    ((curveName > ec_noName) &&                      \
     (curveName < ec_pastLastName) &&                \
     ((1UL << curveName) & curvemsk) != 0)

static SECStatus ssl3_CreateECDHEphemeralKeys(sslSocket *ss, ECName ec_curve);

#define supportedCurve(x) (((x) > ec_noName) && ((x) < ec_pastLastName))

/* Table containing OID tags for elliptic curves named in the
 * ECC-TLS IETF draft.
 */
static const SECOidTag ecName2OIDTag[] = {
    0,
    SEC_OID_SECG_EC_SECT163K1, /*  1 */
    SEC_OID_SECG_EC_SECT163R1, /*  2 */
    SEC_OID_SECG_EC_SECT163R2, /*  3 */
    SEC_OID_SECG_EC_SECT193R1, /*  4 */
    SEC_OID_SECG_EC_SECT193R2, /*  5 */
    SEC_OID_SECG_EC_SECT233K1, /*  6 */
    SEC_OID_SECG_EC_SECT233R1, /*  7 */
    SEC_OID_SECG_EC_SECT239K1, /*  8 */
    SEC_OID_SECG_EC_SECT283K1, /*  9 */
    SEC_OID_SECG_EC_SECT283R1, /* 10 */
    SEC_OID_SECG_EC_SECT409K1, /* 11 */
    SEC_OID_SECG_EC_SECT409R1, /* 12 */
    SEC_OID_SECG_EC_SECT571K1, /* 13 */
    SEC_OID_SECG_EC_SECT571R1, /* 14 */
    SEC_OID_SECG_EC_SECP160K1, /* 15 */
    SEC_OID_SECG_EC_SECP160R1, /* 16 */
    SEC_OID_SECG_EC_SECP160R2, /* 17 */
    SEC_OID_SECG_EC_SECP192K1, /* 18 */
    SEC_OID_SECG_EC_SECP192R1, /* 19 */
    SEC_OID_SECG_EC_SECP224K1, /* 20 */
    SEC_OID_SECG_EC_SECP224R1, /* 21 */
    SEC_OID_SECG_EC_SECP256K1, /* 22 */
    SEC_OID_SECG_EC_SECP256R1, /* 23 */
    SEC_OID_SECG_EC_SECP384R1, /* 24 */
    SEC_OID_SECG_EC_SECP521R1, /* 25 */
};

static const PRUint16 curve2bits[] = {
    0,    /*  ec_noName     = 0,   */
    163,  /*  ec_sect163k1  = 1,   */
    163,  /*  ec_sect163r1  = 2,   */
    163,  /*  ec_sect163r2  = 3,   */
    193,  /*  ec_sect193r1  = 4,   */
    193,  /*  ec_sect193r2  = 5,   */
    233,  /*  ec_sect233k1  = 6,   */
    233,  /*  ec_sect233r1  = 7,   */
    239,  /*  ec_sect239k1  = 8,   */
    283,  /*  ec_sect283k1  = 9,   */
    283,  /*  ec_sect283r1  = 10,  */
    409,  /*  ec_sect409k1  = 11,  */
    409,  /*  ec_sect409r1  = 12,  */
    571,  /*  ec_sect571k1  = 13,  */
    571,  /*  ec_sect571r1  = 14,  */
    160,  /*  ec_secp160k1  = 15,  */
    160,  /*  ec_secp160r1  = 16,  */
    160,  /*  ec_secp160r2  = 17,  */
    192,  /*  ec_secp192k1  = 18,  */
    192,  /*  ec_secp192r1  = 19,  */
    224,  /*  ec_secp224k1  = 20,  */
    224,  /*  ec_secp224r1  = 21,  */
    256,  /*  ec_secp256k1  = 22,  */
    256,  /*  ec_secp256r1  = 23,  */
    384,  /*  ec_secp384r1  = 24,  */
    521,  /*  ec_secp521r1  = 25,  */
    65535 /*  ec_pastLastName      */
};

typedef struct Bits2CurveStr {
    PRUint16 bits;
    ECName curve;
} Bits2Curve;

static const Bits2Curve bits2curve[] = {
    { 192, ec_secp192r1 /*  = 19,  fast */ },
    { 160, ec_secp160r2 /*  = 17,  fast */ },
    { 160, ec_secp160k1 /*  = 15,  */ },
    { 160, ec_secp160r1 /*  = 16,  */ },
    { 163, ec_sect163k1 /*  = 1,   */ },
    { 163, ec_sect163r1 /*  = 2,   */ },
    { 163, ec_sect163r2 /*  = 3,   */ },
    { 192, ec_secp192k1 /*  = 18,  */ },
    { 193, ec_sect193r1 /*  = 4,   */ },
    { 193, ec_sect193r2 /*  = 5,   */ },
    { 224, ec_secp224r1 /*  = 21,  fast */ },
    { 224, ec_secp224k1 /*  = 20,  */ },
    { 233, ec_sect233k1 /*  = 6,   */ },
    { 233, ec_sect233r1 /*  = 7,   */ },
    { 239, ec_sect239k1 /*  = 8,   */ },
    { 256, ec_secp256r1 /*  = 23,  fast */ },
    { 256, ec_secp256k1 /*  = 22,  */ },
    { 283, ec_sect283k1 /*  = 9,   */ },
    { 283, ec_sect283r1 /*  = 10,  */ },
    { 384, ec_secp384r1 /*  = 24,  fast */ },
    { 409, ec_sect409k1 /*  = 11,  */ },
    { 409, ec_sect409r1 /*  = 12,  */ },
    { 521, ec_secp521r1 /*  = 25,  fast */ },
    { 571, ec_sect571k1 /*  = 13,  */ },
    { 571, ec_sect571r1 /*  = 14,  */ },
    { 65535, ec_noName }
};

typedef struct ECDHEKeyPairStr {
    ssl3KeyPair *pair;
    int error; /* error code of the call-once function */
    PRCallOnceType once;
} ECDHEKeyPair;

/* arrays of ECDHE KeyPairs */
static ECDHEKeyPair gECDHEKeyPairs[ec_pastLastName];

SECStatus
ssl3_ECName2Params(PLArenaPool *arena, ECName curve, SECKEYECParams *params)
{
    SECOidData *oidData = NULL;
    PRUint32 policyFlags = 0;

    if ((curve <= ec_noName) || (curve >= ec_pastLastName) ||
        ((oidData = SECOID_FindOIDByTag(ecName2OIDTag[curve])) == NULL)) {
        PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
        return SECFailure;
    }

    if ((NSS_GetAlgorithmPolicy(ecName2OIDTag[curve], &policyFlags) ==
         SECSuccess) &&
        !(policyFlags & NSS_USE_ALG_IN_SSL_KX)) {
        PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
        return SECFailure;
    }

    SECITEM_AllocItem(arena, params, (2 + oidData->oid.len));
    /*
     * params->data needs to contain the ASN encoding of an object ID (OID)
     * representing the named curve. The actual OID is in
     * oidData->oid.data so we simply prepend 0x06 and OID length
     */
    params->data[0] = SEC_ASN1_OBJECT_ID;
    params->data[1] = oidData->oid.len;
    memcpy(params->data + 2, oidData->oid.data, oidData->oid.len);

    return SECSuccess;
}

ECName
ssl3_PubKey2ECName(SECKEYPublicKey *pubKey)
{
    SECItem oid = { siBuffer, NULL, 0 };
    SECOidData *oidData = NULL;
    PRUint32 policyFlags = 0;
    ECName i;
    SECKEYECParams *params;

    if (pubKey->keyType != ecKey) {
        PORT_Assert(0);
        return ec_noName;
    }

    params = &pubKey->u.ec.DEREncodedParams;

    /*
     * params->data needs to contain the ASN encoding of an object ID (OID)
     * representing a named curve. Here, we strip away everything
     * before the actual OID and use the OID to look up a named curve.
     */
    if (params->data[0] != SEC_ASN1_OBJECT_ID)
        return ec_noName;
    oid.len = params->len - 2;
    oid.data = params->data + 2;
    if ((oidData = SECOID_FindOID(&oid)) == NULL)
        return ec_noName;
    if ((NSS_GetAlgorithmPolicy(oidData->offset, &policyFlags) ==
         SECSuccess) &&
        !(policyFlags & NSS_USE_ALG_IN_SSL_KX)) {
        return ec_noName;
    }
    for (i = ec_noName + 1; i < ec_pastLastName; i++) {
        if (ecName2OIDTag[i] == oidData->offset)
            return i;
    }

    return ec_noName;
}

/* Caller must set hiLevel error code. */
static SECStatus
ssl3_ComputeECDHKeyHash(SSLHashType hashAlg,
                        SECItem ec_params, SECItem server_ecpoint,
                        SSL3Random *client_rand, SSL3Random *server_rand,
                        SSL3Hashes *hashes, PRBool bypassPKCS11)
{
    PRUint8 *hashBuf;
    PRUint8 *pBuf;
    SECStatus rv = SECSuccess;
    unsigned int bufLen;
    /*
     * XXX For now, we only support named curves (the appropriate
     * checks are made before this method is called) so ec_params
     * takes up only two bytes. ECPoint needs to fit in 256 bytes
     * (because the spec says the length must fit in one byte)
     */
    PRUint8 buf[2 * SSL3_RANDOM_LENGTH + 2 + 1 + 256];

    bufLen = 2 * SSL3_RANDOM_LENGTH + ec_params.len + 1 + server_ecpoint.len;
    if (bufLen <= sizeof buf) {
        hashBuf = buf;
    } else {
        hashBuf = PORT_Alloc(bufLen);
        if (!hashBuf) {
            return SECFailure;
        }
    }

    memcpy(hashBuf, client_rand, SSL3_RANDOM_LENGTH);
    pBuf = hashBuf + SSL3_RANDOM_LENGTH;
    memcpy(pBuf, server_rand, SSL3_RANDOM_LENGTH);
    pBuf += SSL3_RANDOM_LENGTH;
    memcpy(pBuf, ec_params.data, ec_params.len);
    pBuf += ec_params.len;
    pBuf[0] = (PRUint8)(server_ecpoint.len);
    pBuf += 1;
    memcpy(pBuf, server_ecpoint.data, server_ecpoint.len);
    pBuf += server_ecpoint.len;
    PORT_Assert((unsigned int)(pBuf - hashBuf) == bufLen);

    rv = ssl3_ComputeCommonKeyHash(hashAlg, hashBuf, bufLen, hashes,
                                   bypassPKCS11);

    PRINT_BUF(95, (NULL, "ECDHkey hash: ", hashBuf, bufLen));
    PRINT_BUF(95, (NULL, "ECDHkey hash: MD5 result",
                   hashes->u.s.md5, MD5_LENGTH));
    PRINT_BUF(95, (NULL, "ECDHkey hash: SHA1 result",
                   hashes->u.s.sha, SHA1_LENGTH));

    if (hashBuf != buf)
        PORT_Free(hashBuf);
    return rv;
}

/* Called from ssl3_SendClientKeyExchange(). */
SECStatus
ssl3_SendECDHClientKeyExchange(sslSocket *ss, SECKEYPublicKey *svrPubKey)
{
    PK11SymKey *pms = NULL;
    SECStatus rv = SECFailure;
    PRBool isTLS, isTLS12;
    CK_MECHANISM_TYPE target;
    SECKEYPublicKey *pubKey = NULL;   /* Ephemeral ECDH key */
    SECKEYPrivateKey *privKey = NULL; /* Ephemeral ECDH key */

    PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
    PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));

    isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
    isTLS12 = (PRBool)(ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);

    /* Generate ephemeral EC keypair */
    if (svrPubKey->keyType != ecKey) {
        PORT_SetError(SEC_ERROR_BAD_KEY);
        goto loser;
    }
    /* XXX SHOULD CALL ssl3_CreateECDHEphemeralKeys here, instead! */
    privKey = SECKEY_CreateECPrivateKey(&svrPubKey->u.ec.DEREncodedParams,
                                        &pubKey, ss->pkcs11PinArg);
    if (!privKey || !pubKey) {
        ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
        rv = SECFailure;
        goto loser;
    }
    PRINT_BUF(50, (ss, "ECDH public value:",
                   pubKey->u.ec.publicValue.data,
                   pubKey->u.ec.publicValue.len));

    if (isTLS12) {
        target = CKM_TLS12_MASTER_KEY_DERIVE_DH;
    } else if (isTLS) {
        target = CKM_TLS_MASTER_KEY_DERIVE_DH;
    } else {
        target = CKM_SSL3_MASTER_KEY_DERIVE_DH;
    }

    /*  Determine the PMS */
    pms = PK11_PubDeriveWithKDF(privKey, svrPubKey, PR_FALSE, NULL, NULL,
                                CKM_ECDH1_DERIVE, target, CKA_DERIVE, 0,
                                CKD_NULL, NULL, NULL);

    if (pms == NULL) {
        SSL3AlertDescription desc = illegal_parameter;
        (void)SSL3_SendAlert(ss, alert_fatal, desc);
        ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
        goto loser;
    }

    SECKEY_DestroyPrivateKey(privKey);
    privKey = NULL;

    rv = ssl3_AppendHandshakeHeader(ss, client_key_exchange,
                                    pubKey->u.ec.publicValue.len + 1);
    if (rv != SECSuccess) {
        goto loser; /* err set by ssl3_AppendHandshake* */
    }

    rv = ssl3_AppendHandshakeVariable(ss,
                                      pubKey->u.ec.publicValue.data,
                                      pubKey->u.ec.publicValue.len, 1);
    SECKEY_DestroyPublicKey(pubKey);
    pubKey = NULL;

    if (rv != SECSuccess) {
        goto loser; /* err set by ssl3_AppendHandshake* */
    }

    rv = ssl3_InitPendingCipherSpec(ss, pms);
    PK11_FreeSymKey(pms);
    pms = NULL;

    if (rv != SECSuccess) {
        ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
        goto loser;
    }

    rv = SECSuccess;

loser:
    if (pms)
        PK11_FreeSymKey(pms);
    if (privKey)
        SECKEY_DestroyPrivateKey(privKey);
    if (pubKey)
        SECKEY_DestroyPublicKey(pubKey);
    return rv;
}

ECName
tls13_GroupForECDHEKeyShare(ssl3KeyPair *pair)
{
    return ssl3_PubKey2ECName(pair->pubKey);
}

/* This function returns the size of the key_exchange field in
 * the KeyShareEntry structure. */
unsigned int
tls13_SizeOfECDHEKeyShareKEX(ssl3KeyPair *pair)
{
    return 1 + /* Length */
           pair->pubKey->u.ec.publicValue.len;
}

/* This function encodes the key_exchange field in
 * the KeyShareEntry structure. */
SECStatus
tls13_EncodeECDHEKeyShareKEX(sslSocket *ss, ssl3KeyPair *pair)
{
    const SECItem *publicValue;

    PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
    PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));

    publicValue = &pair->pubKey->u.ec.publicValue;

    return ssl3_AppendHandshakeVariable(ss, publicValue->data,
                                        publicValue->len, 1);
}

/*
** Called from ssl3_HandleClientKeyExchange()
*/
SECStatus
ssl3_HandleECDHClientKeyExchange(sslSocket *ss, SSL3Opaque *b,
                                 PRUint32 length,
                                 SECKEYPublicKey *srvrPubKey,
                                 SECKEYPrivateKey *srvrPrivKey)
{
    PK11SymKey *pms;
    SECStatus rv;
    SECKEYPublicKey clntPubKey;
    CK_MECHANISM_TYPE target;
    PRBool isTLS, isTLS12;

    PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
    PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));

    clntPubKey.keyType = ecKey;
    clntPubKey.u.ec.DEREncodedParams.len =
        srvrPubKey->u.ec.DEREncodedParams.len;
    clntPubKey.u.ec.DEREncodedParams.data =
        srvrPubKey->u.ec.DEREncodedParams.data;

    rv = ssl3_ConsumeHandshakeVariable(ss, &clntPubKey.u.ec.publicValue,
                                       1, &b, &length);
    if (rv != SECSuccess) {
        SEND_ALERT
        return SECFailure; /* XXX Who sets the error code?? */
    }

    isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
    isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);

    if (isTLS12) {
        target = CKM_TLS12_MASTER_KEY_DERIVE_DH;
    } else if (isTLS) {
        target = CKM_TLS_MASTER_KEY_DERIVE_DH;
    } else {
        target = CKM_SSL3_MASTER_KEY_DERIVE_DH;
    }

    /*  Determine the PMS */
    pms = PK11_PubDeriveWithKDF(srvrPrivKey, &clntPubKey, PR_FALSE, NULL, NULL,
                                CKM_ECDH1_DERIVE, target, CKA_DERIVE, 0,
                                CKD_NULL, NULL, NULL);

    if (pms == NULL) {
        /* last gasp.  */
        ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
        return SECFailure;
    }

    rv = ssl3_InitPendingCipherSpec(ss, pms);
    PK11_FreeSymKey(pms);
    if (rv != SECSuccess) {
        SEND_ALERT
        return SECFailure; /* error code set by ssl3_InitPendingCipherSpec */
    }
    return SECSuccess;
}

/*
** Take an encoded key share and make a public key out of it.
** returns NULL on error.
*/
SECKEYPublicKey *
tls13_ImportECDHKeyShare(sslSocket *ss, SSL3Opaque *b,
                         PRUint32 length, ECName curve)
{
    PLArenaPool *arena = NULL;
    SECKEYPublicKey *peerKey = NULL;
    SECStatus rv;
    SECItem ecPoint = { siBuffer, NULL, 0 };

    PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
    PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));

    rv = ssl3_ConsumeHandshakeVariable(ss, &ecPoint, 1, &b, &length);
    if (rv != SECSuccess) {
        tls13_FatalError(ss, SSL_ERROR_RX_MALFORMED_ECDHE_KEY_SHARE,
                         illegal_parameter);
        return NULL;
    }
    if (length || !ecPoint.len) {
        tls13_FatalError(ss, SSL_ERROR_RX_MALFORMED_ECDHE_KEY_SHARE,
                         illegal_parameter);
        return NULL;
    }

    /* Fail if the ec point uses compressed representation */
    if (ecPoint.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
        tls13_FatalError(ss, SEC_ERROR_UNSUPPORTED_EC_POINT_FORM,
                         illegal_parameter);
        return NULL;
    }

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto no_memory;
    }

    peerKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
    if (peerKey == NULL) {
        goto no_memory;
    }

    peerKey->arena = arena;
    peerKey->keyType = ecKey;
    /* Set up the encoded params */
    rv = ssl3_ECName2Params(arena, curve, &peerKey->u.ec.DEREncodedParams);
    if (rv != SECSuccess) {
        goto no_memory;
    }

    /* copy publicValue in peerKey */
    if (SECITEM_CopyItem(arena, &peerKey->u.ec.publicValue, &ecPoint) !=
        SECSuccess) {
        goto no_memory;
    }
    peerKey->pkcs11Slot = NULL;
    peerKey->pkcs11ID = CK_INVALID_HANDLE;

    return peerKey;

no_memory: /* no-memory error has already been set. */
    PORT_FreeArena(arena, PR_FALSE);
    ssl_MapLowLevelError(SSL_ERROR_RX_MALFORMED_ECDHE_KEY_SHARE);
    return NULL;
}

PK11SymKey *
tls13_ComputeECDHSharedKey(sslSocket *ss,
                           SECKEYPrivateKey *myPrivKey,
                           SECKEYPublicKey *peerKey)
{
    PK11SymKey *shared;

    /* Determine the PMS */
    shared = PK11_PubDeriveWithKDF(myPrivKey, peerKey, PR_FALSE, NULL, NULL,
                                   CKM_ECDH1_DERIVE,
                                   tls13_GetHkdfMechanism(ss), CKA_DERIVE, 0,
                                   CKD_NULL, NULL, NULL);

    if (!shared) {
        ssl_MapLowLevelError(SSL_ERROR_KEY_EXCHANGE_FAILURE);
        return NULL;
    }

    return shared;
}

ECName
ssl3_GetCurveWithECKeyStrength(PRUint32 curvemsk, int requiredECCbits)
{
    int i;

    for (i = 0; bits2curve[i].curve != ec_noName; i++) {
        if (bits2curve[i].bits < requiredECCbits)
            continue;
        if (SSL_IS_CURVE_NEGOTIATED(curvemsk, bits2curve[i].curve)) {
            return bits2curve[i].curve;
        }
    }
    PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
    return ec_noName;
}

/* find the "weakest link".  Get strength of signature key and of sym key.
 * choose curve for the weakest of those two.
 */
ECName
ssl3_GetCurveNameForServerSocket(sslSocket *ss)
{
    SECKEYPublicKey *svrPublicKey = NULL;
    ECName ec_curve = ec_noName;
    int signatureKeyStrength = 521;
    int requiredECCbits = ss->sec.secretKeyBits * 2;

    if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_ecdsa) {
        svrPublicKey = SSL_GET_SERVER_PUBLIC_KEY(ss, kt_ecdh);
        if (svrPublicKey)
            ec_curve = ssl3_PubKey2ECName(svrPublicKey);
        if (!SSL_IS_CURVE_NEGOTIATED(ss->ssl3.hs.negotiatedECCurves, ec_curve)) {
            PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
            return ec_noName;
        }
        signatureKeyStrength = curve2bits[ec_curve];
    } else {
        /* RSA is our signing cert */
        int serverKeyStrengthInBits;

        svrPublicKey = SSL_GET_SERVER_PUBLIC_KEY(ss, kt_rsa);
        if (!svrPublicKey) {
            PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
            return ec_noName;
        }

        /* currently strength in bytes */
        serverKeyStrengthInBits = svrPublicKey->u.rsa.modulus.len;
        if (svrPublicKey->u.rsa.modulus.data[0] == 0) {
            serverKeyStrengthInBits--;
        }
        /* convert to strength in bits */
        serverKeyStrengthInBits *= BPB;

        signatureKeyStrength =
            SSL_RSASTRENGTH_TO_ECSTRENGTH(serverKeyStrengthInBits);
    }
    if (requiredECCbits > signatureKeyStrength)
        requiredECCbits = signatureKeyStrength;

    return ssl3_GetCurveWithECKeyStrength(ss->ssl3.hs.negotiatedECCurves,
                                          requiredECCbits);
}

/* function to clear out the lists */
static SECStatus
ssl3_ShutdownECDHECurves(void *appData, void *nssData)
{
    int i;
    ECDHEKeyPair *keyPair = &gECDHEKeyPairs[0];

    for (i = 0; i < ec_pastLastName; i++, keyPair++) {
        if (keyPair->pair) {
            ssl3_FreeKeyPair(keyPair->pair);
        }
    }
    memset(gECDHEKeyPairs, 0, sizeof gECDHEKeyPairs);
    return SECSuccess;
}

static PRStatus
ssl3_ECRegister(void)
{
    SECStatus rv;
    rv = NSS_RegisterShutdown(ssl3_ShutdownECDHECurves, gECDHEKeyPairs);
    if (rv != SECSuccess) {
        gECDHEKeyPairs[ec_noName].error = PORT_GetError();
    }
    return (PRStatus)rv;
}

/* Create an ECDHE key pair for a given curve */
SECStatus
ssl3_CreateECDHEphemeralKeyPair(ECName ec_curve, ssl3KeyPair **keyPair)
{
    SECKEYPrivateKey *privKey = NULL;
    SECKEYPublicKey *pubKey = NULL;
    SECKEYECParams ecParams = { siBuffer, NULL, 0 };

    if (ssl3_ECName2Params(NULL, ec_curve, &ecParams) != SECSuccess) {
        return SECFailure;
    }
    privKey = SECKEY_CreateECPrivateKey(&ecParams, &pubKey, NULL);
    SECITEM_FreeItem(&ecParams, PR_FALSE);

    if (!privKey || !pubKey || !(*keyPair = ssl3_NewKeyPair(privKey, pubKey))) {
        if (privKey) {
            SECKEY_DestroyPrivateKey(privKey);
        }
        if (pubKey) {
            SECKEY_DestroyPublicKey(pubKey);
        }
        ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
        return SECFailure;
    }

    return SECSuccess;
}

/* CallOnce function, called once for each named curve. */
static PRStatus
ssl3_CreateECDHEphemeralKeyPairOnce(void *arg)
{
    ECName ec_curve = (ECName)arg;
    ssl3KeyPair *keyPair = NULL;

    PORT_Assert(gECDHEKeyPairs[ec_curve].pair == NULL);

    /* ok, no one has generated a global key for this curve yet, do so */
    if (ssl3_CreateECDHEphemeralKeyPair(ec_curve, &keyPair) != SECSuccess) {
        gECDHEKeyPairs[ec_curve].error = PORT_GetError();
        return PR_FAILURE;
    }

    gECDHEKeyPairs[ec_curve].pair = keyPair;
    return PR_SUCCESS;
}

/*
 * Creates the ephemeral public and private ECDH keys used by
 * server in ECDHE_RSA and ECDHE_ECDSA handshakes.
 * For now, the elliptic curve is chosen to be the same
 * strength as the signing certificate (ECC or RSA).
 * We need an API to specify the curve. This won't be a real
 * issue until we further develop server-side support for ECC
 * cipher suites.
 */
static SECStatus
ssl3_CreateECDHEphemeralKeys(sslSocket *ss, ECName ec_curve)
{
    ssl3KeyPair *keyPair = NULL;

    /* if there's no global key for this curve, make one. */
    if (gECDHEKeyPairs[ec_curve].pair == NULL) {
        PRStatus status;

        status = PR_CallOnce(&gECDHEKeyPairs[ec_noName].once, ssl3_ECRegister);
        if (status != PR_SUCCESS) {
            PORT_SetError(gECDHEKeyPairs[ec_noName].error);
            return SECFailure;
        }
        status = PR_CallOnceWithArg(&gECDHEKeyPairs[ec_curve].once,
                                    ssl3_CreateECDHEphemeralKeyPairOnce,
                                    (void *)ec_curve);
        if (status != PR_SUCCESS) {
            PORT_SetError(gECDHEKeyPairs[ec_curve].error);
            return SECFailure;
        }
    }

    keyPair = gECDHEKeyPairs[ec_curve].pair;
    PORT_Assert(keyPair != NULL);
    if (!keyPair)
        return SECFailure;
    ss->ephemeralECDHKeyPair = ssl3_GetKeyPairRef(keyPair);

    return SECSuccess;
}

SECStatus
ssl3_HandleECDHServerKeyExchange(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
{
    PLArenaPool *arena = NULL;
    SECKEYPublicKey *peerKey = NULL;
    PRBool isTLS, isTLS12;
    SECStatus rv;
    int errCode = SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH;
    SSL3AlertDescription desc = illegal_parameter;
    SSL3Hashes hashes;
    SECItem signature = { siBuffer, NULL, 0 };

    SECItem ec_params = { siBuffer, NULL, 0 };
    SECItem ec_point = { siBuffer, NULL, 0 };
    unsigned char paramBuf[3]; /* only for curve_type == named_curve */
    SSLSignatureAndHashAlg sigAndHash;

    sigAndHash.hashAlg = ssl_hash_none;

    isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
    isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);

    /* XXX This works only for named curves, revisit this when
     * we support generic curves.
     */
    ec_params.len = sizeof paramBuf;
    ec_params.data = paramBuf;
    rv = ssl3_ConsumeHandshake(ss, ec_params.data, ec_params.len, &b, &length);
    if (rv != SECSuccess) {
        goto loser; /* malformed. */
    }

    /* Fail if the curve is not a named curve */
    if ((ec_params.data[0] != ec_type_named) ||
        (ec_params.data[1] != 0) ||
        !supportedCurve(ec_params.data[2])) {
        errCode = SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
        desc = handshake_failure;
        goto alert_loser;
    }

    rv = ssl3_ConsumeHandshakeVariable(ss, &ec_point, 1, &b, &length);
    if (rv != SECSuccess) {
        goto loser; /* malformed. */
    }
    /* Fail if the ec point uses compressed representation */
    if (ec_point.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
        errCode = SEC_ERROR_UNSUPPORTED_EC_POINT_FORM;
        desc = handshake_failure;
        goto alert_loser;
    }

    if (isTLS12) {
        rv = ssl3_ConsumeSignatureAndHashAlgorithm(ss, &b, &length,
                                                   &sigAndHash);
        if (rv != SECSuccess) {
            goto loser; /* malformed or unsupported. */
        }
        rv = ssl3_CheckSignatureAndHashAlgorithmConsistency(
            ss, &sigAndHash, ss->sec.peerCert);
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    rv = ssl3_ConsumeHandshakeVariable(ss, &signature, 2, &b, &length);
    if (rv != SECSuccess) {
        goto loser; /* malformed. */
    }

    if (length != 0) {
        if (isTLS)
            desc = decode_error;
        goto alert_loser; /* malformed. */
    }

    PRINT_BUF(60, (NULL, "Server EC params", ec_params.data,
                   ec_params.len));
    PRINT_BUF(60, (NULL, "Server EC point", ec_point.data, ec_point.len));

    /* failures after this point are not malformed handshakes. */
    /* TLS: send decrypt_error if signature failed. */
    desc = isTLS ? decrypt_error : handshake_failure;

    /*
     *  check to make sure the hash is signed by right guy
     */
    rv = ssl3_ComputeECDHKeyHash(sigAndHash.hashAlg, ec_params, ec_point,
                                 &ss->ssl3.hs.client_random,
                                 &ss->ssl3.hs.server_random,
                                 &hashes, ss->opt.bypassPKCS11);

    if (rv != SECSuccess) {
        errCode =
            ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
        goto alert_loser;
    }
    rv = ssl3_VerifySignedHashes(&hashes, ss->sec.peerCert, &signature,
                                 isTLS, ss->pkcs11PinArg);
    if (rv != SECSuccess) {
        errCode =
            ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
        goto alert_loser;
    }

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto no_memory;
    }

    peerKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
    if (peerKey == NULL) {
        goto no_memory;
    }

    peerKey->arena = arena;
    peerKey->keyType = ecKey;

    /* set up EC parameters in peerKey */
    if (ssl3_ECName2Params(arena, ec_params.data[2],
                           &peerKey->u.ec.DEREncodedParams) !=
        SECSuccess) {
        /* we should never get here since we already
         * checked that we are dealing with a supported curve
         */
        errCode = SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
        goto alert_loser;
    }

    /* copy publicValue in peerKey */
    if (SECITEM_CopyItem(arena, &peerKey->u.ec.publicValue, &ec_point)) {
        goto no_memory;
    }
    peerKey->pkcs11Slot = NULL;
    peerKey->pkcs11ID = CK_INVALID_HANDLE;

    ss->sec.peerKey = peerKey;
    ss->ssl3.hs.ws = wait_cert_request;

    return SECSuccess;

alert_loser:
    (void)SSL3_SendAlert(ss, alert_fatal, desc);
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    PORT_SetError(errCode);
    return SECFailure;

no_memory: /* no-memory error has already been set. */
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
    return SECFailure;
}

SECStatus
ssl3_SendECDHServerKeyExchange(
    sslSocket *ss,
    const SSLSignatureAndHashAlg *sigAndHash)
{
    const ssl3KEADef *kea_def = ss->ssl3.hs.kea_def;
    SECStatus rv = SECFailure;
    int length;
    PRBool isTLS, isTLS12;
    SECItem signed_hash = { siBuffer, NULL, 0 };
    SSL3Hashes hashes;

    SECKEYPublicKey *ecdhePub;
    SECItem ec_params = { siBuffer, NULL, 0 };
    unsigned char paramBuf[3];
    ECName curve;
    SSL3KEAType certIndex;

    /* Generate ephemeral ECDH key pair and send the public key */
    curve = ssl3_GetCurveNameForServerSocket(ss);
    if (curve == ec_noName) {
        goto loser;
    }

    if (ss->opt.reuseServerECDHEKey) {
        rv = ssl3_CreateECDHEphemeralKeys(ss, curve);
    } else {
        rv = ssl3_CreateECDHEphemeralKeyPair(curve, &ss->ephemeralECDHKeyPair);
    }
    if (rv != SECSuccess) {
        goto loser;
    }

    ecdhePub = ss->ephemeralECDHKeyPair->pubKey;
    PORT_Assert(ecdhePub != NULL);
    if (!ecdhePub) {
        PORT_SetError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
        return SECFailure;
    }

    ec_params.len = sizeof paramBuf;
    ec_params.data = paramBuf;
    curve = ssl3_PubKey2ECName(ecdhePub);
    if (curve != ec_noName) {
        ec_params.data[0] = ec_type_named;
        ec_params.data[1] = 0x00;
        ec_params.data[2] = curve;
    } else {
        PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
        goto loser;
    }

    rv = ssl3_ComputeECDHKeyHash(sigAndHash->hashAlg,
                                 ec_params,
                                 ecdhePub->u.ec.publicValue,
                                 &ss->ssl3.hs.client_random,
                                 &ss->ssl3.hs.server_random,
                                 &hashes, ss->opt.bypassPKCS11);
    if (rv != SECSuccess) {
        ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
        goto loser;
    }

    isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
    isTLS12 = (PRBool)(ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);

    /* XXX SSLKEAType isn't really a good choice for
     * indexing certificates but that's all we have
     * for now.
     */
    if (kea_def->kea == kea_ecdhe_rsa)
        certIndex = kt_rsa;
    else /* kea_def->kea == kea_ecdhe_ecdsa */
        certIndex = kt_ecdh;

    rv = ssl3_SignHashes(&hashes, ss->serverCerts[certIndex].SERVERKEY,
                         &signed_hash, isTLS);
    if (rv != SECSuccess) {
        goto loser; /* ssl3_SignHashes has set err. */
    }
    if (signed_hash.data == NULL) {
        /* how can this happen and rv == SECSuccess ?? */
        PORT_SetError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
        goto loser;
    }

    length = ec_params.len +
             1 + ecdhePub->u.ec.publicValue.len +
             (isTLS12 ? 2 : 0) + 2 + signed_hash.len;

    rv = ssl3_AppendHandshakeHeader(ss, server_key_exchange, length);
    if (rv != SECSuccess) {
        goto loser; /* err set by AppendHandshake. */
    }

    rv = ssl3_AppendHandshake(ss, ec_params.data, ec_params.len);
    if (rv != SECSuccess) {
        goto loser; /* err set by AppendHandshake. */
    }

    rv = ssl3_AppendHandshakeVariable(ss, ecdhePub->u.ec.publicValue.data,
                                      ecdhePub->u.ec.publicValue.len, 1);
    if (rv != SECSuccess) {
        goto loser; /* err set by AppendHandshake. */
    }

    if (isTLS12) {
        rv = ssl3_AppendSignatureAndHashAlgorithm(ss, sigAndHash);
        if (rv != SECSuccess) {
            goto loser; /* err set by AppendHandshake. */
        }
    }

    rv = ssl3_AppendHandshakeVariable(ss, signed_hash.data,
                                      signed_hash.len, 2);
    if (rv != SECSuccess) {
        goto loser; /* err set by AppendHandshake. */
    }

    PORT_Free(signed_hash.data);
    return SECSuccess;

loser:
    if (signed_hash.data != NULL)
        PORT_Free(signed_hash.data);
    return SECFailure;
}

/* Lists of ECC cipher suites for searching and disabling. */

static const ssl3CipherSuite ecdh_suites[] = {
    TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_NULL_SHA,
    TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
    TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_RSA_WITH_NULL_SHA,
    TLS_ECDH_RSA_WITH_RC4_128_SHA,
    0 /* end of list marker */
};

static const ssl3CipherSuite ecdh_ecdsa_suites[] = {
    TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_NULL_SHA,
    TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
    0 /* end of list marker */
};

static const ssl3CipherSuite ecdh_rsa_suites[] = {
    TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_RSA_WITH_NULL_SHA,
    TLS_ECDH_RSA_WITH_RC4_128_SHA,
    0 /* end of list marker */
};

static const ssl3CipherSuite ecdhe_ecdsa_suites[] = {
    TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
    TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
    TLS_ECDHE_ECDSA_WITH_NULL_SHA,
    TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
    0 /* end of list marker */
};

static const ssl3CipherSuite ecdhe_rsa_suites[] = {
    TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
    TLS_ECDHE_RSA_WITH_NULL_SHA,
    TLS_ECDHE_RSA_WITH_RC4_128_SHA,
    0 /* end of list marker */
};

/* List of all ECC cipher suites */
static const ssl3CipherSuite ecSuites[] = {
    TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
    TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
    TLS_ECDHE_ECDSA_WITH_NULL_SHA,
    TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
    TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
    TLS_ECDHE_RSA_WITH_NULL_SHA,
    TLS_ECDHE_RSA_WITH_RC4_128_SHA,
    TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_NULL_SHA,
    TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
    TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_RSA_WITH_NULL_SHA,
    TLS_ECDH_RSA_WITH_RC4_128_SHA,
    0 /* end of list marker */
};

/* On this socket, Disable the ECC cipher suites in the argument's list */
SECStatus
ssl3_DisableECCSuites(sslSocket *ss, const ssl3CipherSuite *suite)
{
    if (!suite)
        suite = ecSuites;
    for (; *suite; ++suite) {
        PORT_CheckSuccess(ssl3_CipherPrefSet(ss, *suite, PR_FALSE));
    }
    return SECSuccess;
}

/* Look at the server certs configured on this socket, and disable any
 * ECC cipher suites that are not supported by those certs.
 */
void
ssl3_FilterECCipherSuitesByServerCerts(sslSocket *ss)
{
    CERTCertificate *svrCert;

    svrCert = ss->serverCerts[kt_rsa].serverCert;
    if (!svrCert) {
        ssl3_DisableECCSuites(ss, ecdhe_rsa_suites);
    }

    svrCert = ss->serverCerts[kt_ecdh].serverCert;
    if (!svrCert) {
        ssl3_DisableECCSuites(ss, ecdh_suites);
        ssl3_DisableECCSuites(ss, ecdhe_ecdsa_suites);
    } else {
        SECOidTag sigTag = SECOID_GetAlgorithmTag(&svrCert->signature);

        switch (sigTag) {
            case SEC_OID_PKCS1_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION:
            case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION:
                ssl3_DisableECCSuites(ss, ecdh_ecdsa_suites);
                break;
            case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE:
            case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE:
            case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE:
            case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE:
            case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE:
            case SEC_OID_ANSIX962_ECDSA_SIGNATURE_RECOMMENDED_DIGEST:
            case SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST:
                ssl3_DisableECCSuites(ss, ecdh_rsa_suites);
                break;
            default:
                ssl3_DisableECCSuites(ss, ecdh_suites);
                break;
        }
    }
}

/* Ask: is ANY ECC cipher suite enabled on this socket? */
/* Order(N^2).  Yuk.  Also, this ignores export policy. */
PRBool
ssl3_IsECCEnabled(sslSocket *ss)
{
    const ssl3CipherSuite *suite;
    PK11SlotInfo *slot;

    /* make sure we can do ECC */
    slot = PK11_GetBestSlot(CKM_ECDH1_DERIVE, ss->pkcs11PinArg);
    if (!slot) {
        return PR_FALSE;
    }
    PK11_FreeSlot(slot);

    /* make sure an ECC cipher is enabled */
    for (suite = ecSuites; *suite; ++suite) {
        PRBool enabled = PR_FALSE;
        SECStatus rv = ssl3_CipherPrefGet(ss, *suite, &enabled);

        PORT_Assert(rv == SECSuccess); /* else is coding error */
        if (rv == SECSuccess && enabled)
            return PR_TRUE;
    }
    return PR_FALSE;
}

#define BE(n) 0, n

/* Prefabricated TLS client hello extension, Elliptic Curves List,
 * offers only 3 curves, the Suite B curves, 23-25
 */
static const PRUint8 suiteBECList[] = {
    23, 24, 25
};

/* Prefabricated TLS client hello extension, Elliptic Curves List,
 * offers curves 1-25.
 */
/* clang-format off */
static const PRUint8 tlsECList[] = {
     1,  2,  3,  4,  5,  6,  7,  8,
     9, 10, 11, 12, 13, 14, 15, 16,
    17, 18, 19, 20, 21, 22, 23, 24,
    25
};
/* clang-format on */

static const PRUint8 ecPtFmt[6] = {
    BE(11), /* Extension type */
    BE(2),  /* octets that follow */
    1,      /* octets that follow */
    0       /* uncompressed type only */
};

/* This function already presumes we can do ECC, ssl3_IsECCEnabled must be
 * called before this function. It looks to see if we have a token which
 * is capable of doing smaller than SuiteB curves. If the token can, we
 * presume the token can do the whole SSL suite of curves. If it can't we
 * presume the token that allowed ECC to be enabled can only do suite B
 * curves. */
static PRBool
ssl3_SuiteBOnly(sslSocket *ss)
{
    /* See if we can support small curves (like 163). If not, assume we can
     * only support Suite-B curves (P-256, P-384, P-521). */
    PK11SlotInfo *slot =
        PK11_GetBestSlotWithAttributes(CKM_ECDH1_DERIVE, 0, 163,
                                       ss ? ss->pkcs11PinArg : NULL);

    if (!slot) {
        /* nope, presume we can only do suite B */
        return PR_TRUE;
    }
    /* we can, presume we can do all curves */
    PK11_FreeSlot(slot);
    return PR_FALSE;
}

#define APPEND_CURVE(CURVE_ID)                                       \
    if ((NSS_GetAlgorithmPolicy(ecName2OIDTag[CURVE_ID], &policy) == \
         SECFailure) ||                                              \
        (policy & NSS_USE_ALG_IN_SSL_KX)) {                          \
        enabledCurves[pos++] = 0;                                    \
        enabledCurves[pos++] = CURVE_ID;                             \
    }

/* Send our "canned" (precompiled) Supported Elliptic Curves extension,
 * which says that we support all TLS-defined named curves.
 */
PRInt32
ssl3_SendSupportedCurvesXtn(
    sslSocket *ss,
    PRBool append,
    PRUint32 maxBytes)
{
    unsigned char enabledCurves[64];
    PRUint32 policy;
    PRInt32 extension_length;
    PRInt32 ecListSize = 0;
    unsigned int pos = 0;
    unsigned int i;

    if (!ss || !ssl3_IsECCEnabled(ss))
        return 0;

    PORT_Assert(sizeof(enabledCurves) > sizeof(tlsECList) * 2);
    if (ssl3_SuiteBOnly(ss)) {
        for (i = 0; i < sizeof(suiteBECList); i++) {
            APPEND_CURVE(suiteBECList[i]);
        }
        ecListSize = pos;
    } else {
        for (i = 0; i < sizeof(tlsECList); i++) {
            APPEND_CURVE(tlsECList[i]);
        }
        ecListSize = pos;
    }
    extension_length =
        2 /* extension type */ +
        2 /* extension length */ +
        2 /* elliptic curves length */ +
        ecListSize;

    if (maxBytes < (PRUint32)extension_length) {
        return 0;
    }

    if (append) {
        SECStatus rv;
        rv = ssl3_AppendHandshakeNumber(ss, ssl_elliptic_curves_xtn, 2);
        if (rv != SECSuccess)
            return -1;
        rv = ssl3_AppendHandshakeNumber(ss, extension_length - 4, 2);
        if (rv != SECSuccess)
            return -1;
        rv = ssl3_AppendHandshakeVariable(ss, enabledCurves, ecListSize, 2);
        if (rv != SECSuccess)
            return -1;
        if (!ss->sec.isServer) {
            TLSExtensionData *xtnData = &ss->xtnData;
            xtnData->advertised[xtnData->numAdvertised++] =
                ssl_elliptic_curves_xtn;
        }
    }
    return extension_length;
}

PRUint32
ssl3_GetSupportedECCurveMask(sslSocket *ss)
{
    int i;
    PRUint32 curves = 0;
    PRUint32 policyFlags = 0;

    PORT_Assert(ec_pastLastName < sizeof(PRUint32) * 8);

    if (ssl3_SuiteBOnly(ss)) {
        curves = SSL3_SUITE_B_SUPPORTED_CURVES_MASK;
    } else {
        curves = SSL3_ALL_SUPPORTED_CURVES_MASK;
    }

    for (i = ec_noName + 1; i < ec_pastLastName; i++) {
        PRUint32 curve_bit = (1U << i);
        if ((curves & curve_bit) &&
            (NSS_GetAlgorithmPolicy(ecName2OIDTag[i], &policyFlags) ==
             SECSuccess) &&
            !(policyFlags & NSS_USE_ALG_IN_SSL_KX)) {
            curves &= ~curve_bit;
        }
    }
    return curves;
}

/* Send our "canned" (precompiled) Supported Point Formats extension,
 * which says that we only support uncompressed points.
 */
PRInt32
ssl3_SendSupportedPointFormatsXtn(
    sslSocket *ss,
    PRBool append,
    PRUint32 maxBytes)
{
    if (!ss || !ssl3_IsECCEnabled(ss))
        return 0;
    if (append && maxBytes >= (sizeof ecPtFmt)) {
        SECStatus rv = ssl3_AppendHandshake(ss, ecPtFmt, (sizeof ecPtFmt));
        if (rv != SECSuccess)
            return -1;
        if (!ss->sec.isServer) {
            TLSExtensionData *xtnData = &ss->xtnData;
            xtnData->advertised[xtnData->numAdvertised++] =
                ssl_ec_point_formats_xtn;
        }
    }
    return (sizeof ecPtFmt);
}

/* Just make sure that the remote client supports uncompressed points,
 * Since that is all we support.  Disable ECC cipher suites if it doesn't.
 */
SECStatus
ssl3_HandleSupportedPointFormatsXtn(sslSocket *ss, PRUint16 ex_type,
                                    SECItem *data)
{
    int i;

    if (data->len < 2 || data->len > 255 || !data->data ||
        data->len != (unsigned int)data->data[0] + 1) {
        return ssl3_DecodeError(ss);
    }
    for (i = data->len; --i > 0;) {
        if (data->data[i] == 0) {
            /* indicate that we should send a reply */
            SECStatus rv;
            rv = ssl3_RegisterServerHelloExtensionSender(ss, ex_type,
                                                         &ssl3_SendSupportedPointFormatsXtn);
            return rv;
        }
    }

    /* evil client doesn't support uncompressed */
    ssl3_DisableECCSuites(ss, ecSuites);
    return SECSuccess;
}

#define SSL3_GET_SERVER_PUBLICKEY(sock, type)                                          \
    (ss->serverCerts[type].serverKeyPair ? ss->serverCerts[type].serverKeyPair->pubKey \
                                         : NULL)

/* Extract the TLS curve name for the public key in our EC server cert. */
ECName
ssl3_GetSvrCertCurveName(sslSocket *ss)
{
    SECKEYPublicKey *srvPublicKey;
    ECName ec_curve = ec_noName;

    srvPublicKey = SSL3_GET_SERVER_PUBLICKEY(ss, kt_ecdh);
    if (srvPublicKey) {
        ec_curve = ssl3_PubKey2ECName(srvPublicKey);
    }
    return ec_curve;
}

/* Ensure that the curve in our server cert is one of the ones supported
 * by the remote client, and disable all ECC cipher suites if not.
 */
SECStatus
ssl3_HandleSupportedCurvesXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
{
    PRInt32 list_len;
    PRUint32 peerCurves = 0;
    PRUint32 mutualCurves = 0;
    PRUint16 svrCertCurveName;

    if (!data->data || data->len < 4) {
        (void)ssl3_DecodeError(ss);
        return SECFailure;
    }

    /* get the length of elliptic_curve_list */
    list_len = ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
    if (list_len < 0 || data->len != list_len || (data->len % 2) != 0) {
        (void)ssl3_DecodeError(ss);
        return SECFailure;
    }
    /* build bit vector of peer's supported curve names */
    while (data->len) {
        PRInt32 curve_name =
            ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
        if (curve_name < 0) {
            return SECFailure; /* fatal alert already sent */
        }
        if (curve_name > ec_noName && curve_name < ec_pastLastName) {
            peerCurves |= (1U << curve_name);
        }
    }
    /* What curves do we support in common? */
    mutualCurves = ss->ssl3.hs.negotiatedECCurves &= peerCurves;
    if (!mutualCurves) {
        /* no mutually supported EC Curves, disable ECC */
        ssl3_DisableECCSuites(ss, ecSuites);
        return SECSuccess;
    }

    /* if our ECC cert doesn't use one of these supported curves,
     * disable ECC cipher suites that require an ECC cert.
     */
    svrCertCurveName = ssl3_GetSvrCertCurveName(ss);
    if (svrCertCurveName != ec_noName &&
        (mutualCurves & (1U << svrCertCurveName)) != 0) {
        return SECSuccess;
    }
    /* Our EC cert doesn't contain a mutually supported curve.
     * Disable all ECC cipher suites that require an EC cert
     */
    ssl3_DisableECCSuites(ss, ecdh_ecdsa_suites);
    ssl3_DisableECCSuites(ss, ecdhe_ecdsa_suites);
    return SECSuccess;
}

#endif /* NSS_DISABLE_ECC */
