3037 lines
85 KiB
C
3037 lines
85 KiB
C
/**
|
|
* @file matrixssl.c
|
|
* @version $Format:%h%d$
|
|
*
|
|
* The session and authentication management portions of the MatrixSSL library.
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013-2016 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
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
#include "matrixsslApi.h"
|
|
/******************************************************************************/
|
|
|
|
static const char copyright[] =
|
|
"Copyright Inside Secure Corporation. All rights reserved.";
|
|
|
|
#if defined(USE_RSA) || defined(USE_ECC)
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
static int32 verifyReadKeys(psPool_t *pool, sslKeys_t *keys, void *poolUserPtr);
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
#endif /* USE_RSA || USE_ECC */
|
|
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
|
|
#ifndef SSL_SESSION_TICKET_LIST_LEN
|
|
#define SSL_SESSION_TICKET_LIST_LEN 32
|
|
#endif /* SSL_SESSION_TICKET_LIST_LEN */
|
|
|
|
/*
|
|
Static session table for session cache and lock for multithreaded env
|
|
*/
|
|
#ifdef USE_MULTITHREADING
|
|
static psMutex_t sessionTableLock;
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
static psMutex_t g_sessTicketLock;
|
|
#endif
|
|
#endif /* USE_MULTITHREADING */
|
|
|
|
static sslSessionEntry_t sessionTable[SSL_SESSION_TABLE_SIZE];
|
|
static DLListEntry sessionChronList;
|
|
static void initSessionEntryChronList(void);
|
|
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
#if defined(USE_RSA) || defined(USE_ECC)
|
|
#ifdef MATRIX_USE_FILE_SYSTEM
|
|
static int32 matrixSslLoadKeyMaterial(sslKeys_t *keys, const char *certFile,
|
|
const char *privFile, const char *privPass, const char *CAfile,
|
|
int32 privKeyType);
|
|
#endif
|
|
static int32 matrixSslLoadKeyMaterialMem(sslKeys_t *keys,
|
|
const unsigned char *certBuf, int32 certLen,
|
|
const unsigned char *privBuf,
|
|
int32 privLen, const unsigned char *CAbuf, int32 CAlen,
|
|
int32 privKeyType);
|
|
#endif /* USE_RSA || USE_ECC */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Open and close the SSL module. These routines are called once in the
|
|
lifetime of the application and initialize and clean up the library
|
|
respectively.
|
|
The config param should always be passed as:
|
|
MATRIXSSL_CONFIG
|
|
*/
|
|
static char g_config[32] = "N";
|
|
|
|
int32 matrixSslOpenWithConfig(const char *config)
|
|
{
|
|
unsigned long clen;
|
|
|
|
/* Use copyright to avoid compiler warning about it being unused */
|
|
if (*copyright != 'C') {
|
|
return PS_FAILURE;
|
|
}
|
|
if (*g_config == 'Y') {
|
|
return PS_SUCCESS; /* Function has been called previously */
|
|
}
|
|
/* config parameter is matrixconfig + cryptoconfig + coreconfig */
|
|
strncpy(g_config, MATRIXSSL_CONFIG, sizeof(g_config) - 1);
|
|
clen = strlen(MATRIXSSL_CONFIG) - strlen(PSCRYPTO_CONFIG);
|
|
if (strncmp(g_config, config, clen) != 0) {
|
|
psErrorStr( "MatrixSSL config mismatch.\n" \
|
|
"Library: " MATRIXSSL_CONFIG \
|
|
"\nCurrent: %s\n", config);
|
|
return -1;
|
|
}
|
|
if (psCryptoOpen(config + clen) < 0) {
|
|
psError("pscrypto open failure\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
memset(sessionTable, 0x0,
|
|
sizeof(sslSessionEntry_t) * SSL_SESSION_TABLE_SIZE);
|
|
|
|
initSessionEntryChronList();
|
|
#ifdef USE_MULTITHREADING
|
|
psCreateMutex(&sessionTableLock);
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
psCreateMutex(&g_sessTicketLock);
|
|
#endif /* USE_STATELESS_SESSION_TICKETS */
|
|
#endif /* USE_MULTITHREADING */
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
#ifdef USE_DTLS
|
|
matrixDtlsSetPmtu(-1);
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
dtlsGenCookieSecret();
|
|
#endif
|
|
#endif /* USE_DTLS */
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
matrixSslClose
|
|
*/
|
|
void matrixSslClose(void)
|
|
{
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
int32 i;
|
|
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
for (i = 0; i < SSL_SESSION_TABLE_SIZE; i++) {
|
|
if (sessionTable[i].inUse > 1) {
|
|
psTraceInfo("Warning: closing while session still in use\n");
|
|
}
|
|
}
|
|
memset(sessionTable, 0x0,
|
|
sizeof(sslSessionEntry_t) * SSL_SESSION_TABLE_SIZE);
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
psDestroyMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
psCryptoClose();
|
|
*g_config = 'N';
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
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;
|
|
int32_t rc;
|
|
|
|
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);
|
|
if (rc < 0) {
|
|
psFree(lkeys, pool);
|
|
return rc;
|
|
}
|
|
#endif
|
|
*keys = lkeys;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
#ifdef USE_ECC
|
|
/* User is specifying EC curves that are supported so check that against the
|
|
keys they are supporting */
|
|
int32 psTestUserEcID(int32 id, int32 ecFlags)
|
|
{
|
|
if (id == 19) {
|
|
if (!(ecFlags & IS_SECP192R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 21) {
|
|
if (!(ecFlags & IS_SECP224R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 23) {
|
|
if (!(ecFlags & IS_SECP256R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 24) {
|
|
if (!(ecFlags & IS_SECP384R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 25) {
|
|
if (!(ecFlags & IS_SECP521R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 255) {
|
|
if (!(ecFlags & IS_BRAIN224R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 26) {
|
|
if (!(ecFlags & IS_BRAIN256R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 27) {
|
|
if (!(ecFlags & IS_BRAIN384R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else if (id == 28) {
|
|
if (!(ecFlags & IS_BRAIN512R1)) {
|
|
return PS_FAILURE;
|
|
}
|
|
} else {
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
int32 curveIdToFlag(int32 id)
|
|
{
|
|
if (id == 19) {
|
|
return IS_SECP192R1;
|
|
} else if (id == 21) {
|
|
return IS_SECP224R1;
|
|
} else if (id == 23) {
|
|
return IS_SECP256R1;
|
|
} else if (id == 24) {
|
|
return IS_SECP384R1;
|
|
} else if (id == 25) {
|
|
return IS_SECP521R1;
|
|
} else if (id == 255) {
|
|
return IS_BRAIN224R1;
|
|
} else if (id == 26) {
|
|
return IS_BRAIN256R1;
|
|
} else if (id == 27) {
|
|
return IS_BRAIN384R1;
|
|
} else if (id == 28) {
|
|
return IS_BRAIN512R1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int32 testUserEc(int32 ecFlags, const sslKeys_t *keys)
|
|
{
|
|
const psEccKey_t *eccKey;
|
|
psX509Cert_t *cert;
|
|
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (keys->privKey.type == PS_ECC) {
|
|
eccKey = &keys->privKey.key.ecc;
|
|
if (psTestUserEcID(eccKey->curve->curveId, ecFlags) < 0) {
|
|
return PS_FAILURE;
|
|
}
|
|
}
|
|
|
|
cert = keys->cert;
|
|
while (cert) {
|
|
if (cert->publicKey.type == PS_ECC) {
|
|
eccKey = &cert->publicKey.key.ecc;
|
|
if (psTestUserEcID(eccKey->curve->curveId, ecFlags) < 0) {
|
|
return PS_FAILURE;
|
|
}
|
|
}
|
|
cert = cert->next;
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
cert = keys->CAcerts;
|
|
while (cert) {
|
|
if (cert->publicKey.type == PS_ECC) {
|
|
eccKey = &cert->publicKey.key.ecc;
|
|
if (psTestUserEcID(eccKey->curve->curveId, ecFlags) < 0) {
|
|
return PS_FAILURE;
|
|
}
|
|
}
|
|
cert = cert->next;
|
|
}
|
|
#endif
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_ECC */
|
|
|
|
|
|
#ifdef MATRIX_USE_FILE_SYSTEM
|
|
#ifdef USE_PKCS12
|
|
|
|
/* Have seen cases where the PKCS#12 files are not in a child-to-parent order */
|
|
static void ReorderCertChain(psX509Cert_t *a_cert)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
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;
|
|
|
|
if (keys == NULL) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
pool = keys->pool;
|
|
|
|
if (macPass == NULL) {
|
|
mPass = (unsigned char*)importPass;
|
|
mpasslen = ipasslen;
|
|
} else {
|
|
mPass = (unsigned char*)macPass;
|
|
}
|
|
|
|
if ((rc = psPkcs12Parse(pool, &keys->cert, &keys->privKey, certFile, flags,
|
|
(unsigned char*)importPass, ipasslen, mPass, mpasslen)) < 0) {
|
|
if (keys->cert) {
|
|
psX509FreeCert(keys->cert);
|
|
keys->cert = NULL;
|
|
}
|
|
psClearPubKey(&keys->privKey);
|
|
return rc;
|
|
}
|
|
ReorderCertChain(keys->cert);
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (verifyReadKeys(pool, keys, keys->poolUserPtr) < PS_SUCCESS) {
|
|
psTraceInfo("PKCS#12 parse success but material didn't validate\n");
|
|
psX509FreeCert(keys->cert);
|
|
psClearPubKey(&keys->privKey);
|
|
keys->cert = NULL;
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_PKCS12 */
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifdef USE_RSA
|
|
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);
|
|
|
|
}
|
|
#endif /* USE_RSA */
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifdef USE_ECC
|
|
/**
|
|
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;
|
|
|
|
psAssert(keys && curve);
|
|
#if ECC_EPHEMERAL_CACHE_USAGE > 0
|
|
psGetTime(&t, keys->poolUserPtr);
|
|
(void)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);
|
|
}
|
|
(void)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;
|
|
}
|
|
rc = psEccGenKey(keys->pool, &keys->cache.eccPrivKey, curve, hwCtx);
|
|
if (rc < 0) {
|
|
(void)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);
|
|
}
|
|
(void)psUnlockMutex(&keys->cache.lock);
|
|
return rc;
|
|
#else
|
|
/* Not using ephemeral caching. */
|
|
if (ecc) {
|
|
rc = psEccGenKey(keys->pool, ecc, curve, hwCtx);
|
|
return rc;
|
|
}
|
|
rc = PS_SUCCESS;
|
|
return rc;
|
|
#endif /* ECC_EPHEMERAL_CACHE_USAGE > 0 */
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
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);
|
|
|
|
}
|
|
#endif /* USE_ECC */
|
|
|
|
#if defined(USE_RSA) || defined(USE_ECC)
|
|
static int32 matrixSslLoadKeyMaterial(sslKeys_t *keys, const char *certFile,
|
|
const char *privFile, const char *privPass, const char *CAfile,
|
|
int32 privKeyType)
|
|
{
|
|
psPool_t *pool;
|
|
int32 err, flags;
|
|
|
|
if (keys == NULL) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
pool = keys->pool;
|
|
|
|
/*
|
|
Setting flags to store raw ASN.1 stream for SSL CERTIFICATE message use
|
|
*/
|
|
flags = CERT_STORE_UNPARSED_BUFFER;
|
|
|
|
#ifdef USE_CLIENT_AUTH
|
|
/*
|
|
If the CERTIFICATE_REQUEST message will possibly be needed we must
|
|
save aside the Distiguished Name portion of the certs for that message.
|
|
*/
|
|
flags |= CERT_STORE_DN_BUFFER;
|
|
#endif /* USE_CLIENT_AUTH */
|
|
|
|
if (certFile) {
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (keys->cert != NULL) {
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
if ((err = psX509ParseCertFile(pool, (char *)certFile,
|
|
&keys->cert, flags)) < 0) {
|
|
return err;
|
|
}
|
|
if (keys->cert->authFailFlags) {
|
|
psAssert(keys->cert->authFailFlags == PS_CERT_AUTH_FAIL_DATE_FLAG);
|
|
#ifdef POSIX /* TODO - implement date check on WIN32, etc. */
|
|
psX509FreeCert(keys->cert);
|
|
keys->cert = NULL;
|
|
return PS_CERT_AUTH_FAIL_EXTENSION;
|
|
#endif
|
|
}
|
|
#else
|
|
psTraceStrInfo("Ignoring %s certFile in matrixSslReadKeys\n",
|
|
(char *)certFile);
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
}
|
|
/*
|
|
Parse the private key file
|
|
*/
|
|
if (privFile) {
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
/* See if private key already exists */
|
|
if (keys->privKey.keysize > 0) {
|
|
if (keys->cert) {
|
|
psX509FreeCert(keys->cert);
|
|
keys->cert = NULL;
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
}
|
|
#ifdef USE_RSA
|
|
if (privKeyType == PS_RSA) {
|
|
psInitPubKey(pool, &keys->privKey, PS_RSA);
|
|
if ((err = pkcs1ParsePrivFile(pool, (char *)privFile,
|
|
(char *)privPass, &keys->privKey.key.rsa)) < 0) {
|
|
if (keys->cert) {
|
|
psX509FreeCert(keys->cert);
|
|
keys->cert = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
keys->privKey.keysize = psRsaSize(&keys->privKey.key.rsa);
|
|
}
|
|
#endif /* USE_RSA */
|
|
#ifdef USE_ECC
|
|
if (privKeyType == PS_ECC) {
|
|
psInitPubKey(pool, &keys->privKey, PS_ECC);
|
|
if ((err = psEccParsePrivFile(pool, (char *)privFile,
|
|
(char *)privPass, &keys->privKey.key.ecc)) < 0) {
|
|
if (keys->cert) {
|
|
psX509FreeCert(keys->cert);
|
|
keys->cert = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
keys->privKey.keysize = psEccSize(&keys->privKey.key.ecc);
|
|
}
|
|
#endif /* USE_ECC */
|
|
#else
|
|
psTraceStrInfo("Ignoring %s privFile in matrixSslReadKeys\n",
|
|
(char *)privFile);
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
}
|
|
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (verifyReadKeys(pool, keys, keys->poolUserPtr) < PS_SUCCESS) {
|
|
psTraceInfo("Cert parse success but material didn't validate\n");
|
|
psX509FreeCert(keys->cert);
|
|
psClearPubKey(&keys->privKey);
|
|
keys->cert = NULL;
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
|
|
/* Not necessary to store binary representations of CA certs */
|
|
flags &= ~CERT_STORE_UNPARSED_BUFFER;
|
|
|
|
if (CAfile) {
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (keys->CAcerts != NULL) {
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
err = psX509ParseCertFile(pool, (char*)CAfile, &keys->CAcerts, flags);
|
|
if (err >= 0) {
|
|
if (keys->CAcerts->authFailFlags) {
|
|
/* This should be the only no err, FailFlags case currently */
|
|
psAssert(keys->CAcerts->authFailFlags ==
|
|
PS_CERT_AUTH_FAIL_DATE_FLAG);
|
|
#ifdef POSIX /* TODO - implement date check on WIN32, etc. */
|
|
psX509FreeCert(keys->CAcerts);
|
|
keys->CAcerts = NULL;
|
|
err = PS_CERT_AUTH_FAIL_EXTENSION;
|
|
#endif
|
|
}
|
|
}
|
|
if (err < 0) {
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (keys->cert) {
|
|
psX509FreeCert(keys->cert);
|
|
keys->cert = NULL;
|
|
}
|
|
psClearPubKey(&keys->privKey);
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
return err;
|
|
}
|
|
#else
|
|
psTraceStrInfo("Ignoring %s CAfile in matrixSslReadKeys\n", (char*)CAfile);
|
|
#endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
|
|
}
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
#endif /* USE_RSA || USE_ECC */
|
|
#endif /* MATRIX_USE_FILE_SYSTEM */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Memory buffer versions of ReadKeys
|
|
|
|
This function supports cert chains and multiple CAs. Just need to
|
|
string them together and let psX509ParseCert handle it
|
|
*/
|
|
#ifdef USE_RSA
|
|
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);
|
|
|
|
}
|
|
#endif /* USE_RSA */
|
|
|
|
#ifdef USE_ECC
|
|
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);
|
|
|
|
}
|
|
|
|
#endif /* USE_ECC */
|
|
|
|
#if defined(USE_RSA) || defined(USE_ECC)
|
|
static int32 matrixSslLoadKeyMaterialMem(sslKeys_t *keys,
|
|
const unsigned char *certBuf, int32 certLen,
|
|
const unsigned char *privBuf,
|
|
int32 privLen, const unsigned char *CAbuf, int32 CAlen,
|
|
int32 privKeyType)
|
|
{
|
|
psPool_t *pool;
|
|
int32 err, flags = 0;
|
|
|
|
if (keys == NULL) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
pool = keys->pool;
|
|
|
|
/*
|
|
Setting flags to store raw ASN.1 stream for SSL CERTIFICATE message use
|
|
*/
|
|
flags = CERT_STORE_UNPARSED_BUFFER;
|
|
|
|
#ifdef USE_CLIENT_AUTH
|
|
/*
|
|
Setting flag to store raw ASN.1 DN stream for CERTIFICATE_REQUEST
|
|
*/
|
|
flags |= CERT_STORE_DN_BUFFER;
|
|
#endif /* USE_CLIENT_AUTH */
|
|
|
|
if (certBuf) {
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (keys->cert != NULL) {
|
|
psTraceInfo("WARNING: An identity certificate already exists\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
if ((err = psX509ParseCert(pool, (unsigned char *)certBuf,
|
|
(uint32)certLen, &keys->cert, flags)) < 0) {
|
|
psX509FreeCert(keys->cert);
|
|
keys->cert = NULL;
|
|
return err;
|
|
}
|
|
#else
|
|
psTraceInfo("Ignoring certBuf in matrixSslReadKeysMem\n");
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
}
|
|
|
|
if (privBuf) {
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
#ifdef USE_RSA
|
|
if (privKeyType == PS_RSA) {
|
|
psInitPubKey(pool, &keys->privKey, PS_RSA);
|
|
if ((err = psRsaParsePkcs1PrivKey(pool, privBuf,
|
|
privLen, &keys->privKey.key.rsa)) < 0) {
|
|
#ifdef USE_PKCS8
|
|
/* Attempt a PKCS#8 but mem parse doesn't take password */
|
|
if ((err = pkcs8ParsePrivBin(pool, (unsigned char*)privBuf,
|
|
(uint32)privLen, NULL, &keys->privKey)) < 0) {
|
|
psX509FreeCert(keys->cert); keys->cert = NULL;
|
|
return err;
|
|
}
|
|
#else
|
|
psX509FreeCert(keys->cert); keys->cert = NULL;
|
|
return err;
|
|
#endif
|
|
}
|
|
keys->privKey.keysize = psRsaSize(&keys->privKey.key.rsa);
|
|
}
|
|
#endif /* USE_RSA */
|
|
#ifdef USE_ECC
|
|
if (privKeyType == PS_ECC) {
|
|
psInitPubKey(pool, &keys->privKey, PS_ECC);
|
|
if ((err = psEccParsePrivKey(pool, (unsigned char*)privBuf,
|
|
(uint32)privLen, &keys->privKey.key.ecc, NULL)) < 0) {
|
|
#ifdef USE_PKCS8
|
|
/* Attempt a PKCS#8 but mem parse doesn't take password */
|
|
if ((err = pkcs8ParsePrivBin(pool, (unsigned char*)privBuf,
|
|
(uint32)privLen, NULL, &keys->privKey)) < 0) {
|
|
psX509FreeCert(keys->cert); keys->cert = NULL;
|
|
return err;
|
|
}
|
|
#else
|
|
psX509FreeCert(keys->cert); keys->cert = NULL;
|
|
return err;
|
|
#endif
|
|
}
|
|
keys->privKey.keysize = psEccSize(&keys->privKey.key.ecc);
|
|
}
|
|
#endif /* USE_ECC */
|
|
#else
|
|
psTraceInfo("Ignoring privBuf in matrixSslReadKeysMem\n");
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
}
|
|
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (verifyReadKeys(pool, keys, keys->poolUserPtr) < PS_SUCCESS) {
|
|
psX509FreeCert(keys->cert);
|
|
psClearPubKey(&keys->privKey);
|
|
keys->cert = NULL;
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
|
|
/*
|
|
Not necessary to store binary representations of CA certs
|
|
*/
|
|
flags &= ~CERT_STORE_UNPARSED_BUFFER;
|
|
|
|
if (CAbuf) {
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (keys->CAcerts != NULL) {
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
if ((err = psX509ParseCert(pool, (unsigned char*)CAbuf, (uint32)CAlen,
|
|
&keys->CAcerts, flags)) < 0) {
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
psClearPubKey(&keys->privKey);
|
|
psX509FreeCert(keys->cert);
|
|
psX509FreeCert(keys->CAcerts);
|
|
keys->cert = keys->CAcerts = NULL;
|
|
#endif
|
|
return err;
|
|
}
|
|
#else
|
|
psTraceInfo("Ignoring CAbuf in matrixSslReadKeysMem\n");
|
|
#endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
|
|
}
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_RSA || USE_ECC */
|
|
|
|
|
|
#if defined(USE_OCSP) && defined(USE_SERVER_SIDE_SSL)
|
|
int32_t matrixSslLoadOCSPResponse(sslKeys_t *keys,
|
|
const unsigned char *OCSPResponseBuf, uint16_t OCSPResponseBufLen)
|
|
{
|
|
psPool_t *pool;
|
|
|
|
if (keys == NULL || OCSPResponseBuf == NULL || OCSPResponseBufLen == 0) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
pool = keys->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 && USE_SERVER_SIDE_SSL */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
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;
|
|
}
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (keys->cert) {
|
|
psX509FreeCert(keys->cert);
|
|
}
|
|
|
|
psClearPubKey(&keys->privKey);
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
|
|
#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
|
|
pkcs3ClearDhParams(&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 */
|
|
|
|
#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) && 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);
|
|
}
|
|
|
|
#if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
#if defined(USE_RSA) || defined(USE_ECC)
|
|
/*
|
|
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
|
|
*/
|
|
static int32 verifyReadKeys(psPool_t *pool, sslKeys_t *keys, void *poolUserPtr)
|
|
{
|
|
#ifdef USE_CERT_PARSE
|
|
psX509Cert_t *tmp, *found;
|
|
#endif
|
|
|
|
if (keys->cert == NULL && keys->privKey.type == 0) {
|
|
return PS_SUCCESS;
|
|
}
|
|
/*
|
|
Not allowed to have a certficate with no matching private key or
|
|
private key with no cert to match with
|
|
*/
|
|
if (keys->cert != NULL && keys->privKey.type == 0) {
|
|
psTraceInfo("No private key given to matrixSslReadKeys cert\n");
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
if (keys->privKey.type != 0 && keys->cert == NULL) {
|
|
psTraceInfo("No cert given with private key to matrixSslReadKeys\n");
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
#ifdef USE_CERT_PARSE
|
|
/*
|
|
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 (keys->cert != NULL && keys->cert->next != NULL) {
|
|
found = NULL;
|
|
psX509AuthenticateCert(pool, keys->cert, NULL, &found, NULL,
|
|
poolUserPtr);
|
|
tmp = keys->cert;
|
|
while (tmp->next != NULL) {
|
|
if (tmp->authStatus != PS_TRUE) {
|
|
psTraceInfo("Failed to authenticate cert chain\n");
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
tmp = tmp->next;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_RSA
|
|
if (keys->privKey.type == PS_RSA) {
|
|
if (psRsaCmpPubKey(&keys->privKey.key.rsa,
|
|
&keys->cert->publicKey.key.rsa) < 0) {
|
|
psTraceInfo("Private key doesn't match cert\n");
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
}
|
|
#endif /* USE_RSA */
|
|
#endif /* USE_CERT_PARSE */
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_RSA || USE_ECC */
|
|
#endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
|
|
/******************************************************************************/
|
|
|
|
#ifdef REQUIRE_DH_PARAMS
|
|
/******************************************************************************/
|
|
/*
|
|
User level API to assign the DH parameter file to the server application.
|
|
*/
|
|
#ifdef MATRIX_USE_FILE_SYSTEM
|
|
int32 matrixSslLoadDhParams(sslKeys_t *keys, const char *paramFile)
|
|
{
|
|
if (keys == NULL) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
return pkcs3ParseDhParamFile(keys->pool, (char*)paramFile, &keys->dhParams);
|
|
}
|
|
#endif /* MATRIX_USE_FILE_SYSTEM */
|
|
|
|
/******************************************************************************/
|
|
int32 matrixSslLoadDhParamsMem(sslKeys_t *keys, const unsigned char *dhBin,
|
|
int32 dhBinLen)
|
|
{
|
|
if (keys == NULL) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
return pkcs3ParseDhParamBin(keys->pool, (unsigned char*)dhBin, dhBinLen,
|
|
&keys->dhParams);
|
|
}
|
|
#endif /* REQUIRE_DH_PARAMS */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
New SSL protocol context
|
|
This structure is associated with a single SSL connection. Each socket
|
|
using SSL should be associated with a new SSL context.
|
|
|
|
certBuf and privKey ARE NOT duplicated within the server context, in order
|
|
to minimize memory usage with multiple simultaneous requests. They must
|
|
not be deleted by caller until all server contexts using them are deleted.
|
|
*/
|
|
int32 matrixSslNewSession(ssl_t **ssl, const sslKeys_t *keys,
|
|
sslSessionId_t *session, sslSessOpts_t *options)
|
|
{
|
|
psPool_t *pool = NULL;
|
|
ssl_t *lssl;
|
|
int32_t specificVersion, flags;
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
uint32_t i;
|
|
#endif
|
|
|
|
/* SERVER_SIDE and CLIENT_AUTH and others will have been added to
|
|
versionFlag by callers */
|
|
flags = options->versionFlag;
|
|
|
|
/*
|
|
First API level chance to make sure a user is not attempting to use
|
|
client or server support that was not built into this library compile
|
|
*/
|
|
#ifndef USE_SERVER_SIDE_SSL
|
|
if (flags & SSL_FLAGS_SERVER) {
|
|
psTraceInfo("SSL_FLAGS_SERVER passed to matrixSslNewSession but MatrixSSL lib was not compiled with server support\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
#endif
|
|
|
|
#ifndef USE_CLIENT_SIDE_SSL
|
|
if (!(flags & SSL_FLAGS_SERVER)) {
|
|
psTraceInfo("SSL_FLAGS_SERVER was not passed to matrixSslNewSession but MatrixSSL was not compiled with client support\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
#endif
|
|
|
|
#ifndef USE_CLIENT_AUTH
|
|
if (flags & SSL_FLAGS_CLIENT_AUTH) {
|
|
psTraceInfo("SSL_FLAGS_CLIENT_AUTH passed to matrixSslNewSession but MatrixSSL was not compiled with USE_CLIENT_AUTH enabled\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
#endif
|
|
|
|
if (flags & SSL_FLAGS_SERVER) {
|
|
#ifndef USE_PSK_CIPHER_SUITE
|
|
if (keys == NULL) {
|
|
/* TODO: test not correct if coming from matrixSslNewServer */
|
|
psTraceInfo("NULL keys parameter passed to matrixSslNewSession\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
#endif /* USE_PSK_CIPHER_SUITE */
|
|
if (session != NULL) {
|
|
psTraceInfo("Ignoring session parameter to matrixSslNewSession\n");
|
|
}
|
|
}
|
|
|
|
if (flags & SSL_FLAGS_INTERCEPTOR) {
|
|
psTraceInfo("SSL_FLAGS_INTERCEPTOR not supported\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
lssl = psMalloc(pool, sizeof(ssl_t));
|
|
if (lssl == NULL) {
|
|
psTraceInfo("Out of memory for ssl_t in matrixSslNewSession\n");
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(lssl, 0x0, sizeof(ssl_t));
|
|
lssl->memAllocPtr = options->memAllocPtr;
|
|
|
|
#ifdef USE_ECC
|
|
/* If user specified EC curves they support, let's check that against
|
|
the key material they provided so there are no conflicts. Don't
|
|
need to test against default compiled-in curves because the keys
|
|
would not have loaded at all */
|
|
if (options->ecFlags) {
|
|
if (testUserEc(options->ecFlags, keys) < 0) {
|
|
psTraceIntInfo("ERROR: Only EC 0x%x specified in options.ecFlags ",
|
|
options->ecFlags);
|
|
psTraceInfo("but other curves were found in key material\n");
|
|
psFree(lssl, pool);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
lssl->ecInfo.ecFlags = options->ecFlags;
|
|
} else {
|
|
lssl->ecInfo.ecFlags = compiledInEcFlags();
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
Data buffers
|
|
*/
|
|
lssl->bufferPool = options->bufferPool;
|
|
lssl->outsize = SSL_DEFAULT_OUT_BUF_SIZE;
|
|
#ifdef USE_DTLS
|
|
if (flags & SSL_FLAGS_DTLS) {
|
|
lssl->outsize = matrixDtlsGetPmtu();
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
/* Standard software implementation */
|
|
lssl->outbuf = psMalloc(lssl->bufferPool, lssl->outsize);
|
|
|
|
if (lssl->outbuf == NULL) {
|
|
psTraceInfo("Buffer pool is too small\n");
|
|
psFree(lssl, pool);
|
|
return PS_MEM_FAIL;
|
|
}
|
|
lssl->insize = SSL_DEFAULT_IN_BUF_SIZE;
|
|
#ifdef USE_DTLS
|
|
if (flags & SSL_FLAGS_DTLS) {
|
|
lssl->insize = matrixDtlsGetPmtu();
|
|
}
|
|
#endif /* USE_DTLS */
|
|
lssl->inbuf = psMalloc(lssl->bufferPool, lssl->insize);
|
|
if (lssl->inbuf == NULL) {
|
|
psTraceInfo("Buffer pool is too small\n");
|
|
psFree(lssl->outbuf, lssl->bufferPool);
|
|
psFree(lssl, pool);
|
|
return PS_MEM_FAIL;
|
|
}
|
|
|
|
lssl->sPool = pool;
|
|
lssl->keys = (sslKeys_t*)keys;
|
|
if ((lssl->cipher = sslGetCipherSpec(lssl, SSL_NULL_WITH_NULL_NULL)) == NULL) {
|
|
psFree(lssl->outbuf, lssl->bufferPool);
|
|
psFree(lssl, pool);
|
|
return PS_MEM_FAIL;
|
|
}
|
|
sslActivateReadCipher(lssl);
|
|
sslActivateWriteCipher(lssl);
|
|
|
|
lssl->recordHeadLen = SSL3_HEADER_LEN;
|
|
lssl->hshakeHeadLen = SSL3_HANDSHAKE_HEADER_LEN;
|
|
|
|
#ifdef SSL_REHANDSHAKES_ENABLED
|
|
lssl->rehandshakeCount = DEFAULT_RH_CREDITS;
|
|
#endif /* SSL_REHANDSHAKES_ENABLED */
|
|
|
|
#ifdef USE_DTLS
|
|
if (flags & SSL_FLAGS_DTLS) {
|
|
lssl->flags |= SSL_FLAGS_DTLS;
|
|
lssl->recordHeadLen += DTLS_HEADER_ADD_LEN;
|
|
lssl->hshakeHeadLen += DTLS_HEADER_ADD_LEN;
|
|
lssl->pmtu = matrixDtlsGetPmtu();
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
lssl->haveCookie = 0;
|
|
#endif
|
|
lssl->flightDone = 0;
|
|
lssl->appDataExch = 0;
|
|
lssl->lastMsn = -1;
|
|
dtlsInitFrag(lssl);
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
if (flags & SSL_FLAGS_SERVER) {
|
|
lssl->flags |= SSL_FLAGS_SERVER;
|
|
/*
|
|
Client auth can only be requested by server, not set by client
|
|
*/
|
|
if (flags & SSL_FLAGS_CLIENT_AUTH) {
|
|
lssl->flags |= SSL_FLAGS_CLIENT_AUTH;
|
|
}
|
|
lssl->hsState = SSL_HS_CLIENT_HELLO;
|
|
|
|
/* Is caller requesting specific protocol version for this client?
|
|
Make sure it's enabled and use specificVersion var for err */
|
|
specificVersion = 0;
|
|
if (flags & SSL_FLAGS_SSLV3) {
|
|
#ifndef DISABLE_SSLV3
|
|
lssl->majVer = SSL3_MAJ_VER;
|
|
lssl->minVer = SSL3_MIN_VER;
|
|
#else
|
|
specificVersion = 1;
|
|
#endif
|
|
}
|
|
|
|
if (flags & SSL_FLAGS_TLS_1_0) {
|
|
#ifdef USE_TLS
|
|
#ifndef DISABLE_TLS_1_0
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_MIN_VER;
|
|
#else
|
|
specificVersion = 1; /* TLS enabled but TLS_1_0 disabled */
|
|
#endif
|
|
#else
|
|
specificVersion = 1; /* TLS not even enabled */
|
|
#endif
|
|
}
|
|
|
|
if (flags & SSL_FLAGS_TLS_1_1) {
|
|
#ifdef USE_TLS_1_1
|
|
#ifndef DISABLE_TLS_1_1
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_1_1_MIN_VER;
|
|
#else
|
|
specificVersion = 1; /* TLS_1_1 enabled but TLS_1_1 disabled */
|
|
#endif
|
|
#else
|
|
specificVersion = 1; /* TLS not even enabled */
|
|
#endif
|
|
}
|
|
|
|
if (flags & SSL_FLAGS_TLS_1_2) {
|
|
#ifdef USE_TLS_1_2
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_1_2_MIN_VER;
|
|
#else
|
|
specificVersion = 1; /* TLS_1_2 disabled */
|
|
#endif
|
|
}
|
|
|
|
if (specificVersion) {
|
|
psTraceInfo("ERROR: protocol version isn't compiled into matrix\n");
|
|
matrixSslDeleteSession(lssl);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
#ifdef USE_DTLS
|
|
/* FLAGS_DTLS used in conjuction with specific 1.1 or 1.2 protocol */
|
|
if (flags & SSL_FLAGS_DTLS) {
|
|
if (lssl->majVer) {
|
|
if (lssl->minVer == SSL3_MIN_VER || lssl->minVer == TLS_MIN_VER)
|
|
{
|
|
psTraceInfo("ERROR: Can't use SSLv3 or TLS1.0 with DTLS\n");
|
|
matrixSslDeleteSession(lssl);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
lssl->majVer = DTLS_MAJ_VER;
|
|
if (lssl->minVer == TLS_1_2_MIN_VER) {
|
|
lssl->minVer = DTLS_1_2_MIN_VER;
|
|
} else if (lssl->minVer == TLS_1_1_MIN_VER) {
|
|
lssl->minVer = DTLS_MIN_VER;
|
|
} else {
|
|
psTraceInfo("ERROR: Protocol version parse error\n");
|
|
matrixSslDeleteSession(lssl);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
/*
|
|
Client is first to set protocol version information based on
|
|
compile and/or the 'flags' parameter so header information in
|
|
the handshake messages will be correctly set.
|
|
|
|
Look for specific version first... but still have to make sure library
|
|
has been compiled to support it
|
|
*/
|
|
specificVersion = 0;
|
|
|
|
if (flags & SSL_FLAGS_SSLV3) {
|
|
#ifndef DISABLE_SSLV3
|
|
lssl->majVer = SSL3_MAJ_VER;
|
|
lssl->minVer = SSL3_MIN_VER;
|
|
specificVersion = 1;
|
|
#else
|
|
specificVersion = 2;
|
|
#endif
|
|
}
|
|
|
|
if (flags & SSL_FLAGS_TLS_1_0) {
|
|
#ifdef USE_TLS
|
|
#ifndef DISABLE_TLS_1_0
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_MIN_VER;
|
|
lssl->flags |= SSL_FLAGS_TLS;
|
|
specificVersion = 1;
|
|
#else
|
|
specificVersion = 2; /* TLS enabled but TLS_1_0 disabled */
|
|
#endif
|
|
#else
|
|
specificVersion = 2; /* TLS not even enabled */
|
|
#endif
|
|
}
|
|
|
|
if (flags & SSL_FLAGS_TLS_1_1) {
|
|
#ifdef USE_TLS_1_1
|
|
#ifndef DISABLE_TLS_1_1
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_1_1_MIN_VER;
|
|
lssl->flags |= SSL_FLAGS_TLS | SSL_FLAGS_TLS_1_1;
|
|
specificVersion = 1;
|
|
#else
|
|
specificVersion = 2; /* TLS_1_1 enabled but TLS_1_1 disabled */
|
|
#endif
|
|
#else
|
|
specificVersion = 2; /* TLS not even enabled */
|
|
#endif
|
|
}
|
|
|
|
if (flags & SSL_FLAGS_TLS_1_2) {
|
|
#ifdef USE_TLS_1_2
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_1_2_MIN_VER;
|
|
lssl->flags |= SSL_FLAGS_TLS | SSL_FLAGS_TLS_1_1 | SSL_FLAGS_TLS_1_2;
|
|
specificVersion = 1;
|
|
#else
|
|
specificVersion = 2; /* TLS_1_2 disabled */
|
|
#endif
|
|
}
|
|
|
|
if (specificVersion == 2) {
|
|
psTraceInfo("ERROR: protocol version isn't compiled into matrix\n");
|
|
matrixSslDeleteSession(lssl);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
if (specificVersion == 0) {
|
|
/* Highest available if not specified (or not legal value) */
|
|
#ifdef USE_TLS
|
|
#ifndef DISABLE_TLS_1_0
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_MIN_VER;
|
|
#endif
|
|
#if defined(USE_TLS_1_1) && !defined(DISABLE_TLS_1_1)
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_1_1_MIN_VER;
|
|
lssl->flags |= SSL_FLAGS_TLS_1_1;
|
|
#endif /* USE_TLS_1_1 */
|
|
#ifdef USE_TLS_1_2
|
|
lssl->majVer = TLS_MAJ_VER;
|
|
lssl->minVer = TLS_1_2_MIN_VER;
|
|
lssl->flags |= SSL_FLAGS_TLS_1_2 | SSL_FLAGS_TLS_1_1;
|
|
#endif
|
|
if (lssl->majVer == 0) {
|
|
/* USE_TLS enabled but all DISABLE_TLS versions are enabled so
|
|
use SSLv3. Compile time tests would catch if no versions
|
|
are enabled at all */
|
|
lssl->majVer = SSL3_MAJ_VER;
|
|
lssl->minVer = SSL3_MIN_VER;
|
|
} else {
|
|
lssl->flags |= SSL_FLAGS_TLS;
|
|
}
|
|
|
|
#ifdef USE_DTLS
|
|
/* ssl->flags will have already been set above. Just set version */
|
|
if (flags & SSL_FLAGS_DTLS) {
|
|
lssl->minVer = DTLS_MIN_VER;
|
|
lssl->majVer = DTLS_MAJ_VER;
|
|
#ifdef USE_TLS_1_2
|
|
lssl->minVer = DTLS_1_2_MIN_VER;
|
|
#endif
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
#else /* USE_TLS */
|
|
lssl->majVer = SSL3_MAJ_VER;
|
|
lssl->minVer = SSL3_MIN_VER;
|
|
#endif /* USE_TLS */
|
|
} /* end non-specific version */
|
|
|
|
#ifdef USE_DTLS
|
|
if (flags & SSL_FLAGS_DTLS && specificVersion == 1) {
|
|
lssl->majVer = DTLS_MAJ_VER;
|
|
if (lssl->minVer == TLS_1_2_MIN_VER) {
|
|
lssl->minVer = DTLS_1_2_MIN_VER;
|
|
} else if (lssl->minVer == TLS_1_1_MIN_VER) {
|
|
lssl->minVer = DTLS_MIN_VER;
|
|
} else {
|
|
psTraceInfo("ERROR: DTLS must be TLS 1.1 or TLS 1.2\n");
|
|
matrixSslDeleteSession(lssl);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
lssl->hsState = SSL_HS_SERVER_HELLO;
|
|
if (session != NULL && session->cipherId != SSL_NULL_WITH_NULL_NULL) {
|
|
lssl->cipher = sslGetCipherSpec(lssl, session->cipherId);
|
|
if (lssl->cipher == NULL) {
|
|
psTraceInfo("Invalid session id to matrixSslNewSession\n");
|
|
} else {
|
|
memcpy(lssl->sec.masterSecret, session->masterSecret,
|
|
SSL_HS_MASTER_SIZE);
|
|
lssl->sessionIdLen = SSL_MAX_SESSION_ID_SIZE;
|
|
memcpy(lssl->sessionId, session->id, SSL_MAX_SESSION_ID_SIZE);
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
/* Possible no sessionId here at all if tickets used instead.
|
|
Will know if all 0s */
|
|
lssl->sessionIdLen = 0;
|
|
for (i = 0; i < SSL_MAX_SESSION_ID_SIZE; i++) {
|
|
if (session->id[i] != 0x0) {
|
|
lssl->sessionIdLen = SSL_MAX_SESSION_ID_SIZE;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
lssl->sid = session;
|
|
}
|
|
/* Clear these to minimize damage on a protocol parsing bug */
|
|
memset(lssl->inbuf, 0x0, lssl->insize);
|
|
memset(lssl->outbuf, 0x0, lssl->outsize);
|
|
lssl->err = SSL_ALERT_NONE;
|
|
lssl->encState = SSL_HS_NONE;
|
|
lssl->decState = SSL_HS_NONE;
|
|
*ssl = lssl;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Delete an SSL session. Some information on the session may stay around
|
|
in the session resumption cache.
|
|
SECURITY - We memset relevant values to zero before freeing to reduce
|
|
the risk of our keys floating around in memory after we're done.
|
|
*/
|
|
void matrixSslDeleteSession(ssl_t *ssl)
|
|
{
|
|
|
|
if (ssl == NULL) {
|
|
return;
|
|
}
|
|
|
|
ssl->flags |= SSL_FLAGS_CLOSED;
|
|
|
|
/* Synchronize all digests, in case some of them have been updated, but
|
|
not finished. */
|
|
#ifdef USE_TLS_1_2
|
|
psSha256Sync(NULL, 1);
|
|
#else /* !USE_TLS_1_2 */
|
|
psSha1Sync(NULL, 1);
|
|
#endif /* USE_TLS_1_2 */
|
|
|
|
/*
|
|
If we have a sessionId, for servers we need to clear the inUse flag in
|
|
the session cache so the ID can be replaced if needed. In the client case
|
|
the caller should have called matrixSslGetSessionId already to copy the
|
|
master secret and sessionId, so free it now.
|
|
|
|
In all cases except a successful updateSession call on the server, the
|
|
master secret must be freed.
|
|
*/
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
if (ssl->sessionIdLen > 0 && (ssl->flags & SSL_FLAGS_SERVER)) {
|
|
matrixUpdateSession(ssl);
|
|
}
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
if ((ssl->flags & SSL_FLAGS_SERVER) && ssl->sid) {
|
|
psFree(ssl->sid, ssl->sPool);
|
|
ssl->sid = NULL;
|
|
}
|
|
#endif
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
ssl->sessionIdLen = 0;
|
|
|
|
if (ssl->expectedName) {
|
|
psFree(ssl->expectedName, ssl->sPool);
|
|
}
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
if (ssl->sec.cert) {
|
|
psX509FreeCert(ssl->sec.cert);
|
|
ssl->sec.cert = NULL;
|
|
}
|
|
|
|
#endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
|
|
#endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
|
|
#ifdef REQUIRE_DH_PARAMS
|
|
if (ssl->sec.dhP) {
|
|
psFree(ssl->sec.dhP, ssl->hsPool); ssl->sec.dhP = NULL;
|
|
}
|
|
if (ssl->sec.dhG) {
|
|
psFree(ssl->sec.dhG, ssl->hsPool); ssl->sec.dhG = NULL;
|
|
}
|
|
if (ssl->sec.dhKeyPub) {
|
|
psDhClearKey(ssl->sec.dhKeyPub);
|
|
psFree(ssl->sec.dhKeyPub, ssl->hsPool);
|
|
ssl->sec.dhKeyPub = NULL;
|
|
}
|
|
if (ssl->sec.dhKeyPriv) {
|
|
psDhClearKey(ssl->sec.dhKeyPriv);
|
|
psFree(ssl->sec.dhKeyPriv, ssl->hsPool);
|
|
ssl->sec.dhKeyPriv = NULL;
|
|
}
|
|
#endif /* REQUIRE_DH_PARAMS */
|
|
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
if (ssl->sec.eccKeyPub) psEccDeleteKey(&ssl->sec.eccKeyPub);
|
|
if (ssl->sec.eccKeyPriv) psEccDeleteKey(&ssl->sec.eccKeyPriv);
|
|
#endif /* USE_ECC_CIPHER_SUITE */
|
|
|
|
/*
|
|
Premaster could also be allocated if this DeleteSession is the result
|
|
of a failed handshake. This test is fine since all frees will NULL pointer
|
|
*/
|
|
if (ssl->sec.premaster) {
|
|
psFree(ssl->sec.premaster, ssl->hsPool);
|
|
}
|
|
if (ssl->fragMessage) {
|
|
psFree(ssl->fragMessage, ssl->hsPool);
|
|
}
|
|
|
|
#ifdef USE_DTLS
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
if (ssl->cookie) {
|
|
psFree(ssl->cookie, ssl->hsPool);
|
|
}
|
|
#endif
|
|
if (ssl->helloExt) {
|
|
psFree(ssl->helloExt, ssl->hsPool);
|
|
}
|
|
dtlsInitFrag(ssl);
|
|
if (ssl->ckeMsg) {
|
|
psFree(ssl->ckeMsg, ssl->hsPool);
|
|
}
|
|
if (ssl->certVerifyMsg) {
|
|
psFree(ssl->certVerifyMsg, ssl->hsPool);
|
|
}
|
|
#if defined(USE_PSK_CIPHER_SUITE) && defined(USE_CLIENT_SIDE_SSL)
|
|
if (ssl->sec.hint) {
|
|
psFree(ssl->sec.hint, ssl->hsPool);
|
|
}
|
|
#endif
|
|
#endif /* USE_DTLS */
|
|
|
|
/*
|
|
Free the data buffers, clear any remaining user data
|
|
*/
|
|
memset(ssl->inbuf, 0x0, ssl->insize);
|
|
memset(ssl->outbuf, 0x0, ssl->outsize);
|
|
psFree(ssl->outbuf, ssl->bufferPool);
|
|
psFree(ssl->inbuf, ssl->bufferPool);
|
|
|
|
freePkaAfter(ssl);
|
|
clearFlightList(ssl);
|
|
|
|
#ifdef USE_ALPN
|
|
if (ssl->alpn) {
|
|
psFree(ssl->alpn, ssl->sPool); ssl->alpn = NULL;
|
|
}
|
|
#endif
|
|
/*
|
|
The cipher and mac contexts are inline in the ssl structure, so
|
|
clearing the structure clears those states as well.
|
|
*/
|
|
memset(ssl, 0x0, sizeof(ssl_t));
|
|
psFree(ssl, pool);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Generic session option control for changing already connected sessions.
|
|
(ie. rehandshake control). arg param is future for options that may
|
|
require a value.
|
|
*/
|
|
void matrixSslSetSessionOption(ssl_t *ssl, int32 option, void *arg)
|
|
{
|
|
if (option == SSL_OPTION_FULL_HANDSHAKE) {
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
if (ssl->flags & SSL_FLAGS_SERVER) {
|
|
matrixClearSession(ssl, 1);
|
|
}
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
ssl->sessionIdLen = 0;
|
|
memset(ssl->sessionId, 0x0, SSL_MAX_SESSION_ID_SIZE);
|
|
}
|
|
|
|
#ifdef SSL_REHANDSHAKES_ENABLED
|
|
if (option == SSL_OPTION_DISABLE_REHANDSHAKES) {
|
|
ssl->rehandshakeCount = -1;
|
|
}
|
|
/* Get one credit if re-enabling */
|
|
if (option == SSL_OPTION_REENABLE_REHANDSHAKES) {
|
|
ssl->rehandshakeCount = 1;
|
|
}
|
|
#endif
|
|
|
|
#if defined(USE_CLIENT_AUTH) && defined(USE_SERVER_SIDE_SSL)
|
|
if (ssl->flags & SSL_FLAGS_SERVER) {
|
|
if (option == SSL_OPTION_DISABLE_CLIENT_AUTH) {
|
|
ssl->flags &= ~SSL_FLAGS_CLIENT_AUTH;
|
|
} else if (option == SSL_OPTION_ENABLE_CLIENT_AUTH) {
|
|
ssl->flags |= SSL_FLAGS_CLIENT_AUTH;
|
|
matrixClearSession(ssl, 1);
|
|
}
|
|
}
|
|
#endif /* USE_CLIENT_AUTH && USE_SERVER_SIDE_SSL */
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Will be true if the cipher suite is an 'anon' variety OR if the
|
|
user certificate callback returned SSL_ALLOW_ANON_CONNECTION
|
|
*/
|
|
void matrixSslGetAnonStatus(ssl_t *ssl, int32 *certArg)
|
|
{
|
|
*certArg = ssl->sec.anon;
|
|
}
|
|
|
|
|
|
#ifdef USE_SSL_INFORMATIONAL_TRACE
|
|
void matrixSslPrintHSDetails(ssl_t *ssl)
|
|
{
|
|
if (ssl->hsState == SSL_HS_DONE) {
|
|
psTraceInfo("\n");
|
|
if (ssl->minVer == SSL3_MIN_VER) {
|
|
psTraceInfo("SSL 3.0 ");
|
|
} else if (ssl->minVer == TLS_MIN_VER) {
|
|
psTraceInfo("TLS 1.0 ");
|
|
} else if (ssl->minVer == TLS_1_1_MIN_VER) {
|
|
psTraceInfo("TLS 1.1 ");
|
|
} else if (ssl->minVer == TLS_1_2_MIN_VER) {
|
|
psTraceInfo("TLS 1.2 ");
|
|
}
|
|
#ifdef USE_DTLS
|
|
else if (ssl->minVer == DTLS_1_2_MIN_VER) {
|
|
psTraceInfo("DTLS 1.2 ");
|
|
} else if (ssl->minVer == DTLS_MIN_VER) {
|
|
psTraceInfo("DTLS 1.0 ");
|
|
}
|
|
#endif
|
|
psTraceInfo("connection established: ");
|
|
switch (ssl->cipher->ident) {
|
|
case SSL_RSA_WITH_NULL_MD5:
|
|
psTraceInfo("SSL_RSA_WITH_NULL_MD5\n");
|
|
break;
|
|
case SSL_RSA_WITH_NULL_SHA:
|
|
psTraceInfo("SSL_RSA_WITH_NULL_SHA\n");
|
|
break;
|
|
case SSL_RSA_WITH_RC4_128_MD5:
|
|
psTraceInfo("SSL_RSA_WITH_RC4_128_MD5\n");
|
|
break;
|
|
case SSL_RSA_WITH_RC4_128_SHA:
|
|
psTraceInfo("SSL_RSA_WITH_RC4_128_SHA\n");
|
|
break;
|
|
case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
psTraceInfo("SSL_RSA_WITH_3DES_EDE_CBC_SHA\n");
|
|
break;
|
|
case TLS_RSA_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_RSA_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_RSA_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_RSA_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
psTraceInfo("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA\n");
|
|
break;
|
|
case SSL_DH_anon_WITH_RC4_128_MD5:
|
|
psTraceInfo("SSL_DH_anon_WITH_RC4_128_MD5\n");
|
|
break;
|
|
case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
|
|
psTraceInfo("SSL_DH_anon_WITH_3DES_EDE_CBC_SHA\n");
|
|
break;
|
|
case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_DHE_RSA_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_DHE_RSA_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
|
|
psTraceInfo("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256\n");
|
|
break;
|
|
case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
|
|
psTraceInfo("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256\n");
|
|
break;
|
|
case TLS_DH_anon_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_DH_anon_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_DH_anon_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_DH_anon_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_RSA_WITH_AES_128_CBC_SHA256:
|
|
psTraceInfo("TLS_RSA_WITH_AES_128_CBC_SHA256\n");
|
|
break;
|
|
case TLS_RSA_WITH_AES_256_CBC_SHA256:
|
|
psTraceInfo("TLS_RSA_WITH_AES_256_CBC_SHA256\n");
|
|
break;
|
|
case TLS_RSA_WITH_SEED_CBC_SHA:
|
|
psTraceInfo("TLS_RSA_WITH_SEED_CBC_SHA\n");
|
|
break;
|
|
case TLS_RSA_WITH_IDEA_CBC_SHA:
|
|
psTraceInfo("TLS_RSA_WITH_IDEA_CBC_SHA\n");
|
|
break;
|
|
case TLS_PSK_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_PSK_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_PSK_WITH_AES_128_CBC_SHA256:
|
|
psTraceInfo("TLS_PSK_WITH_AES_128_CBC_SHA256\n");
|
|
break;
|
|
case TLS_PSK_WITH_AES_256_CBC_SHA384:
|
|
psTraceInfo("TLS_PSK_WITH_AES_256_CBC_SHA384\n");
|
|
break;
|
|
case TLS_PSK_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_PSK_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_DHE_PSK_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_DHE_PSK_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\n");
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
|
|
psTraceInfo("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
|
|
psTraceInfo("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256\n");
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
|
|
psTraceInfo("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384\n");
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
|
|
psTraceInfo("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256\n");
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
|
|
psTraceInfo("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384\n");
|
|
break;
|
|
case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
|
|
psTraceInfo("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA\n");
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
|
|
psTraceInfo("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256\n");
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
|
|
psTraceInfo("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384\n");
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
|
|
psTraceInfo("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256\n");
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
|
|
psTraceInfo("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384\n");
|
|
break;
|
|
case TLS_RSA_WITH_AES_128_GCM_SHA256:
|
|
psTraceInfo("TLS_RSA_WITH_AES_128_GCM_SHA256\n");
|
|
break;
|
|
case TLS_RSA_WITH_AES_256_GCM_SHA384:
|
|
psTraceInfo("TLS_RSA_WITH_AES_256_GCM_SHA384\n");
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
|
|
psTraceInfo("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\n");
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
|
|
psTraceInfo("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384\n");
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
|
|
psTraceInfo("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256\n");
|
|
break;
|
|
case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
|
|
psTraceInfo("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384\n");
|
|
break;
|
|
case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
|
|
psTraceInfo("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256\n");
|
|
break;
|
|
case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
|
|
psTraceInfo("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256\n");
|
|
break;
|
|
default:
|
|
psTraceIntInfo("!!!! DEFINE ME %d !!!!\n", ssl->cipher->ident);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
@return PS_TRUE if we've completed the SSL handshake. PS_FALSE otherwise.
|
|
*/
|
|
int32_t matrixSslHandshakeIsComplete(const ssl_t *ssl)
|
|
{
|
|
return (ssl->hsState == SSL_HS_DONE) ? PS_TRUE : PS_FALSE;
|
|
}
|
|
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
/******************************************************************************/
|
|
/*
|
|
Set a custom callback to receive the certificate being presented to the
|
|
session to perform custom authentication if needed
|
|
|
|
NOTE: Must define either USE_CLIENT_SIDE_SSL or USE_CLIENT_AUTH
|
|
in matrixConfig.h
|
|
*/
|
|
void matrixSslSetCertValidator(ssl_t *ssl, sslCertCb_t certValidator)
|
|
{
|
|
if ((ssl != NULL) && (certValidator != NULL)) {
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
ssl->sec.validateCert = certValidator;
|
|
#endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
}
|
|
}
|
|
#endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
|
|
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
/******************************************************************************/
|
|
/*
|
|
Initialize the session table.
|
|
*/
|
|
static void initSessionEntryChronList(void)
|
|
{
|
|
uint32 i;
|
|
DLListInit(&sessionChronList);
|
|
/* Assign every session table entry with their ID from the start */
|
|
for (i = 0; i < SSL_SESSION_TABLE_SIZE; i++) {
|
|
DLListInsertTail(&sessionChronList, &sessionTable[i].chronList);
|
|
sessionTable[i].id[0] = (unsigned char)(i & 0xFF);
|
|
sessionTable[i].id[1] = (unsigned char)((i & 0xFF00) >> 8);
|
|
sessionTable[i].id[2] = (unsigned char)((i & 0xFF0000) >> 16);
|
|
sessionTable[i].id[3] = (unsigned char)((i & 0xFF000000) >> 24);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Register a session in the session resumption cache. If successful (rc >=0),
|
|
the ssl sessionId and sessionIdLength fields will be non-NULL upon
|
|
return.
|
|
*/
|
|
int32 matrixRegisterSession(ssl_t *ssl)
|
|
{
|
|
uint32 i;
|
|
sslSessionEntry_t *sess;
|
|
DLListEntry *pList;
|
|
unsigned char *id;
|
|
|
|
if (!(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
/* Tickets override the other resumption mechanism */
|
|
if (ssl->sid &&
|
|
(ssl->sid->sessionTicketState == SESS_TICKET_STATE_RECVD_EXT)) {
|
|
/* Have recieved new ticket usage request by client */
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_DTLS
|
|
/*
|
|
Don't reassign a new sessionId if we already have one or we blow the
|
|
handshake hash
|
|
*/
|
|
if ((ssl->flags & SSL_FLAGS_DTLS) && ssl->sessionIdLen > 0) {
|
|
/* This is a retransmit case */
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
Iterate the session table, looking for an empty entry (cipher null), or
|
|
the oldest entry that is not in use
|
|
*/
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
|
|
if (DLListIsEmpty(&sessionChronList)) {
|
|
/* All in use */
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_LIMIT_FAIL;
|
|
|
|
}
|
|
/* GetHead Detaches */
|
|
pList = DLListGetHead(&sessionChronList);
|
|
sess = DLListGetContainer(pList, sslSessionEntry_t, chronList);
|
|
id = sess->id;
|
|
i = (id[3] << 24) + (id[2] << 16) + (id[1] << 8) + id[0];
|
|
if (i >= SSL_SESSION_TABLE_SIZE) {
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
|
|
/*
|
|
Register the incoming masterSecret and cipher, which could still be null,
|
|
depending on when we're called.
|
|
*/
|
|
memcpy(sessionTable[i].masterSecret, ssl->sec.masterSecret,
|
|
SSL_HS_MASTER_SIZE);
|
|
sessionTable[i].cipher = ssl->cipher;
|
|
sessionTable[i].inUse += 1;
|
|
/*
|
|
The sessionId is the current serverRandom value, with the first 4 bytes
|
|
replaced with the current cache index value for quick lookup later.
|
|
FUTURE SECURITY - Should generate more random bytes here for the session
|
|
id. We re-use the server random as the ID, which is OK, since it is
|
|
sent plaintext on the network, but an attacker listening to a resumed
|
|
connection will also be able to determine part of the original server
|
|
random used to generate the master key, even if he had not seen it
|
|
initially.
|
|
*/
|
|
memcpy(sessionTable[i].id + 4, ssl->sec.serverRandom,
|
|
min(SSL_HS_RANDOM_SIZE, SSL_MAX_SESSION_ID_SIZE) - 4);
|
|
ssl->sessionIdLen = SSL_MAX_SESSION_ID_SIZE;
|
|
|
|
memcpy(ssl->sessionId, sessionTable[i].id, SSL_MAX_SESSION_ID_SIZE);
|
|
/*
|
|
startTime is used to check expiry of the entry
|
|
|
|
The versions are stored, because a cached session must be reused
|
|
with same SSL version.
|
|
*/
|
|
psGetTime(&sessionTable[i].startTime, ssl->userPtr);
|
|
sessionTable[i].majVer = ssl->majVer;
|
|
sessionTable[i].minVer = ssl->minVer;
|
|
|
|
sessionTable[i].extendedMasterSecret = ssl->extFlags.extended_master_secret;
|
|
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return i;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Decrement inUse to keep the reference count meaningful
|
|
*/
|
|
int32 matrixClearSession(ssl_t *ssl, int32 remove)
|
|
{
|
|
unsigned char *id;
|
|
uint32 i;
|
|
|
|
if (ssl->sessionIdLen <= 0) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
id = ssl->sessionId;
|
|
|
|
i = (id[3] << 24) + (id[2] << 16) + (id[1] << 8) + id[0];
|
|
if (i >= SSL_SESSION_TABLE_SIZE) {
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
sessionTable[i].inUse -= 1;
|
|
if (sessionTable[i].inUse == 0) {
|
|
DLListInsertTail(&sessionChronList, &sessionTable[i].chronList);
|
|
}
|
|
|
|
/*
|
|
If this is a full removal, actually delete the entry. Also need to
|
|
clear any RESUME flag on the ssl connection so a new session
|
|
will be correctly registered.
|
|
*/
|
|
if (remove) {
|
|
memset(ssl->sessionId, 0x0, SSL_MAX_SESSION_ID_SIZE);
|
|
ssl->sessionIdLen = 0;
|
|
ssl->flags &= ~SSL_FLAGS_RESUMED;
|
|
/* Always preserve the id for chronList */
|
|
memset(sessionTable[i].id + 4, 0x0, SSL_MAX_SESSION_ID_SIZE - 4);
|
|
memset(sessionTable[i].masterSecret, 0x0, SSL_HS_MASTER_SIZE);
|
|
sessionTable[i].extendedMasterSecret = 0;
|
|
sessionTable[i].cipher = NULL;
|
|
}
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Look up a session ID in the cache. If found, set the ssl masterSecret
|
|
and cipher to the pre-negotiated values
|
|
*/
|
|
int32 matrixResumeSession(ssl_t *ssl)
|
|
{
|
|
psTime_t accessTime;
|
|
unsigned char *id;
|
|
uint32 i;
|
|
|
|
if (!(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (ssl->sessionIdLen <= 0) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
id = ssl->sessionId;
|
|
|
|
i = (id[3] << 24) + (id[2] << 16) + (id[1] << 8) + id[0];
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
if (i >= SSL_SESSION_TABLE_SIZE || sessionTable[i].cipher == NULL) {
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
/*
|
|
Id looks valid. Update the access time for expiration check.
|
|
Expiration is done on daily basis (86400 seconds)
|
|
*/
|
|
psGetTime(&accessTime, ssl->userPtr);
|
|
if ((memcmp(sessionTable[i].id, id,
|
|
(uint32)min(ssl->sessionIdLen, SSL_MAX_SESSION_ID_SIZE)) != 0) ||
|
|
(psDiffMsecs(sessionTable[i].startTime, accessTime, ssl->userPtr) >
|
|
SSL_SESSION_ENTRY_LIFE) || (sessionTable[i].majVer != ssl->majVer)
|
|
|| (sessionTable[i].minVer != ssl->minVer)) {
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* Enforce the RFC 7627 rules for resumpion and extended master secret.
|
|
Essentially, a resumption must use (or not use) the extended master
|
|
secret extension in step with the orginal connection */
|
|
if (sessionTable[i].extendedMasterSecret == 0 &&
|
|
ssl->extFlags.extended_master_secret == 1) {
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_FAILURE;
|
|
}
|
|
if (sessionTable[i].extendedMasterSecret == 1 &&
|
|
ssl->extFlags.extended_master_secret == 0) {
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* Looks good */
|
|
memcpy(ssl->sec.masterSecret, sessionTable[i].masterSecret,
|
|
SSL_HS_MASTER_SIZE);
|
|
ssl->cipher = sessionTable[i].cipher;
|
|
sessionTable[i].inUse += 1;
|
|
if (sessionTable[i].inUse == 1) {
|
|
DLListRemove(&sessionTable[i].chronList);
|
|
}
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Update session information in the cache.
|
|
This is called when we've determined the master secret and when we're
|
|
closing the connection to update various values in the cache.
|
|
*/
|
|
int32 matrixUpdateSession(ssl_t *ssl)
|
|
{
|
|
unsigned char *id;
|
|
uint32 i;
|
|
|
|
if (!(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (ssl->sessionIdLen == 0) {
|
|
/* No table entry. matrixRegisterSession was full of inUse entries */
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
id = ssl->sessionId;
|
|
i = (id[3] << 24) + (id[2] << 16) + (id[1] << 8) + id[0];
|
|
if (i >= SSL_SESSION_TABLE_SIZE) {
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
/*
|
|
If there is an error on the session, invalidate for any future use
|
|
*/
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
sessionTable[i].inUse += ssl->flags & SSL_FLAGS_CLOSED ? -1 : 0;
|
|
if (sessionTable[i].inUse == 0) {
|
|
/* End of the line */
|
|
DLListInsertTail(&sessionChronList, &sessionTable[i].chronList);
|
|
}
|
|
if (ssl->flags & SSL_FLAGS_ERROR) {
|
|
memset(sessionTable[i].masterSecret, 0x0, SSL_HS_MASTER_SIZE);
|
|
sessionTable[i].cipher = NULL;
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_FAILURE;
|
|
}
|
|
memcpy(sessionTable[i].masterSecret, ssl->sec.masterSecret,
|
|
SSL_HS_MASTER_SIZE);
|
|
sessionTable[i].cipher = ssl->cipher;
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&sessionTableLock);
|
|
#endif /* USE_MULTITHREADING */
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
/* This implementation supports AES-128/256_CBC and HMAC-SHA1/256 */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Remove a named key from the list.
|
|
|
|
NOTE: If this list can get very large the faster DLList API should be
|
|
used instead of this single linked list.
|
|
*/
|
|
int32 matrixSslDeleteSessionTicketKey(sslKeys_t *keys, unsigned char name[16])
|
|
{
|
|
psSessionTicketKeys_t *lkey, *prev;
|
|
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&g_sessTicketLock);
|
|
#endif
|
|
lkey = keys->sessTickets;
|
|
prev = NULL;
|
|
while (lkey) {
|
|
if (lkey->inUse == 0 && (memcmp(lkey->name, name, 16) == 0)) {
|
|
if (prev == NULL) {
|
|
/* removing the first in the list */
|
|
if (lkey->next == NULL) {
|
|
/* no more list == no more session ticket support */
|
|
psFree(lkey, keys->pool);
|
|
keys->sessTickets = NULL;
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_SUCCESS;
|
|
}
|
|
/* first in list but not alone */
|
|
keys->sessTickets = lkey->next;
|
|
psFree(lkey, keys->pool);
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_SUCCESS;
|
|
}
|
|
/* Middle of list. Join previous with our next */
|
|
prev->next = lkey->next;
|
|
psFree(lkey, keys->pool);
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_SUCCESS;
|
|
}
|
|
prev = lkey;
|
|
lkey = lkey->next;
|
|
}
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_FAILURE; /* not found */
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
This will be called on ticket decryption if the named key is not
|
|
in the current local list
|
|
*/
|
|
void matrixSslSetSessionTicketCallback(sslKeys_t *keys,
|
|
int32 (*ticket_cb)(void *, unsigned char[16], short))
|
|
{
|
|
keys->ticket_cb = ticket_cb;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
The first in the list will be the one used for all newly issued tickets
|
|
*/
|
|
int32 matrixSslLoadSessionTicketKeys(sslKeys_t *keys,
|
|
const unsigned char name[16], const unsigned char *symkey,
|
|
short symkeyLen, const unsigned char *hashkey, short hashkeyLen)
|
|
{
|
|
psSessionTicketKeys_t *keylist, *prev;
|
|
int32 i = 0;
|
|
|
|
/* AES-128 or AES-256 */
|
|
if (symkeyLen != 16 && symkeyLen != 32) {
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
/* SHA256 only */
|
|
if (hashkeyLen != 32) {
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&g_sessTicketLock);
|
|
#endif
|
|
if (keys->sessTickets == NULL) {
|
|
/* first one */
|
|
keys->sessTickets = psMalloc(keys->pool, sizeof(psSessionTicketKeys_t));
|
|
if (keys->sessTickets == NULL) {
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_MEM_FAIL;
|
|
}
|
|
keylist = keys->sessTickets;
|
|
} else {
|
|
/* append */
|
|
keylist = keys->sessTickets;
|
|
while (keylist) {
|
|
prev = keylist;
|
|
keylist = keylist->next;
|
|
i++;
|
|
}
|
|
if (i > SSL_SESSION_TICKET_LIST_LEN) {
|
|
psTraceInfo("Session ticket list > SSL_SESSION_TICKET_LIST_LEN\n");
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
keylist = psMalloc(keys->pool, sizeof(psSessionTicketKeys_t));
|
|
if (keylist == NULL) {
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_MEM_FAIL;
|
|
}
|
|
prev->next = keylist;
|
|
}
|
|
|
|
memset(keylist, 0x0, sizeof(psSessionTicketKeys_t));
|
|
keylist->hashkeyLen = hashkeyLen;
|
|
keylist->symkeyLen = symkeyLen;
|
|
memcpy(keylist->name, name, 16);
|
|
memcpy(keylist->hashkey, hashkey, hashkeyLen);
|
|
memcpy(keylist->symkey, symkey, symkeyLen);
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Size of encrypted session ticket using 16-byte block cipher and SHA-256
|
|
*/
|
|
int32 matrixSessionTicketLen(void)
|
|
{
|
|
int32 len = 0;
|
|
|
|
/* Master secret, 2 version, 2 cipher suite, 4 timestamp,
|
|
1 extended master secret flag are encypted */
|
|
len += SSL_HS_MASTER_SIZE + 2 + 2 + 4 + 1;
|
|
len += psPadLenPwr2(len, 16);
|
|
/* Name, IV and MAC plaintext */
|
|
len += 16 + 16 + SHA256_HASH_SIZE;
|
|
return len;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* Plaintext Format:
|
|
4 bytes lifetime hint
|
|
2 bytes length of following:
|
|
16 bytes name
|
|
16 bytes IV
|
|
<encrypt>
|
|
2 bytes protocol version
|
|
2 bytes cipher suite
|
|
1 byte extended master secret flag
|
|
48 bytes master secret
|
|
4 bytes timestamp
|
|
<padding /encrypt>
|
|
32 byte HMAC starting at 'name'
|
|
*/
|
|
int32 matrixCreateSessionTicket(ssl_t *ssl, unsigned char *out, int32 *outLen)
|
|
{
|
|
int32 len, ticketLen, pad, rc;
|
|
uint32 timeSecs;
|
|
psTime_t t;
|
|
psAesCbc_t ctx;
|
|
#ifdef USE_HMAC_SHA256
|
|
psHmacSha256_t dgst;
|
|
#else
|
|
psHmacSha1_t dgst;
|
|
#endif
|
|
psSessionTicketKeys_t *keys;
|
|
unsigned char *enc, *c = out;
|
|
unsigned char randno[AES_IVLEN];
|
|
|
|
ticketLen = matrixSessionTicketLen();
|
|
if ((ticketLen + 6) > *outLen) {
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
|
|
/* Lifetime hint taken from define in matrixsslConfig.h */
|
|
timeSecs = SSL_SESSION_ENTRY_LIFE / 1000; /* it's in milliseconds */
|
|
*c = (unsigned char)((timeSecs & 0xFF000000) >> 24); c++;
|
|
*c = (unsigned char)((timeSecs & 0xFF0000) >> 16); c++;
|
|
*c = (unsigned char)((timeSecs & 0xFF00) >> 8); c++;
|
|
*c = (unsigned char)(timeSecs & 0xFF); c++;
|
|
|
|
/* Len of ticket */
|
|
*c = (ticketLen & 0xFF00) >> 8; c++;
|
|
*c = ticketLen & 0xFF; c++;
|
|
|
|
/* Do the heavier CPU stuff outside lock */
|
|
timeSecs = psGetTime(&t, ssl->userPtr);
|
|
|
|
if (matrixCryptoGetPrngData(randno, AES_IVLEN, ssl->userPtr) < 0) {
|
|
psTraceInfo("WARNING: matrixCryptoGetPrngData failed\n");
|
|
}
|
|
|
|
psLockMutex(&g_sessTicketLock);
|
|
/* Ticket itself */
|
|
keys = ssl->keys->sessTickets;
|
|
/* name */
|
|
memcpy(c, keys->name, 16);
|
|
c += 16;
|
|
memcpy(c, randno, AES_IVLEN);
|
|
c += AES_IVLEN;
|
|
enc = c; /* encrypt start */
|
|
*c = ssl->majVer; c++;
|
|
*c = ssl->minVer; c++;
|
|
*c = (ssl->cipher->ident & 0xFF00) >> 8; c++;
|
|
*c = ssl->cipher->ident & 0xFF; c++;
|
|
/* Need to track if original handshake used extended master secret */
|
|
*c = ssl->extFlags.extended_master_secret; c++;
|
|
|
|
memcpy(c, ssl->sec.masterSecret, SSL_HS_MASTER_SIZE);
|
|
c += SSL_HS_MASTER_SIZE;
|
|
|
|
*c = (unsigned char)((timeSecs & 0xFF000000) >> 24); c++;
|
|
*c = (unsigned char)((timeSecs & 0xFF0000) >> 16); c++;
|
|
*c = (unsigned char)((timeSecs & 0xFF00) >> 8); c++;
|
|
*c = (unsigned char)(timeSecs & 0xFF); c++;
|
|
|
|
/* 4 time stamp, 2 version, 2 cipher, 1 extended master secret */
|
|
len = SSL_HS_MASTER_SIZE + 4 + 2 + 2 + 1;
|
|
|
|
pad = psPadLenPwr2(len, AES_BLOCKLEN);
|
|
c += sslWritePad(c, (unsigned char)pad); len += pad;
|
|
/* out + 6 + 16 (name) is pointing at IV */
|
|
if ((rc = psAesInitCBC(&ctx, out + 6 + 16, keys->symkey, keys->symkeyLen, PS_AES_ENCRYPT)) < 0) {
|
|
goto ERR_LOCKED;
|
|
}
|
|
psAesEncryptCBC(&ctx, enc, enc, len);
|
|
psAesClearCBC(&ctx);
|
|
|
|
/* HMAC starting from the Name */
|
|
#ifdef USE_HMAC_SHA256
|
|
if ((rc = psHmacSha256Init(&dgst, keys->hashkey, keys->hashkeyLen)) < 0) {
|
|
goto ERR_LOCKED;
|
|
}
|
|
psHmacSha256Update(&dgst, out + 6, len + 16 + 16);
|
|
psHmacSha256Final(&dgst, c);
|
|
*outLen = len + SHA256_HASHLEN + 16 + 16 + 6;
|
|
#else
|
|
if ((rc = psHmacSha1Init(&dgst, keys->hashkey, keys->hashkeyLen)) < 0) {
|
|
goto ERR_LOCKED;
|
|
}
|
|
psHmacSha1Update(&dgst, out + 6, len + 16 + 16);
|
|
psHmacSha1Final(&dgst, c);
|
|
*outLen = len + SHA1_HASHLEN + 16 + 16 + 6;
|
|
#endif
|
|
rc = PS_SUCCESS;
|
|
ERR_LOCKED:
|
|
memzero_s(randno, sizeof(randno));
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
return rc;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
@note careful, this function assumes the lock is on so must relock before
|
|
leaving if SUCCESS case. Failure assumes it's unlocked
|
|
*/
|
|
static int32 getTicketKeys(ssl_t *ssl, unsigned char *c,
|
|
psSessionTicketKeys_t **keys)
|
|
{
|
|
psSessionTicketKeys_t *lkey;
|
|
unsigned char name[16];
|
|
short cachedTicket = 0;
|
|
|
|
/* First 16 bytes are the key name */
|
|
memcpy(name, c, 16);
|
|
*keys = NULL;
|
|
/* check our cached list beginning with our own encryption key */
|
|
lkey = ssl->keys->sessTickets;
|
|
while (lkey) {
|
|
if (memcmp(lkey->name, name, 16) == 0) {
|
|
lkey->inUse = 1;
|
|
*keys = lkey;
|
|
/* Have the key. Invoke callback with SUCCESS */
|
|
if (ssl->keys->ticket_cb) {
|
|
cachedTicket++;
|
|
break;
|
|
} else {
|
|
return PS_SUCCESS;
|
|
}
|
|
}
|
|
lkey = lkey->next;
|
|
}
|
|
/* didn't find it. Ask user */
|
|
if (ssl->keys->ticket_cb) {
|
|
/* Unlock. Cback will likely call matrixSslLoadSessionTicketKeys */
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
if (ssl->keys->ticket_cb((struct sslKeys_t*)ssl->keys, name,
|
|
cachedTicket) < 0) {
|
|
if (lkey) {
|
|
/* inUse could be set in the odd case where we
|
|
found the cached key but the user didn't want to use it. */
|
|
lkey->inUse = 0;
|
|
}
|
|
return PS_FAILURE; /* user couldn't find it either */
|
|
} else {
|
|
/* found it */
|
|
psLockMutex(&g_sessTicketLock);
|
|
if (cachedTicket == 0) {
|
|
/* it's been found and added at end of list. confirm this */
|
|
lkey = ssl->keys->sessTickets;
|
|
if (lkey == NULL) {
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
return PS_FAILURE; /* user claims they added, but empty */
|
|
}
|
|
while (lkey->next) {
|
|
lkey = lkey->next;
|
|
}
|
|
if (memcmp(lkey->name, c, 16) != 0) {
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
return PS_FAILURE; /* user claims to have added, but... */
|
|
}
|
|
lkey->inUse = 1;
|
|
*keys = lkey;
|
|
}
|
|
return PS_SUCCESS;
|
|
}
|
|
}
|
|
return PS_FAILURE; /* not in list and no callback registered */
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
int32 matrixUnlockSessionTicket(ssl_t *ssl, unsigned char *in, int32 inLen)
|
|
{
|
|
unsigned char *c, *enc;
|
|
unsigned char name[16];
|
|
psSessionTicketKeys_t *keys;
|
|
#ifdef USE_HMAC_SHA256
|
|
psHmacSha256_t dgst;
|
|
#define L_HASHLEN SHA256_HASHLEN
|
|
#else
|
|
psHmacSha1_t dgst;
|
|
#define L_HASHLEN SHA1_HASHLEN
|
|
#endif
|
|
unsigned char hash[L_HASHLEN];
|
|
psAesCbc_t ctx;
|
|
int32 len;
|
|
psTime_t t;
|
|
uint32 majVer, minVer, cipherSuite, time, now;
|
|
|
|
/* Validate that the incoming ticket is the length we expect */
|
|
if (inLen != matrixSessionTicketLen()) {
|
|
return PS_FAILURE;
|
|
}
|
|
c = in;
|
|
len = inLen;
|
|
#ifdef USE_MULTITHREADING
|
|
psLockMutex(&g_sessTicketLock);
|
|
#endif
|
|
if (getTicketKeys(ssl, c, &keys) < 0) {
|
|
psTraceInfo("No key found for session ticket\n");
|
|
/* We've been unlocked in getTicketKeys */
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* Mac is over the name, IV and encrypted data */
|
|
#ifdef USE_HMAC_SHA256
|
|
psHmacSha256Init(&dgst, keys->hashkey, keys->hashkeyLen);
|
|
psHmacSha256Update(&dgst, c, len - L_HASHLEN);
|
|
psHmacSha256Final(&dgst, hash);
|
|
#else
|
|
psHmacSha1Init(&dgst, keys->hashkey, keys->hashkeyLen);
|
|
psHmacSha1Update(&dgst, c, len - L_HASHLEN);
|
|
psHmacSha1Final(&dgst, hash);
|
|
#endif
|
|
|
|
memcpy(name, c, 16);
|
|
c += 16;
|
|
|
|
/* out is pointing at IV */
|
|
psAesInitCBC(&ctx, c, keys->symkey, keys->symkeyLen, PS_AES_DECRYPT);
|
|
psAesDecryptCBC(&ctx, c + 16, c + 16, len - 16 - 16 - L_HASHLEN);
|
|
psAesClearCBC(&ctx);
|
|
keys->inUse = 0;
|
|
#ifdef USE_MULTITHREADING
|
|
psUnlockMutex(&g_sessTicketLock);
|
|
#endif
|
|
|
|
/* decrypted marker */
|
|
enc = c + 16;
|
|
|
|
c+= (len - 16 - L_HASHLEN); /* already moved past name */
|
|
|
|
if (memcmp(hash, c, L_HASHLEN) != 0) {
|
|
psTraceInfo("HMAC check failure on session ticket\n");
|
|
return PS_FAILURE;
|
|
}
|
|
#undef L_HASHLEN
|
|
|
|
majVer = *enc; enc++;
|
|
minVer = *enc; enc++;
|
|
|
|
/* Match protcol version */
|
|
if (majVer != ssl->majVer || minVer != ssl->minVer) {
|
|
psTraceInfo("Protocol check failure on session ticket\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
cipherSuite = *enc << 8; enc++;
|
|
cipherSuite += *enc; enc++;
|
|
|
|
/* Force cipher suite */
|
|
if ((ssl->cipher = sslGetCipherSpec(ssl, cipherSuite)) == NULL) {
|
|
psTraceInfo("Cipher suite check failure on session ticket\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
/* Did the initial connection use extended master secret? */
|
|
/* First round of "require" testing can be done here. If server is
|
|
set to require extended master secret and this ticket DOES NOT have it
|
|
then we can stop resumption right now */
|
|
if (*enc == 0x0 && ssl->extFlags.require_extended_master_secret == 1) {
|
|
psTraceInfo("Ticket and master secret derivation methods differ\n");
|
|
return PS_FAILURE;
|
|
}
|
|
ssl->extFlags.require_extended_master_secret = *enc; enc++;
|
|
|
|
/* Set aside masterSecret */
|
|
memcpy(ssl->sid->masterSecret, enc, SSL_HS_MASTER_SIZE);
|
|
enc += SSL_HS_MASTER_SIZE;
|
|
|
|
/* Check lifetime */
|
|
time = *enc << 24; enc++;
|
|
time += *enc << 16; enc++;
|
|
time += *enc << 8; enc++;
|
|
time += *enc; enc++;
|
|
|
|
now = psGetTime(&t, ssl->userPtr);
|
|
|
|
if ((now - time) > (SSL_SESSION_ENTRY_LIFE / 1000)) {
|
|
/* Expired session ticket. New one will be issued */
|
|
psTraceInfo("Session ticket was expired\n");
|
|
return PS_FAILURE;
|
|
}
|
|
ssl->sid->cipherId = cipherSuite;
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_STATELESS_SESSION_TICKETS */
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
/******************************************************************************/
|
|
/*
|
|
Get session information from the ssl structure and populate the given
|
|
session structure. Session will contain a copy of the relevant session
|
|
information, suitable for creating a new, resumed session.
|
|
|
|
NOTE: Must define USE_CLIENT_SIDE_SSL in matrixConfig.h
|
|
|
|
sslSessionId_t myClientSession;
|
|
|
|
...&myClientSession
|
|
*/
|
|
int32 matrixSslGetSessionId(ssl_t *ssl, sslSessionId_t *session)
|
|
{
|
|
|
|
if (ssl == NULL || ssl->flags & SSL_FLAGS_SERVER || session == NULL) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
if (ssl->cipher != NULL && ssl->cipher->ident != SSL_NULL_WITH_NULL_NULL &&
|
|
ssl->sessionIdLen == SSL_MAX_SESSION_ID_SIZE) {
|
|
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
/* There is only one sessionId_t structure for any given session and
|
|
it is possible a re-handshake on a session ticket connection will
|
|
agree on using standard resumption and so the old master secret
|
|
for the session ticket will be overwritten. Check for this case
|
|
here and do not update our session if a ticket is in use */
|
|
if (session->sessionTicket != NULL && session->sessionTicketLen > 0) {
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif
|
|
session->cipherId = ssl->cipher->ident;
|
|
memcpy(session->id, ssl->sessionId, ssl->sessionIdLen);
|
|
memcpy(session->masterSecret, ssl->sec.masterSecret,
|
|
SSL_HS_MASTER_SIZE);
|
|
return PS_SUCCESS;
|
|
}
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
if (ssl->cipher != NULL && ssl->cipher->ident != SSL_NULL_WITH_NULL_NULL &&
|
|
session->sessionTicket != NULL && session->sessionTicketLen > 0) {
|
|
session->cipherId = ssl->cipher->ident;
|
|
memcpy(session->masterSecret, ssl->sec.masterSecret,
|
|
SSL_HS_MASTER_SIZE);
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
#ifdef USE_ALPN
|
|
/******************************************************************************/
|
|
|
|
int32 matrixSslCreateALPNext(psPool_t *pool, int32 protoCount,
|
|
unsigned char *proto[MAX_PROTO_EXT], int32 protoLen[MAX_PROTO_EXT],
|
|
unsigned char **extOut, int32 *extLen)
|
|
{
|
|
int32 i, len;
|
|
unsigned char *c;
|
|
|
|
if (protoCount > MAX_PROTO_EXT) {
|
|
psTraceIntInfo("Must increase MAX_PROTO_EXT to %d\n", protoCount);
|
|
return PS_ARG_FAIL;
|
|
}
|
|
len = 2; /* overall len is 2 bytes */
|
|
for (i = 0; i < protoCount; i++) {
|
|
if (protoLen[i] <= 0 || protoLen[i] > 255) {
|
|
return PS_ARG_FAIL;
|
|
}
|
|
len += protoLen[i] + 1; /* each string has 1 byte len */
|
|
}
|
|
if ((c = psMalloc(pool, len)) == NULL) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(c, 0, len);
|
|
*extOut = c;
|
|
*extLen = len;
|
|
|
|
*c = ((len - 2) & 0xFF00) >> 8; c++; /* don't include ourself */
|
|
*c = (len - 2) & 0xFF; c++;
|
|
for (i = 0; i < protoCount; i++) {
|
|
*c = protoLen[i]; c++;
|
|
memcpy(c, proto[i], protoLen[i]);
|
|
c += protoLen[i];
|
|
}
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
|
|
int32 matrixSslCreateSNIext(psPool_t *pool, unsigned char *host, int32 hostLen,
|
|
unsigned char **extOut, int32 *extLen)
|
|
{
|
|
unsigned char *c;
|
|
|
|
*extLen = hostLen + 5;
|
|
if ((c = psMalloc(pool, *extLen)) == NULL) {
|
|
return PS_MEM_FAIL;
|
|
}
|
|
memset(c, 0, *extLen);
|
|
*extOut = c;
|
|
|
|
*c = ((hostLen + 3) & 0xFF00) >> 8; c++;
|
|
*c = (hostLen + 3) & 0xFF; c++;
|
|
c++; /* host_name enum */
|
|
*c = (hostLen & 0xFF00) >> 8; c++;
|
|
*c = hostLen & 0xFF; c++;
|
|
memcpy(c, host, hostLen);
|
|
return PS_SUCCESS;
|
|
}
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
/******************************************************************************/
|
|
/*
|
|
If client sent a ServerNameIndication extension, see if we have those
|
|
keys to load
|
|
*/
|
|
int32 matrixServerSetKeysSNI(ssl_t *ssl, char *host, int32 hostLen)
|
|
{
|
|
sslKeys_t *keys;
|
|
|
|
if (ssl->sni_cb) {
|
|
ssl->extFlags.sni = 1; /* extension was actually handled */
|
|
keys = NULL;
|
|
(ssl->sni_cb)((void*)ssl, host, hostLen, &keys) ;
|
|
if (keys) {
|
|
ssl->keys = keys;
|
|
return 0;
|
|
}
|
|
return PS_UNSUPPORTED_FAIL; /* callback didn't provide keys */
|
|
}
|
|
|
|
return 0; /* No callback registered. Go with default */
|
|
}
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Rehandshake. Free any allocated sec members that will be repopulated
|
|
*/
|
|
void sslResetContext(ssl_t *ssl)
|
|
{
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
if (!(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
ssl->anonBk = ssl->sec.anon;
|
|
ssl->flagsBk = ssl->flags;
|
|
ssl->bFlagsBk = ssl->bFlags;
|
|
}
|
|
#endif
|
|
ssl->sec.anon = 0;
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
if (ssl->flags & SSL_FLAGS_SERVER) {
|
|
matrixClearSession(ssl, 0);
|
|
}
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
#ifdef USE_DHE_CIPHER_SUITE
|
|
ssl->flags &= ~SSL_FLAGS_DHE_KEY_EXCH;
|
|
ssl->flags &= ~SSL_FLAGS_DHE_WITH_RSA;
|
|
#ifdef USE_ANON_DH_CIPHER_SUITE
|
|
ssl->flags &= ~SSL_FLAGS_ANON_CIPHER;
|
|
#endif /* USE_ANON_DH_CIPHER_SUITE */
|
|
#ifdef USE_ECC_CIPHER_SUITE
|
|
ssl->flags &= ~SSL_FLAGS_ECC_CIPHER;
|
|
ssl->flags &= ~SSL_FLAGS_DHE_WITH_RSA;
|
|
ssl->flags &= ~SSL_FLAGS_DHE_WITH_DSA;
|
|
#endif /* USE_ECC_CIPHER_SUITE */
|
|
#endif /* USE_DHE_CIPHER_SUITE */
|
|
|
|
#ifdef USE_PSK_CIPHER_SUITE
|
|
ssl->flags &= ~SSL_FLAGS_PSK_CIPHER;
|
|
#endif /* USE_PSK_CIPHER_SUITE */
|
|
|
|
#ifdef USE_DTLS
|
|
/*
|
|
This flag is used in conjuction with flightDone in the buffer
|
|
management API set to determine whether we are still in a handshake
|
|
state for attempting flight resends. If we are resetting context we
|
|
know a handshake phase is starting up again
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
ssl->appDataExch = 0;
|
|
}
|
|
#endif
|
|
ssl->bFlags = 0; /* Reset buffer control */
|
|
}
|
|
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
|
|
static int wildcardMatch(char *wild, char *s)
|
|
{
|
|
char *c, *e;
|
|
|
|
c = wild;
|
|
if (*c == '*') {
|
|
c++;
|
|
//TODO - this is actually a parse error
|
|
if (*c != '.') return -1;
|
|
if (strchr(s, '@')) return -1;
|
|
if ((e = strchr(s, '.')) == NULL) return -1;
|
|
if (strcasecmp(c, e) == 0) return 0;
|
|
} else if (*c == '.') {
|
|
//TODO - this is actually a parse error
|
|
return -1;
|
|
} else if (strcasecmp(c, s) == 0) {
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Subject certs is the leaf first chain of certs from the peer
|
|
Issuer certs is a flat list of trusted CAs loaded by LoadKeys
|
|
*/
|
|
int32 matrixValidateCerts(psPool_t *pool, psX509Cert_t *subjectCerts,
|
|
psX509Cert_t *issuerCerts, char *expectedName,
|
|
psX509Cert_t **foundIssuer, void *hwCtx,
|
|
void *poolUserPtr)
|
|
{
|
|
psX509Cert_t *ic, *sc;
|
|
x509GeneralName_t *n;
|
|
x509v3extensions_t *ext;
|
|
char ip[16];
|
|
int32 rc, pathLen = 0;
|
|
|
|
*foundIssuer = NULL;
|
|
/*
|
|
Case #1 is no issuing cert. Going to want to check that the final
|
|
subject cert presented is a SelfSigned CA
|
|
*/
|
|
if (issuerCerts == NULL) {
|
|
return psX509AuthenticateCert(pool, subjectCerts, NULL, foundIssuer,
|
|
hwCtx, poolUserPtr);
|
|
}
|
|
/*
|
|
Case #2 is an issuing cert AND possibly a chain of subjectCerts.
|
|
*/
|
|
sc = subjectCerts;
|
|
if ((ic = sc->next) != NULL) {
|
|
/*
|
|
We do have a chain. Authenticate the chain before even looking
|
|
to our issuer CAs.
|
|
*/
|
|
while (ic->next != NULL) {
|
|
if ((rc = psX509AuthenticateCert(pool, sc, ic, foundIssuer, hwCtx,
|
|
poolUserPtr)) < PS_SUCCESS) {
|
|
return rc;
|
|
}
|
|
if (ic->extensions.bc.pathLenConstraint >= 0) {
|
|
/* Make sure the pathLen is not exceeded */
|
|
if (ic->extensions.bc.pathLenConstraint < pathLen) {
|
|
psTraceInfo("Authentication failed due to X.509 pathLen\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_PATH_LEN;
|
|
return PS_CERT_AUTH_FAIL_PATH_LEN;
|
|
}
|
|
}
|
|
pathLen++;
|
|
sc = sc->next;
|
|
ic = sc->next;
|
|
}
|
|
/*
|
|
Test using the parent-most in chain as the subject
|
|
*/
|
|
if ((rc = psX509AuthenticateCert(pool, sc, ic, foundIssuer, hwCtx,
|
|
poolUserPtr)) < PS_SUCCESS) {
|
|
return rc;
|
|
}
|
|
if (ic->extensions.bc.pathLenConstraint >= 0) {
|
|
/* Make sure the pathLen is not exceeded */
|
|
if (ic->extensions.bc.pathLenConstraint < pathLen) {
|
|
psTraceInfo("Authentication failed due to X.509 pathLen\n");
|
|
sc->authStatus = PS_CERT_AUTH_FAIL_PATH_LEN;
|
|
return PS_CERT_AUTH_FAIL_PATH_LEN;
|
|
}
|
|
}
|
|
pathLen++;
|
|
/*
|
|
Lastly, set subject to the final cert for the real issuer test below
|
|
*/
|
|
sc = sc->next;
|
|
}
|
|
/*
|
|
Now loop through the issuer certs and see if we can authenticate this chain
|
|
|
|
If subject cert was a chain, that has already been authenticated above so
|
|
we only need to pass in the single parent-most cert to be tested against
|
|
*/
|
|
*foundIssuer = NULL;
|
|
ic = issuerCerts;
|
|
while (ic != NULL) {
|
|
sc->authStatus = PS_FALSE;
|
|
if ((rc = psX509AuthenticateCert(pool, sc, ic, foundIssuer, hwCtx,
|
|
poolUserPtr)) == PS_SUCCESS) {
|
|
if (ic->extensions.bc.pathLenConstraint >= 0) {
|
|
/* Make sure the pathLen is not exceeded. If the sc and ic
|
|
are the same CA at this point, this means the peer
|
|
included the root CA in the chain it sent. It's not good
|
|
practice to do this but implementations seem to allow it.
|
|
Subtract one from pathLen in this case since one got
|
|
added when it was truly just self-authenticating */
|
|
if (ic->signatureLen == sc->signatureLen &&
|
|
(memcmp(ic->signature, sc->signature,
|
|
sc->signatureLen) == 0)) {
|
|
if (pathLen > 0) pathLen--;
|
|
}
|
|
if (ic->extensions.bc.pathLenConstraint < pathLen) {
|
|
psTraceInfo("Authentication failed due to X.509 pathLen\n");
|
|
rc = sc->authStatus = PS_CERT_AUTH_FAIL_PATH_LEN;
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Validate extensions of leaf certificate */
|
|
ext = &subjectCerts->extensions;
|
|
|
|
/* Validate extended key usage */
|
|
if (ext->critFlags & EXT_CRIT_FLAG(OID_ENUM(id_ce_extKeyUsage))) {
|
|
if (!(ext->ekuFlags & (EXT_KEY_USAGE_TLS_SERVER_AUTH |
|
|
EXT_KEY_USAGE_TLS_CLIENT_AUTH))) {
|
|
_psTrace("End-entity certificate not for TLS usage!\n");
|
|
subjectCerts->authFailFlags |= PS_CERT_AUTH_FAIL_EKU_FLAG;
|
|
rc = subjectCerts->authStatus = PS_CERT_AUTH_FAIL_EXTENSION;
|
|
}
|
|
}
|
|
|
|
/* Check the subject/altSubject. Should match requested domain */
|
|
if (expectedName == NULL) {
|
|
return rc;
|
|
}
|
|
if (wildcardMatch(subjectCerts->subject.commonName,
|
|
expectedName) == 0) {
|
|
return rc;
|
|
}
|
|
for (n = ext->san; n != NULL; n = n->next) {
|
|
if (n->id == GN_DNS) {
|
|
if (wildcardMatch((char *)n->data, expectedName) == 0) {
|
|
return rc;
|
|
}
|
|
} else if (n->id == GN_EMAIL) {
|
|
/* Email doesn't have wildcards */
|
|
if (strcasecmp((char *)n->data, expectedName) == 0) {
|
|
return rc;
|
|
}
|
|
} else if (n->id == GN_IP) {
|
|
snprintf(ip, 15, "%u.%u.%u.%u",
|
|
(unsigned char)(n->data[0]),
|
|
(unsigned char )(n->data[1]),
|
|
(unsigned char )(n->data[2]),
|
|
(unsigned char )(n->data[3]));
|
|
ip[15] = '\0';
|
|
if (strcmp(ip, expectedName) == 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
psTraceInfo("Authentication failed: no matching subject\n");
|
|
subjectCerts->authFailFlags |= PS_CERT_AUTH_FAIL_SUBJECT_FLAG;
|
|
rc = subjectCerts->authStatus = PS_CERT_AUTH_FAIL_EXTENSION;
|
|
return rc;
|
|
} else if (rc == PS_MEM_FAIL) {
|
|
/*
|
|
OK to fail on the authentication because there may be a list here
|
|
but MEM failures prevent us from continuing at all.
|
|
*/
|
|
return rc;
|
|
}
|
|
ic = ic->next;
|
|
}
|
|
/*
|
|
Success would have returned if it happen
|
|
*/
|
|
return PS_CERT_AUTH_FAIL;
|
|
}
|
|
#endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Calls a user defined callback to allow for manual validation of the
|
|
certificate.
|
|
*/
|
|
int32 matrixUserCertValidator(ssl_t *ssl, int32 alert,
|
|
psX509Cert_t *subjectCert, sslCertCb_t certValidator)
|
|
{
|
|
int32 status;
|
|
|
|
/*
|
|
If there is no callback, return PS_SUCCESS because there has already been
|
|
a test for the case where the certificate did NOT PASS pubkey test
|
|
and a callback does not exist to manually handle.
|
|
|
|
It is highly recommended that the user manually verify, but the cert
|
|
material has internally authenticated and the user has implied that
|
|
is sufficient enough.
|
|
*/
|
|
if (certValidator == NULL) {
|
|
psTraceInfo("Internal cert auth passed. No user callback registered\n");
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
Finally, let the user know what the alert status is and
|
|
give them the cert material to access. Any non-zero value in alert
|
|
indicates there is a pending fatal alert.
|
|
|
|
The user can look at authStatus members if they want to examine the cert
|
|
that did not pass.
|
|
*/
|
|
if (alert == SSL_ALERT_NONE) {
|
|
status = 0;
|
|
} else {
|
|
status = alert;
|
|
}
|
|
|
|
/*
|
|
The user callback
|
|
*/
|
|
return certValidator(ssl, subjectCert, status);
|
|
}
|
|
#endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
|
|
/******************************************************************************/
|
|
#ifdef USE_MATRIXSSL_STATS
|
|
void matrixSslRegisterStatCallback(ssl_t *ssl, void (*stat_cb)(void *ssl,
|
|
void *stats_ptr, int32 type, int32 value), void *stats_ptr)
|
|
{
|
|
ssl->statCb = stat_cb;
|
|
ssl->statsPtr = stats_ptr;
|
|
}
|
|
|
|
void matrixsslUpdateStat(ssl_t *ssl, int32 type, int32 value)
|
|
{
|
|
if (ssl->statCb) {
|
|
(ssl->statCb)(ssl, ssl->statsPtr, type, value);
|
|
}
|
|
}
|
|
|
|
#endif /* USE_MATRIXSSL_STATS */
|
|
/******************************************************************************/
|
|
|