1003 lines
31 KiB
C
1003 lines
31 KiB
C
/* -*- 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 "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 "ssl3ext.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>
|
|
|
|
SECStatus
|
|
ssl_NamedGroup2ECParams(PLArenaPool *arena, const sslNamedGroupDef *ecGroup,
|
|
SECKEYECParams *params)
|
|
{
|
|
SECOidData *oidData = NULL;
|
|
|
|
if (!params) {
|
|
PORT_Assert(0);
|
|
PORT_SetError(SEC_ERROR_INVALID_ARGS);
|
|
return SECFailure;
|
|
}
|
|
|
|
if (!ecGroup || ecGroup->keaType != ssl_kea_ecdh ||
|
|
(oidData = SECOID_FindOIDByTag(ecGroup->oidTag)) == NULL) {
|
|
PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
|
|
return SECFailure;
|
|
}
|
|
|
|
if (SECITEM_AllocItem(arena, params, (2 + oidData->oid.len)) == NULL) {
|
|
PORT_SetError(SEC_ERROR_NO_MEMORY);
|
|
return SECFailure;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
const sslNamedGroupDef *
|
|
ssl_ECPubKey2NamedGroup(const SECKEYPublicKey *pubKey)
|
|
{
|
|
SECItem oid = { siBuffer, NULL, 0 };
|
|
SECOidData *oidData = NULL;
|
|
PRUint32 policyFlags = 0;
|
|
unsigned int i;
|
|
const SECKEYECParams *params;
|
|
|
|
if (pubKey->keyType != ecKey) {
|
|
PORT_Assert(0);
|
|
return NULL;
|
|
}
|
|
|
|
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 NULL;
|
|
oid.len = params->len - 2;
|
|
oid.data = params->data + 2;
|
|
if ((oidData = SECOID_FindOID(&oid)) == NULL)
|
|
return NULL;
|
|
if ((NSS_GetAlgorithmPolicy(oidData->offset, &policyFlags) ==
|
|
SECSuccess) &&
|
|
!(policyFlags & NSS_USE_ALG_IN_SSL_KX)) {
|
|
return NULL;
|
|
}
|
|
for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
|
|
if (ssl_named_groups[i].oidTag == oidData->offset) {
|
|
return &ssl_named_groups[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Caller must set hiLevel error code. */
|
|
static SECStatus
|
|
ssl3_ComputeECDHKeyHash(SSLHashType hashAlg,
|
|
SECItem ec_params, SECItem server_ecpoint,
|
|
PRUint8 *client_rand, PRUint8 *server_rand,
|
|
SSL3Hashes *hashes)
|
|
{
|
|
PRUint8 *hashBuf;
|
|
PRUint8 *pBuf;
|
|
SECStatus rv = SECSuccess;
|
|
unsigned int bufLen;
|
|
/*
|
|
* 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);
|
|
|
|
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;
|
|
const sslNamedGroupDef *groupDef;
|
|
sslEphemeralKeyPair *keyPair = NULL;
|
|
SECKEYPublicKey *pubKey;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
|
|
|
|
isTLS = (PRBool)(ss->version > SSL_LIBRARY_VERSION_3_0);
|
|
isTLS12 = (PRBool)(ss->version >= SSL_LIBRARY_VERSION_TLS_1_2);
|
|
|
|
/* Generate ephemeral EC keypair */
|
|
if (svrPubKey->keyType != ecKey) {
|
|
PORT_SetError(SEC_ERROR_BAD_KEY);
|
|
goto loser;
|
|
}
|
|
groupDef = ssl_ECPubKey2NamedGroup(svrPubKey);
|
|
if (!groupDef) {
|
|
PORT_SetError(SEC_ERROR_BAD_KEY);
|
|
goto loser;
|
|
}
|
|
ss->sec.keaGroup = groupDef;
|
|
rv = ssl_CreateECDHEphemeralKeyPair(ss, groupDef, &keyPair);
|
|
if (rv != SECSuccess) {
|
|
ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
|
|
goto loser;
|
|
}
|
|
|
|
pubKey = keyPair->keys->pubKey;
|
|
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(keyPair->keys->privKey, svrPubKey,
|
|
PR_FALSE, NULL, NULL, CKM_ECDH1_DERIVE, target,
|
|
CKA_DERIVE, 0, CKD_NULL, NULL, NULL);
|
|
|
|
if (pms == NULL) {
|
|
(void)SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
|
|
ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
|
|
goto loser;
|
|
}
|
|
|
|
rv = ssl3_AppendHandshakeHeader(ss, ssl_hs_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);
|
|
|
|
if (rv != SECSuccess) {
|
|
goto loser; /* err set by ssl3_AppendHandshake* */
|
|
}
|
|
|
|
rv = ssl3_InitPendingCipherSpecs(ss, pms, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
|
|
goto loser;
|
|
}
|
|
|
|
PK11_FreeSymKey(pms);
|
|
ssl_FreeEphemeralKeyPair(keyPair);
|
|
return SECSuccess;
|
|
|
|
loser:
|
|
if (pms)
|
|
PK11_FreeSymKey(pms);
|
|
if (keyPair)
|
|
ssl_FreeEphemeralKeyPair(keyPair);
|
|
return SECFailure;
|
|
}
|
|
|
|
/*
|
|
** Called from ssl3_HandleClientKeyExchange()
|
|
*/
|
|
SECStatus
|
|
ssl3_HandleECDHClientKeyExchange(sslSocket *ss, PRUint8 *b,
|
|
PRUint32 length,
|
|
sslKeyPair *serverKeyPair)
|
|
{
|
|
PK11SymKey *pms;
|
|
SECStatus rv;
|
|
SECKEYPublicKey clntPubKey;
|
|
CK_MECHANISM_TYPE target;
|
|
PRBool isTLS, isTLS12;
|
|
int errCode = SSL_ERROR_RX_MALFORMED_CLIENT_KEY_EXCH;
|
|
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
|
|
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
|
|
|
|
clntPubKey.keyType = ecKey;
|
|
clntPubKey.u.ec.DEREncodedParams.len =
|
|
serverKeyPair->pubKey->u.ec.DEREncodedParams.len;
|
|
clntPubKey.u.ec.DEREncodedParams.data =
|
|
serverKeyPair->pubKey->u.ec.DEREncodedParams.data;
|
|
clntPubKey.u.ec.encoding = ECPoint_Undefined;
|
|
|
|
rv = ssl3_ConsumeHandshakeVariable(ss, &clntPubKey.u.ec.publicValue,
|
|
1, &b, &length);
|
|
if (rv != SECSuccess) {
|
|
PORT_SetError(errCode);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* we have to catch the case when the client's public key has length 0. */
|
|
if (!clntPubKey.u.ec.publicValue.len) {
|
|
(void)SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
|
|
PORT_SetError(errCode);
|
|
return SECFailure;
|
|
}
|
|
|
|
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(serverKeyPair->privKey, &clntPubKey,
|
|
PR_FALSE, NULL, NULL,
|
|
CKM_ECDH1_DERIVE, target, CKA_DERIVE, 0,
|
|
CKD_NULL, NULL, NULL);
|
|
|
|
if (pms == NULL) {
|
|
/* last gasp. */
|
|
errCode = ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
|
|
PORT_SetError(errCode);
|
|
return SECFailure;
|
|
}
|
|
|
|
rv = ssl3_InitPendingCipherSpecs(ss, pms, PR_TRUE);
|
|
PK11_FreeSymKey(pms);
|
|
if (rv != SECSuccess) {
|
|
/* error code set by ssl3_InitPendingCipherSpec */
|
|
return SECFailure;
|
|
}
|
|
ss->sec.keaGroup = ssl_ECPubKey2NamedGroup(&clntPubKey);
|
|
return SECSuccess;
|
|
}
|
|
|
|
/*
|
|
** Take an encoded key share and make a public key out of it.
|
|
*/
|
|
SECStatus
|
|
ssl_ImportECDHKeyShare(SECKEYPublicKey *peerKey,
|
|
PRUint8 *b, PRUint32 length,
|
|
const sslNamedGroupDef *ecGroup)
|
|
{
|
|
SECStatus rv;
|
|
SECItem ecPoint = { siBuffer, NULL, 0 };
|
|
|
|
if (!length) {
|
|
PORT_SetError(SSL_ERROR_RX_MALFORMED_ECDHE_KEY_SHARE);
|
|
return SECFailure;
|
|
}
|
|
|
|
/* Fail if the ec point uses compressed representation */
|
|
if (b[0] != EC_POINT_FORM_UNCOMPRESSED &&
|
|
ecGroup->name != ssl_grp_ec_curve25519) {
|
|
PORT_SetError(SEC_ERROR_UNSUPPORTED_EC_POINT_FORM);
|
|
return SECFailure;
|
|
}
|
|
|
|
peerKey->keyType = ecKey;
|
|
/* Set up the encoded params */
|
|
rv = ssl_NamedGroup2ECParams(peerKey->arena, ecGroup,
|
|
&peerKey->u.ec.DEREncodedParams);
|
|
if (rv != SECSuccess) {
|
|
ssl_MapLowLevelError(SSL_ERROR_RX_MALFORMED_ECDHE_KEY_SHARE);
|
|
return SECFailure;
|
|
}
|
|
peerKey->u.ec.encoding = ECPoint_Undefined;
|
|
|
|
/* copy publicValue in peerKey */
|
|
ecPoint.data = b;
|
|
ecPoint.len = length;
|
|
|
|
rv = SECITEM_CopyItem(peerKey->arena, &peerKey->u.ec.publicValue, &ecPoint);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
const sslNamedGroupDef *
|
|
ssl_GetECGroupWithStrength(sslSocket *ss, unsigned int requiredECCbits)
|
|
{
|
|
int i;
|
|
|
|
PORT_Assert(ss);
|
|
|
|
for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
|
|
const sslNamedGroupDef *group = ss->namedGroupPreferences[i];
|
|
if (group && group->keaType == ssl_kea_ecdh &&
|
|
group->bits >= requiredECCbits) {
|
|
return group;
|
|
}
|
|
}
|
|
|
|
PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
|
|
return NULL;
|
|
}
|
|
|
|
/* Find the "weakest link". Get the strength of the signature and symmetric
|
|
* keys and choose a curve based on the weakest of those two. */
|
|
const sslNamedGroupDef *
|
|
ssl_GetECGroupForServerSocket(sslSocket *ss)
|
|
{
|
|
const sslServerCert *cert = ss->sec.serverCert;
|
|
unsigned int certKeySize;
|
|
const ssl3BulkCipherDef *bulkCipher;
|
|
unsigned int requiredECCbits;
|
|
|
|
PORT_Assert(cert);
|
|
if (!cert || !cert->serverKeyPair || !cert->serverKeyPair->pubKey) {
|
|
PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
|
|
return NULL;
|
|
}
|
|
|
|
if (SSL_CERT_IS(cert, ssl_auth_rsa_sign) ||
|
|
SSL_CERT_IS(cert, ssl_auth_rsa_pss)) {
|
|
certKeySize = SECKEY_PublicKeyStrengthInBits(cert->serverKeyPair->pubKey);
|
|
certKeySize = SSL_RSASTRENGTH_TO_ECSTRENGTH(certKeySize);
|
|
} else if (SSL_CERT_IS_EC(cert)) {
|
|
/* We won't select a certificate unless the named curve has been
|
|
* negotiated (or supported_curves was absent), double check that. */
|
|
PORT_Assert(cert->namedCurve->keaType == ssl_kea_ecdh);
|
|
PORT_Assert(ssl_NamedGroupEnabled(ss, cert->namedCurve));
|
|
if (!ssl_NamedGroupEnabled(ss, cert->namedCurve)) {
|
|
return NULL;
|
|
}
|
|
certKeySize = cert->namedCurve->bits;
|
|
} else {
|
|
PORT_Assert(0);
|
|
return NULL;
|
|
}
|
|
bulkCipher = ssl_GetBulkCipherDef(ss->ssl3.hs.suite_def);
|
|
requiredECCbits = bulkCipher->key_size * BPB * 2;
|
|
PORT_Assert(requiredECCbits ||
|
|
ss->ssl3.hs.suite_def->bulk_cipher_alg == cipher_null);
|
|
if (requiredECCbits > certKeySize) {
|
|
requiredECCbits = certKeySize;
|
|
}
|
|
|
|
return ssl_GetECGroupWithStrength(ss, requiredECCbits);
|
|
}
|
|
|
|
/* ssl_CreateECDHEPrivateKey is a variant of SECKEY_CreateECPrivateKey that
|
|
* tries to use CKM_NSS_ECDHE_NO_PAIRWISE_CHECK_KEY_PAIR_GEN instead of CKM_EC_KEY_PAIR_GEN.
|
|
* Softoken's implementation of CKM_EC_KEY_PAIR_GEN always performs the
|
|
* SP800-56A pairwise consistency check, even though Section 5.6.2.1.4 of that
|
|
* document only requires that check for static keys. The
|
|
* CKM_NSS_ECDHE_NO_PAIRWISE_CHECK_KEY_PAIR_GEN mechanism skips the pairwise consistency
|
|
* check, but is otherwise identical to CKM_EC_KEY_PAIR_GEN. */
|
|
static SECKEYPrivateKey *
|
|
ssl_CreateECDHEPrivateKey(SECKEYECParams *param, SECKEYPublicKey **pubk, void *cx)
|
|
{
|
|
SECKEYPrivateKey *privk = NULL;
|
|
CK_MECHANISM_TYPE type = CKM_NSS_ECDHE_NO_PAIRWISE_CHECK_KEY_PAIR_GEN;
|
|
|
|
PK11SlotInfo *slot = PK11_GetInternalSlot();
|
|
if (!slot || !PK11_DoesMechanism(slot, type) || PK11_IsFIPS()) {
|
|
if (slot) {
|
|
PK11_FreeSlot(slot);
|
|
}
|
|
return SECKEY_CreateECPrivateKey(param, pubk, cx);
|
|
}
|
|
|
|
privk = PK11_GenerateKeyPairWithOpFlags(slot, type, param, pubk,
|
|
PK11_ATTR_SESSION | PK11_ATTR_INSENSITIVE | PK11_ATTR_PUBLIC,
|
|
CKF_DERIVE, CKF_DERIVE, cx);
|
|
if (!privk) {
|
|
privk = PK11_GenerateKeyPairWithOpFlags(slot, type, param, pubk,
|
|
PK11_ATTR_SESSION | PK11_ATTR_SENSITIVE | PK11_ATTR_PRIVATE,
|
|
CKF_DERIVE, CKF_DERIVE, cx);
|
|
}
|
|
|
|
PK11_FreeSlot(slot);
|
|
return privk;
|
|
}
|
|
|
|
/* Create an ECDHE key pair for a given curve */
|
|
SECStatus
|
|
ssl_CreateECDHEphemeralKeyPair(const sslSocket *ss,
|
|
const sslNamedGroupDef *ecGroup,
|
|
sslEphemeralKeyPair **keyPair)
|
|
{
|
|
SECKEYPrivateKey *privKey = NULL;
|
|
SECKEYPublicKey *pubKey = NULL;
|
|
SECKEYECParams ecParams = { siBuffer, NULL, 0 };
|
|
sslEphemeralKeyPair *pair;
|
|
|
|
if (ssl_NamedGroup2ECParams(NULL, ecGroup, &ecParams) != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
privKey = ssl_CreateECDHEPrivateKey(&ecParams, &pubKey, ss->pkcs11PinArg);
|
|
SECITEM_FreeItem(&ecParams, PR_FALSE);
|
|
|
|
if (!privKey || !pubKey ||
|
|
!(pair = ssl_NewEphemeralKeyPair(ecGroup, privKey, pubKey))) {
|
|
if (privKey) {
|
|
SECKEY_DestroyPrivateKey(privKey);
|
|
}
|
|
if (pubKey) {
|
|
SECKEY_DestroyPublicKey(pubKey);
|
|
}
|
|
ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
|
|
return SECFailure;
|
|
}
|
|
|
|
*keyPair = pair;
|
|
SSL_TRC(50, ("%d: SSL[%d]: Create ECDH ephemeral key %d",
|
|
SSL_GETPID(), ss ? ss->fd : NULL, ecGroup->name));
|
|
PRINT_BUF(50, (ss, "Public Key", pubKey->u.ec.publicValue.data,
|
|
pubKey->u.ec.publicValue.len));
|
|
#ifdef TRACE
|
|
if (ssl_trace >= 50) {
|
|
SECItem d = { siBuffer, NULL, 0 };
|
|
SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, privKey,
|
|
CKA_VALUE, &d);
|
|
if (rv == SECSuccess) {
|
|
PRINT_BUF(50, (ss, "Private Key", d.data, d.len));
|
|
SECITEM_FreeItem(&d, PR_FALSE);
|
|
} else {
|
|
SSL_TRC(50, ("Error extracting private key"));
|
|
}
|
|
}
|
|
#endif
|
|
return SECSuccess;
|
|
}
|
|
|
|
SECStatus
|
|
ssl3_HandleECDHServerKeyExchange(sslSocket *ss, PRUint8 *b, PRUint32 length)
|
|
{
|
|
PLArenaPool *arena = NULL;
|
|
SECKEYPublicKey *peerKey = NULL;
|
|
PRBool isTLS;
|
|
SECStatus rv;
|
|
int errCode = SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH;
|
|
SSL3AlertDescription desc = illegal_parameter;
|
|
SSL3Hashes hashes;
|
|
SECItem signature = { siBuffer, NULL, 0 };
|
|
SSLHashType hashAlg;
|
|
SSLSignatureScheme sigScheme;
|
|
|
|
SECItem ec_params = { siBuffer, NULL, 0 };
|
|
SECItem ec_point = { siBuffer, NULL, 0 };
|
|
unsigned char paramBuf[3];
|
|
const sslNamedGroupDef *ecGroup;
|
|
|
|
isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
|
|
|
|
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) {
|
|
errCode = SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
|
|
desc = handshake_failure;
|
|
goto alert_loser;
|
|
}
|
|
ecGroup = ssl_LookupNamedGroup(ec_params.data[1] << 8 | ec_params.data[2]);
|
|
if (!ecGroup || ecGroup->keaType != ssl_kea_ecdh) {
|
|
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 provided point has length 0. */
|
|
if (!ec_point.len) {
|
|
/* desc and errCode are initialized already */
|
|
goto alert_loser;
|
|
}
|
|
|
|
/* Fail if the ec point is not uncompressed for any curve that's not 25519. */
|
|
if (ecGroup->name != ssl_grp_ec_curve25519 &&
|
|
ec_point.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
|
|
errCode = SEC_ERROR_UNSUPPORTED_EC_POINT_FORM;
|
|
desc = handshake_failure;
|
|
goto alert_loser;
|
|
}
|
|
|
|
PORT_Assert(ss->ssl3.prSpec->version <= SSL_LIBRARY_VERSION_TLS_1_2);
|
|
if (ss->ssl3.prSpec->version == SSL_LIBRARY_VERSION_TLS_1_2) {
|
|
rv = ssl_ConsumeSignatureScheme(ss, &b, &length, &sigScheme);
|
|
if (rv != SECSuccess) {
|
|
errCode = PORT_GetError();
|
|
goto alert_loser; /* malformed or unsupported. */
|
|
}
|
|
rv = ssl_CheckSignatureSchemeConsistency(
|
|
ss, sigScheme, &ss->sec.peerCert->subjectPublicKeyInfo);
|
|
if (rv != SECSuccess) {
|
|
errCode = PORT_GetError();
|
|
goto alert_loser;
|
|
}
|
|
hashAlg = ssl_SignatureSchemeToHashType(sigScheme);
|
|
} else {
|
|
/* Use ssl_hash_none to represent the MD5+SHA1 combo. */
|
|
hashAlg = ssl_hash_none;
|
|
sigScheme = ssl_sig_none;
|
|
}
|
|
|
|
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(hashAlg, ec_params, ec_point,
|
|
ss->ssl3.hs.client_random,
|
|
ss->ssl3.hs.server_random,
|
|
&hashes);
|
|
|
|
if (rv != SECSuccess) {
|
|
errCode =
|
|
ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
|
|
goto alert_loser;
|
|
}
|
|
rv = ssl3_VerifySignedHashes(ss, sigScheme, &hashes, &signature);
|
|
if (rv != SECSuccess) {
|
|
errCode =
|
|
ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
|
|
goto alert_loser;
|
|
}
|
|
|
|
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (arena == NULL) {
|
|
errCode = SEC_ERROR_NO_MEMORY;
|
|
goto loser;
|
|
}
|
|
|
|
peerKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
|
|
if (peerKey == NULL) {
|
|
errCode = SEC_ERROR_NO_MEMORY;
|
|
goto loser;
|
|
}
|
|
peerKey->arena = arena;
|
|
|
|
/* create public key from point data */
|
|
rv = ssl_ImportECDHKeyShare(peerKey, ec_point.data, ec_point.len,
|
|
ecGroup);
|
|
if (rv != SECSuccess) {
|
|
/* error code is set */
|
|
desc = handshake_failure;
|
|
errCode = PORT_GetError();
|
|
goto alert_loser;
|
|
}
|
|
peerKey->pkcs11Slot = NULL;
|
|
peerKey->pkcs11ID = CK_INVALID_HANDLE;
|
|
|
|
ss->sec.peerKey = peerKey;
|
|
return SECSuccess;
|
|
|
|
alert_loser:
|
|
(void)SSL3_SendAlert(ss, alert_fatal, desc);
|
|
loser:
|
|
if (arena) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
PORT_SetError(errCode);
|
|
return SECFailure;
|
|
}
|
|
|
|
SECStatus
|
|
ssl3_SendECDHServerKeyExchange(sslSocket *ss)
|
|
{
|
|
SECStatus rv = SECFailure;
|
|
int length;
|
|
PRBool isTLS12;
|
|
SECItem signed_hash = { siBuffer, NULL, 0 };
|
|
SSLHashType hashAlg;
|
|
SSL3Hashes hashes;
|
|
|
|
SECItem ec_params = { siBuffer, NULL, 0 };
|
|
unsigned char paramBuf[3];
|
|
const sslNamedGroupDef *ecGroup;
|
|
sslEphemeralKeyPair *keyPair;
|
|
SECKEYPublicKey *pubKey;
|
|
|
|
/* Generate ephemeral ECDH key pair and send the public key */
|
|
ecGroup = ssl_GetECGroupForServerSocket(ss);
|
|
if (!ecGroup) {
|
|
goto loser;
|
|
}
|
|
|
|
PORT_Assert(PR_CLIST_IS_EMPTY(&ss->ephemeralKeyPairs));
|
|
if (ss->opt.reuseServerECDHEKey) {
|
|
rv = ssl_CreateStaticECDHEKey(ss, ecGroup);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
keyPair = (sslEphemeralKeyPair *)PR_NEXT_LINK(&ss->ephemeralKeyPairs);
|
|
} else {
|
|
rv = ssl_CreateECDHEphemeralKeyPair(ss, ecGroup, &keyPair);
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
PR_APPEND_LINK(&keyPair->link, &ss->ephemeralKeyPairs);
|
|
}
|
|
|
|
PORT_Assert(keyPair);
|
|
if (!keyPair) {
|
|
PORT_SetError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
|
|
return SECFailure;
|
|
}
|
|
|
|
ec_params.len = sizeof(paramBuf);
|
|
ec_params.data = paramBuf;
|
|
PORT_Assert(keyPair->group);
|
|
PORT_Assert(keyPair->group->keaType == ssl_kea_ecdh);
|
|
ec_params.data[0] = ec_type_named;
|
|
ec_params.data[1] = keyPair->group->name >> 8;
|
|
ec_params.data[2] = keyPair->group->name & 0xff;
|
|
|
|
pubKey = keyPair->keys->pubKey;
|
|
if (ss->version == SSL_LIBRARY_VERSION_TLS_1_2) {
|
|
hashAlg = ssl_SignatureSchemeToHashType(ss->ssl3.hs.signatureScheme);
|
|
} else {
|
|
/* Use ssl_hash_none to represent the MD5+SHA1 combo. */
|
|
hashAlg = ssl_hash_none;
|
|
}
|
|
rv = ssl3_ComputeECDHKeyHash(hashAlg, ec_params,
|
|
pubKey->u.ec.publicValue,
|
|
ss->ssl3.hs.client_random,
|
|
ss->ssl3.hs.server_random,
|
|
&hashes);
|
|
if (rv != SECSuccess) {
|
|
ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
|
|
goto loser;
|
|
}
|
|
|
|
isTLS12 = (PRBool)(ss->version >= SSL_LIBRARY_VERSION_TLS_1_2);
|
|
|
|
rv = ssl3_SignHashes(ss, &hashes,
|
|
ss->sec.serverCert->serverKeyPair->privKey, &signed_hash);
|
|
if (rv != SECSuccess) {
|
|
goto loser; /* ssl3_SignHashes has set err. */
|
|
}
|
|
|
|
length = ec_params.len +
|
|
1 + pubKey->u.ec.publicValue.len +
|
|
(isTLS12 ? 2 : 0) + 2 + signed_hash.len;
|
|
|
|
rv = ssl3_AppendHandshakeHeader(ss, ssl_hs_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, pubKey->u.ec.publicValue.data,
|
|
pubKey->u.ec.publicValue.len, 1);
|
|
if (rv != SECSuccess) {
|
|
goto loser; /* err set by AppendHandshake. */
|
|
}
|
|
|
|
if (isTLS12) {
|
|
rv = ssl3_AppendHandshakeNumber(ss, ss->ssl3.hs.signatureScheme, 2);
|
|
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;
|
|
}
|
|
|
|
/* List of all ECC cipher suites */
|
|
static const ssl3CipherSuite ssl_all_ec_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,
|
|
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
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_GCM_SHA384,
|
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
|
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 */
|
|
};
|
|
|
|
static const ssl3CipherSuite ssl_dhe_suites[] = {
|
|
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
|
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
|
|
TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
|
TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
|
|
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
|
|
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
|
|
TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
|
TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
|
TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
|
TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
|
|
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
|
|
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
|
|
TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
|
TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
|
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
|
|
TLS_DHE_DSS_WITH_RC4_128_SHA,
|
|
TLS_DHE_RSA_WITH_DES_CBC_SHA,
|
|
TLS_DHE_DSS_WITH_DES_CBC_SHA,
|
|
0
|
|
};
|
|
|
|
/* Order(N^2). Yuk. */
|
|
static PRBool
|
|
ssl_IsSuiteEnabled(const sslSocket *ss, const ssl3CipherSuite *list)
|
|
{
|
|
const ssl3CipherSuite *suite;
|
|
|
|
for (suite = list; *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;
|
|
}
|
|
|
|
/* Ask: is ANY ECC cipher suite enabled on this socket? */
|
|
PRBool
|
|
ssl_IsECCEnabled(const sslSocket *ss)
|
|
{
|
|
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 */
|
|
return ssl_IsSuiteEnabled(ss, ssl_all_ec_suites);
|
|
}
|
|
|
|
PRBool
|
|
ssl_IsDHEEnabled(const sslSocket *ss)
|
|
{
|
|
return ssl_IsSuiteEnabled(ss, ssl_dhe_suites);
|
|
}
|
|
|
|
/* Send our Supported Groups extension. */
|
|
SECStatus
|
|
ssl_SendSupportedGroupsXtn(const sslSocket *ss, TLSExtensionData *xtnData,
|
|
sslBuffer *buf, PRBool *added)
|
|
{
|
|
unsigned int i;
|
|
PRBool ec;
|
|
PRBool ec_hybrid = PR_FALSE;
|
|
PRBool ff = PR_FALSE;
|
|
PRBool found = PR_FALSE;
|
|
SECStatus rv;
|
|
unsigned int lengthOffset;
|
|
|
|
/* We only send FF supported groups if we require DH named groups
|
|
* or if TLS 1.3 is a possibility. */
|
|
if (ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
ec = ssl_IsECCEnabled(ss);
|
|
if (ss->opt.requireDHENamedGroups) {
|
|
ff = ssl_IsDHEEnabled(ss);
|
|
}
|
|
if (!ec && !ff) {
|
|
return SECSuccess;
|
|
}
|
|
} else {
|
|
ec = ec_hybrid = ff = PR_TRUE;
|
|
}
|
|
|
|
/* Mark the location of the length. */
|
|
rv = sslBuffer_Skip(buf, 2, &lengthOffset);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
|
|
const sslNamedGroupDef *group = ss->namedGroupPreferences[i];
|
|
if (!group) {
|
|
continue;
|
|
}
|
|
if (group->keaType == ssl_kea_ecdh && !ec) {
|
|
continue;
|
|
}
|
|
if (group->keaType == ssl_kea_ecdh_hybrid && !ec_hybrid) {
|
|
continue;
|
|
}
|
|
if (group->keaType == ssl_kea_dh && !ff) {
|
|
continue;
|
|
}
|
|
|
|
found = PR_TRUE;
|
|
rv = sslBuffer_AppendNumber(buf, group->name, 2);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
}
|
|
|
|
/* GREASE SupportedGroups:
|
|
* A client MAY select one or more GREASE named group values and advertise
|
|
* them in the "supported_groups" extension, if sent [RFC8701, Section 3.1].
|
|
*/
|
|
if (!ss->sec.isServer &&
|
|
ss->opt.enableGrease &&
|
|
ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
rv = sslBuffer_AppendNumber(buf, ss->ssl3.hs.grease->idx[grease_group], 2);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
found = PR_TRUE;
|
|
}
|
|
|
|
if (!found) {
|
|
/* We added nothing, don't send the extension. */
|
|
return SECSuccess;
|
|
}
|
|
|
|
rv = sslBuffer_InsertLength(buf, lengthOffset, 2);
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
*added = PR_TRUE;
|
|
return SECSuccess;
|
|
}
|
|
|
|
/* Send our "canned" (precompiled) Supported Point Formats extension,
|
|
* which says that we only support uncompressed points.
|
|
*/
|
|
SECStatus
|
|
ssl3_SendSupportedPointFormatsXtn(const sslSocket *ss, TLSExtensionData *xtnData,
|
|
sslBuffer *buf, PRBool *added)
|
|
{
|
|
SECStatus rv;
|
|
|
|
/* No point in doing this unless we have a socket that supports ECC.
|
|
* Similarly, no point if we are going to do TLS 1.3 only or we have already
|
|
* picked TLS 1.3 (server) given that it doesn't use point formats. */
|
|
if (!ss || !ssl_IsECCEnabled(ss) ||
|
|
ss->vrange.min >= SSL_LIBRARY_VERSION_TLS_1_3 ||
|
|
(ss->sec.isServer && ss->version >= SSL_LIBRARY_VERSION_TLS_1_3)) {
|
|
return SECSuccess;
|
|
}
|
|
rv = sslBuffer_AppendNumber(buf, 1, 1); /* length */
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
rv = sslBuffer_AppendNumber(buf, 0, 1); /* uncompressed type only */
|
|
if (rv != SECSuccess) {
|
|
return SECFailure;
|
|
}
|
|
|
|
*added = PR_TRUE;
|
|
return SECSuccess;
|
|
}
|