2257 lines
66 KiB
C
2257 lines
66 KiB
C
/**
|
|
* @file matrixsslApi.c
|
|
* @version $Format:%h%d$
|
|
*
|
|
* MatrixSSL Public API Layer.
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013-2018 INSIDE Secure Corporation
|
|
* Copyright (c) PeerSec Networks, 2002-2011
|
|
* All Rights Reserved
|
|
*
|
|
* The latest version of this code is available at http://www.matrixssl.org
|
|
*
|
|
* This software is open source; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This General Public License does NOT permit incorporating this software
|
|
* into proprietary programs. If you are unable to comply with the GPL, a
|
|
* commercial license for this software may be purchased from INSIDE at
|
|
* http://www.insidesecure.com/
|
|
*
|
|
* This program is distributed in WITHOUT ANY WARRANTY; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
#include "matrixsslImpl.h"
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Create a new client SSL session
|
|
This creates internal SSL buffers and cipher structures
|
|
Clients initiate the connection with a 'HelloRequest', and this data
|
|
is placed in the outgoing buffer for the caller to send.
|
|
|
|
ssl The ssl_t session structure is returned using this value, on success
|
|
|
|
keys Keys structure initialized with matrixSslReadKeys
|
|
|
|
sid A pointer to storage for a session ID. If there is not yet session
|
|
cache information, the sid.cipherId should be set to
|
|
SSL_NULL_WITH_NULL_NULL. After a successful connection to a server,
|
|
this sid structure will be populated with session cache credentials
|
|
and for subsequent connections should be used without modification
|
|
of the cipherId.
|
|
|
|
cipherSpec Array of requested ciphers to negotiate to (0 for server's choice)
|
|
If non-zero, and server doesn't have it, conn will fail
|
|
|
|
certCb Optional callback to call when validating cert
|
|
|
|
expectedName Optional certificate subject name to validate in the remote
|
|
certificate. Typically a client would specify the hostname or
|
|
IP address it is connecting to.
|
|
|
|
extensions Optional TLS extensions (usually NULL)
|
|
|
|
extCb TLS reply extensions from the server
|
|
|
|
flags TODO out of date
|
|
|
|
Return MATRIXSSL_REQUEST_SEND on success
|
|
< 0 on error. Do not need to call DeleteSession on failure
|
|
*/
|
|
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
int32_t matrixSslNewClientSession(ssl_t **ssl, const sslKeys_t *keys,
|
|
sslSessionId_t *sid,
|
|
const psCipher16_t cipherSpec[], uint8_t cipherSpecLen,
|
|
sslCertCb_t certCb,
|
|
const char *expectedName, tlsExtension_t *extensions,
|
|
sslExtCb_t extCb,
|
|
sslSessOpts_t *options)
|
|
{
|
|
ssl_t *lssl;
|
|
psBuf_t tmp;
|
|
uint32 len;
|
|
int32 rc, i;
|
|
# ifdef USE_TLS_1_3
|
|
tlsExtension_t *ext;
|
|
# endif
|
|
|
|
if (!ssl)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (cipherSpecLen > 0 && (cipherSpec == NULL || cipherSpec[0] == 0))
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (options == NULL)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
*ssl = NULL;
|
|
lssl = NULL;
|
|
|
|
# ifdef USE_EAP_FAST
|
|
if (sid && sid->sessionTicketState == SESS_TICKET_STATE_EAP_FAST)
|
|
{
|
|
/* EAP-FAST mode places some restrictions on session resumption */
|
|
if (sid->cipherId != 0 || sid->sessionTicket == NULL ||
|
|
sid->sessionTicketLen == 0 ||
|
|
sid->sessionTicketLifetimeHint != 0)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
}
|
|
# endif
|
|
# ifdef USE_TLS_1_3
|
|
if (sid && sid->psk && sid->psk->params)
|
|
{
|
|
/*
|
|
TLS 1.3 draft #28, 4.6.1:
|
|
"Clients MUST only resume if the new SNI value is valid for the server
|
|
certificate presented in the original session, and SHOULD only resume
|
|
if the SNI value matches the one used in the original session."
|
|
|
|
We only check the second condition, because the first condition is
|
|
then implied: the original SNI was matched against the server's
|
|
certificate during the initial handshake.
|
|
*/
|
|
ext = extensions;
|
|
while (ext)
|
|
{
|
|
if (ext->extType == EXT_SNI)
|
|
{
|
|
if (sid->psk->params->sniLen != 0)
|
|
{
|
|
if ((ext->extLen != sid->psk->params->sniLen)
|
|
|| Memcmp(ext->extData,
|
|
sid->psk->params->sni,
|
|
sid->psk->params->sniLen))
|
|
{
|
|
psTraceErrr("Error: attempted to resume using an SNI\n" \
|
|
"that does not match with the SNI used in the\n" \
|
|
"original session\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
}
|
|
}
|
|
ext = ext->next;
|
|
}
|
|
}
|
|
# endif
|
|
|
|
/* Give priority to cipher suite if session id is provided
|
|
and doesn't match */
|
|
if (cipherSpec != NULL && cipherSpec[0] != 0 && sid != NULL &&
|
|
sid->cipherId != 0)
|
|
{
|
|
rc = 1;
|
|
for (i = 0; i < cipherSpecLen; i++)
|
|
{
|
|
if (cipherSpec[i] == sid->cipherId)
|
|
{
|
|
rc = 0;
|
|
}
|
|
}
|
|
if (rc)
|
|
{
|
|
psTraceInfo("Explicit cipher suite will override session cache\n");
|
|
Memset(sid->id, 0, SSL_MAX_SESSION_ID_SIZE);
|
|
Memset(sid->masterSecret, 0, SSL_HS_MASTER_SIZE);
|
|
sid->cipherId = 0;
|
|
}
|
|
}
|
|
|
|
# ifdef USE_TLS_1_3
|
|
/* The default cipher list will contain TLS 1.3 suites if USE_TLS_1_3
|
|
is enabled. But if the list is provided directly by the user,
|
|
it is possible that it will not contain TLS 1.3 suites. Mark this
|
|
so that we don't advertise TLS 1.3 in ClientHello. */
|
|
if (cipherSpecLen == 0)
|
|
{
|
|
options->tls13CiphersuitesEnabledClient = PS_TRUE;
|
|
}
|
|
for (i = 0; i < cipherSpecLen; i++)
|
|
{
|
|
if (isTls13Ciphersuite(cipherSpec[i]))
|
|
{
|
|
options->tls13CiphersuitesEnabledClient = PS_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
# endif
|
|
|
|
if ((rc = matrixSslNewSession(&lssl, keys, sid, options)) < 0)
|
|
{
|
|
return rc;
|
|
}
|
|
lssl->userPtr = options->userPtr;
|
|
#ifdef USE_TLS_1_3
|
|
/* Check if the first PSK has maxEarlyData > 0
|
|
Note that the sid has higher priority */
|
|
if(lssl->sec.tls13SessionPskList != NULL &&
|
|
lssl->sec.tls13SessionPskList->params != NULL &&
|
|
lssl->sec.tls13SessionPskList->params->maxEarlyData > 0)
|
|
{
|
|
lssl->tls13ClientEarlyDataEnabled = PS_TRUE;
|
|
}
|
|
|
|
#endif
|
|
# ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
if (expectedName)
|
|
{
|
|
if (psX509ValidateGeneralName((char *) expectedName) < 0)
|
|
{
|
|
matrixSslDeleteSession(lssl);
|
|
return rc;
|
|
}
|
|
rc = Strlen(expectedName);
|
|
lssl->expectedName = psMalloc(lssl->sPool, rc + 1);
|
|
Strcpy(lssl->expectedName, expectedName);
|
|
Memcpy(&lssl->validateCertsOpts,
|
|
&options->validateCertsOpts,
|
|
sizeof(matrixValidateCertsOptions_t));
|
|
}
|
|
if (certCb)
|
|
{
|
|
matrixSslSetCertValidator(lssl, certCb);
|
|
}
|
|
# endif
|
|
if (extCb)
|
|
{
|
|
lssl->extCb = extCb;
|
|
}
|
|
# ifdef USE_EAP_FAST
|
|
if (sid && sid->sessionTicketState == SESS_TICKET_STATE_EAP_FAST)
|
|
{
|
|
/* Flag for EncodeClientHello that we want to resume with a ticket */
|
|
sid->sessionTicketState = SESS_TICKET_STATE_INIT;
|
|
options->ticketResumption = 1;
|
|
/* Indicate we're tunnelled below EAP_FAST */
|
|
lssl->flags |= SSL_FLAGS_EAP_FAST;
|
|
}
|
|
# endif
|
|
RETRY_HELLO:
|
|
tmp.size = lssl->outsize;
|
|
tmp.buf = tmp.start = tmp.end = lssl->outbuf;
|
|
|
|
#ifdef USE_TLS_1_3
|
|
if (USING_TLS_1_3(lssl))
|
|
{
|
|
rc = tls13WriteClientHello(lssl, &tmp, cipherSpec, cipherSpecLen,
|
|
&len, extensions, options);
|
|
}
|
|
else
|
|
#endif /* USE_TLS_1_3 */
|
|
{
|
|
rc = matrixSslEncodeClientHello(lssl, &tmp, cipherSpec, cipherSpecLen,
|
|
&len, extensions, options);
|
|
}
|
|
if (rc < 0)
|
|
{
|
|
if (rc == SSL_FULL)
|
|
{
|
|
if ((tmp.buf = psRealloc(lssl->outbuf, len, lssl->bufferPool))
|
|
== NULL)
|
|
{
|
|
matrixSslDeleteSession(lssl);
|
|
return PS_MEM_FAIL;
|
|
}
|
|
lssl->outbuf = tmp.buf;
|
|
lssl->outsize = len;
|
|
goto RETRY_HELLO;
|
|
}
|
|
else
|
|
{
|
|
matrixSslDeleteSession(lssl);
|
|
return rc;
|
|
}
|
|
}
|
|
psAssert(tmp.start == tmp.buf);
|
|
lssl->outlen = tmp.end - tmp.start;
|
|
# ifdef USE_EXT_CERTIFICATE_VERIFY_SIGNING
|
|
/*
|
|
Private key size is used by MatrixSSL to estimate the Cv
|
|
signature size. When using external Cv signing, we do not
|
|
have access to the private key. However, we can use the
|
|
public key size instead, since it is the same.
|
|
If we don't have a cert, we are likely using a PSK ciphersuite
|
|
in which case a Cv message is not needed.
|
|
*/
|
|
if (options->useExtCvSigOp)
|
|
{
|
|
sslIdentity_t *id;
|
|
|
|
for (id = lssl->keys->identity; id; id = id->next)
|
|
id->privKey.keysize = id->cert->publicKey.keysize;
|
|
/*
|
|
Enable external Cv signature generation for this connection.
|
|
*/
|
|
lssl->extCvSigOpInUse = 1;
|
|
}
|
|
# endif /* USE_EXT_CERTIFICATE_VERIFY_SIGNING */
|
|
*ssl = lssl;
|
|
return MATRIXSSL_REQUEST_SEND;
|
|
}
|
|
|
|
# ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
void matrixSslRegisterClientIdentityCallback(ssl_t *ssl,
|
|
sslIdentityCb_t identityCb)
|
|
{
|
|
ssl->sec.identityCb = identityCb;
|
|
}
|
|
# endif /* USE_ONLY_PSK_CIPHER_SUITE */
|
|
|
|
/* SessionID management functions for clients that wish to perform
|
|
session resumption. This structure handles both the traditional resumption
|
|
mechanism and the server-stateless session ticket mechanism
|
|
*/
|
|
int32 matrixSslNewSessionId(sslSessionId_t **sess, void *poolUserPtr)
|
|
{
|
|
sslSessionId_t *ses;
|
|
psPool_t *pool = NULL;
|
|
|
|
if ((ses = psMalloc(pool, sizeof(sslSessionId_t))) == NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
Memset(ses, 0x0, sizeof(sslSessionId_t));
|
|
ses->pool = pool;
|
|
*sess = ses;
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
void matrixSslClearSessionId(sslSessionId_t *sess)
|
|
{
|
|
psPool_t *pool;
|
|
|
|
# ifdef USE_STATELESS_SESSION_TICKETS
|
|
if (sess->sessionTicket)
|
|
{
|
|
psFree(sess->sessionTicket, sess->pool);
|
|
}
|
|
# endif
|
|
|
|
# ifdef USE_TLS_1_3
|
|
if (sess->psk)
|
|
{
|
|
tls13FreePsk(sess->psk, sess->pool);
|
|
}
|
|
# endif
|
|
|
|
pool = sess->pool;
|
|
Memset(sess, 0x0, sizeof(sslSessionId_t));
|
|
sess->pool = pool;
|
|
}
|
|
|
|
void matrixSslDeleteSessionId(sslSessionId_t *sess)
|
|
{
|
|
|
|
if (sess == NULL)
|
|
{
|
|
return;
|
|
}
|
|
# ifdef USE_STATELESS_SESSION_TICKETS
|
|
if (sess->sessionTicket)
|
|
{
|
|
psFree(sess->sessionTicket, sess->pool);
|
|
}
|
|
# endif
|
|
|
|
# ifdef USE_TLS_1_3
|
|
if (sess->psk)
|
|
{
|
|
tls13FreePsk(sess->psk, sess->pool);
|
|
}
|
|
# endif
|
|
|
|
Memset(sess, 0x0, sizeof(sslSessionId_t));
|
|
psFree(sess, NULL);
|
|
}
|
|
|
|
# ifdef USE_EAP_FAST
|
|
/**
|
|
Set the TLS connection to connect using an externally provisioned EAP-FAST
|
|
Protected Access Credential (PAC).
|
|
@see https://tools.ietf.org/html/rfc4851
|
|
@param[in,out] sess SessionID structure to fill in with PAC.
|
|
@param[in] pac_key 32 byte secret key shared between the EAP-FAST peers.
|
|
This is used by EAP-FAST peers to do session key derivation and is
|
|
never sent over the wire (encrypted or unencrypted) by TLS.
|
|
@param[in] pac_opaque Opaque value to be sent as plaintext to the server
|
|
within the SessionTicket ClientHello Extension so the server can
|
|
choose the correct pac_key.
|
|
@param[in] pac_opaque_len Length in bytes of 'pac_opaque'
|
|
*/
|
|
void matrixSslSetSessionIdEapFast(sslSessionId_t *sess,
|
|
const unsigned char pac_key[EAP_FAST_PAC_KEY_LEN],
|
|
const unsigned char *pac_opaque, psSize_t pac_opaque_len)
|
|
{
|
|
psAssert(sess && pac_key && pac_opaque && (pac_opaque_len > 0));
|
|
|
|
/* Indicate we're overriding the default Ticket fields and behavior */
|
|
sess->sessionTicketState = SESS_TICKET_STATE_EAP_FAST;
|
|
# if EAP_TLS_PAC_KEY_LEN > SSL_HS_MASTER_SIZE
|
|
# error EAP_TLS_PAC_KEY_LEN too large
|
|
# endif
|
|
/** @note, sess->master_secret must go through tprf() before being used */
|
|
Memcpy(sess->masterSecret, pac_key, EAP_FAST_PAC_KEY_LEN);
|
|
sess->sessionTicket = psMalloc(sess->pool, pac_opaque_len);
|
|
Memcpy(sess->sessionTicket, pac_opaque, pac_opaque_len);
|
|
sess->sessionTicketLen = pac_opaque_len;
|
|
}
|
|
|
|
int32_t matrixSslGetEapFastSKS(const ssl_t *ssl,
|
|
unsigned char session_key_seed[EAP_FAST_SESSION_KEY_SEED_LEN])
|
|
{
|
|
if (!ssl || !session_key_seed)
|
|
{
|
|
psAssert(ssl && session_key_seed);
|
|
return PS_FAIL;
|
|
}
|
|
if (!ssl->sec.eap_fast_session_key_seed ||
|
|
matrixSslHandshakeIsComplete(ssl) != PS_TRUE)
|
|
{
|
|
return PS_EAGAIN;
|
|
}
|
|
Memcpy(session_key_seed, ssl->sec.eap_fast_session_key_seed,
|
|
EAP_FAST_SESSION_KEY_SEED_LEN);
|
|
return PS_SUCCESS;
|
|
}
|
|
# endif /* USE_EAP_FAST */
|
|
|
|
# ifdef USE_EXT_CLIENT_CERT_KEY_LOADING
|
|
psBool_t matrixSslNeedClientCert(ssl_t *ssl)
|
|
{
|
|
if (ssl->extClientCertKeyStateFlags ==
|
|
EXT_CLIENT_CERT_KEY_STATE_WAIT_FOR_CERT_KEY_UPDATE)
|
|
{
|
|
return PS_TRUE;
|
|
}
|
|
else
|
|
{
|
|
return PS_FALSE;
|
|
}
|
|
}
|
|
psBool_t matrixSslNeedClientPrivKey(ssl_t *ssl)
|
|
{
|
|
if (ssl->extClientCertKeyStateFlags ==
|
|
EXT_CLIENT_CERT_KEY_STATE_WAIT_FOR_CERT_KEY_UPDATE)
|
|
{
|
|
# ifdef USE_EXT_CERTIFICATE_VERIFY_SIGNING
|
|
/*
|
|
Not going to need priv key loading when we are signing
|
|
the CertificateVerify message externally - we won't need
|
|
direct access to the priv key in that case.
|
|
*/
|
|
if (matrixSslNeedCvSignature(ssl))
|
|
{
|
|
return PS_FALSE;
|
|
}
|
|
# endif /* USE_EXT_CERTIFICATE_VERIFY_SIGNING */
|
|
return PS_TRUE;
|
|
}
|
|
else
|
|
{
|
|
return PS_FALSE;
|
|
}
|
|
}
|
|
|
|
psBool_t matrixSslClientCertUpdated(ssl_t *ssl)
|
|
{
|
|
if (ssl->extClientCertKeyStateFlags !=
|
|
EXT_CLIENT_CERT_KEY_STATE_WAIT_FOR_CERT_KEY_UPDATE)
|
|
{
|
|
psTraceInfo("Error: wrong state for client cert update\n");
|
|
return PS_FALSE;
|
|
}
|
|
else
|
|
{
|
|
ssl->extClientCertKeyStateFlags =
|
|
EXT_CLIENT_CERT_KEY_STATE_GOT_CERT_KEY_UPDATE;
|
|
/*
|
|
We will assume the client has loaded a cert that is
|
|
compatible with the server's expectations.
|
|
By-pass MatrixSSL checks.
|
|
*/
|
|
/**/
|
|
if (ssl->keys && ssl->keys->identity)
|
|
{
|
|
ssl->sec.certMatch = 1;
|
|
}
|
|
|
|
return PS_TRUE;
|
|
}
|
|
}
|
|
|
|
psBool_t matrixSslClientPrivKeyUpdated(ssl_t *ssl)
|
|
{
|
|
if (ssl->extClientCertKeyStateFlags !=
|
|
EXT_CLIENT_CERT_KEY_STATE_WAIT_FOR_CERT_KEY_UPDATE)
|
|
{
|
|
psTraceInfo("Error: wrong state for client key update\n");
|
|
return PS_FALSE;
|
|
}
|
|
else
|
|
{
|
|
ssl->extClientCertKeyStateFlags =
|
|
EXT_CLIENT_CERT_KEY_STATE_GOT_CERT_KEY_UPDATE;
|
|
return PS_TRUE;
|
|
}
|
|
}
|
|
# endif /* USE_EXT_CLIENT_CERT_KEY_LOADING */
|
|
|
|
# ifdef USE_EXT_CERTIFICATE_VERIFY_SIGNING
|
|
int32_t matrixSslNeedCvSignature(ssl_t *ssl)
|
|
{
|
|
psAssert(ssl != NULL);
|
|
|
|
if (ssl->extCvSigOpPending)
|
|
{
|
|
return PS_TRUE;
|
|
}
|
|
else
|
|
{
|
|
return PS_FALSE;
|
|
}
|
|
}
|
|
|
|
int32_t matrixSslGetHSMessagesHash(ssl_t *ssl, unsigned char *hash, size_t *hash_len)
|
|
{
|
|
psAssert(ssl != NULL || hash != NULL || hash_len != NULL);
|
|
|
|
if (!ssl->extCvSigOpPending)
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
psAssert(ssl->extCvHash != NULL);
|
|
|
|
psAssert(ssl->extCvHashLen == 20 || ssl->extCvHashLen == 32 ||
|
|
ssl->extCvHashLen == 36 || ssl->extCvHashLen == 48 ||
|
|
ssl->extCvHashLen == 64);
|
|
|
|
if (*hash_len < ssl->extCvHashLen)
|
|
{
|
|
return PS_OUTPUT_LENGTH;
|
|
}
|
|
|
|
Memcpy(hash, ssl->extCvHash, ssl->extCvHashLen);
|
|
*hash_len = ssl->extCvHashLen;
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
int32_t matrixSslGetCvSignatureAlg(ssl_t *ssl)
|
|
{
|
|
psAssert(ssl != NULL);
|
|
|
|
if (!matrixSslNeedCvSignature(ssl))
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
return ssl->extCvSigAlg;
|
|
}
|
|
|
|
/* XXX: unused, undeclared */
|
|
psRes_t matrixSslGetPubKeySize(ssl_t *ssl)
|
|
{
|
|
int32_t type;
|
|
sslIdentity_t *id;
|
|
|
|
psAssert(ssl != NULL);
|
|
|
|
if (!matrixSslNeedCvSignature(ssl))
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
type = matrixSslGetCvSignatureAlg(ssl);
|
|
if (type < 0)
|
|
{
|
|
return type;
|
|
}
|
|
|
|
for (id = ssl->keys->identity; id; id = id->next)
|
|
{
|
|
if (type != id->cert->publicKey.type)
|
|
continue;
|
|
|
|
switch (type)
|
|
{
|
|
# ifdef USE_RSA
|
|
case PS_RSA:
|
|
return id->cert->publicKey.keysize;
|
|
# endif
|
|
# ifdef USE_ECC
|
|
case PS_ECC:
|
|
return id->cert->publicKey.key.ecc.curve->size;
|
|
# endif
|
|
default:
|
|
psTraceIntInfo("matrixSslGetPubKeySize: unsupported alg type: %d\n", type);
|
|
}
|
|
}
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
int32_t matrixSslSetCvSignature(ssl_t *ssl, const unsigned char *sig, const size_t sig_len)
|
|
{
|
|
psAssert(ssl != NULL || sig != NULL || sig_len > 0);
|
|
|
|
if (!ssl->extCvSigOpPending)
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
psAssert(ssl->extCvSigAlg == PS_RSA || ssl->extCvSigAlg == PS_ECC);
|
|
|
|
if (ssl->extCvSigAlg == PS_RSA)
|
|
{
|
|
ssl->extCvSig = psMalloc(NULL, sig_len);
|
|
}
|
|
else
|
|
{
|
|
ssl->extCvSig = psMalloc(NULL, sig_len + 2); /* See below. */
|
|
|
|
}
|
|
if (ssl->extCvSig == NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
|
|
/*
|
|
struct {
|
|
digitally-signed struct {
|
|
opaque handshake_messages[handshake_messages_length];
|
|
}
|
|
} CertificateVerify;
|
|
|
|
struct {
|
|
SignatureAndHashAlgorithm algorithm;
|
|
opaque signature<0..2^16-1>;
|
|
} DigitallySigned;
|
|
*/
|
|
|
|
switch (ssl->extCvSigAlg)
|
|
{
|
|
# ifdef USE_ECC
|
|
case PS_ECC:
|
|
/*
|
|
The "signature" vector in the DigitallySigned struct
|
|
needs a two-byte length specifier (see the struct defs above).
|
|
|
|
@note For RSA, the length bytes are added already in
|
|
WriteCertificateVerify. For ECDSA, we do not know the size
|
|
of the signature at that point. That's why we need at add the
|
|
length encoding here.
|
|
|
|
@note When computing the signature internally (i.e. when
|
|
USE_EXT_CERTIFICATE_VERIFY_SIGNING is not enabled, psEccDsaSign
|
|
adds the length bytes (the includeSize parameter).
|
|
*/
|
|
ssl->extCvSig[0] = (sig_len & 0xFF00) >> 8;
|
|
ssl->extCvSig[1] = (sig_len & 0xFF);
|
|
Memcpy(ssl->extCvSig + 2, sig, sig_len);
|
|
ssl->extCvSigLen = sig_len + 2;
|
|
break;
|
|
# endif
|
|
# ifdef USE_RSA
|
|
case PS_RSA:
|
|
Memcpy(ssl->extCvSig, sig, sig_len);
|
|
ssl->extCvSigLen = sig_len;
|
|
break;
|
|
# endif
|
|
default:
|
|
psTraceIntInfo("matrixSslSetCvSignature: unsupported alg type: %d\n",
|
|
ssl->extCvSigAlg);
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
# endif /* USE_EXT_CERTIFICATE_VERIFY_SIGNING */
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
/******************************************************************************/
|
|
/*
|
|
Create a new server SSL session
|
|
This creates internal SSL buffers and cipher structures
|
|
Internal SSL state is set to expect an incoming 'HelloRequest'
|
|
|
|
Return MATRIXSSL_SUCCESS on success
|
|
< 0 on error
|
|
*/
|
|
|
|
int32 matrixSslNewServer(ssl_t **ssl,
|
|
pubkeyCb_t pubkeyCb,
|
|
pskCb_t pskCb,
|
|
sslCertCb_t certCb,
|
|
sslSessOpts_t *options)
|
|
{
|
|
int32 rc;
|
|
|
|
if ((rc = matrixSslNewServerSession(ssl, NULL,
|
|
certCb,
|
|
options)) < 0)
|
|
{
|
|
return rc;
|
|
}
|
|
|
|
(*ssl)->sec.pskCb = (pskCb_t) pskCb;
|
|
# ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
(*ssl)->sec.pubkeyCb = (pubkeyCb_t) pubkeyCb;
|
|
# endif
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
|
|
int32 matrixSslNewServerSession(ssl_t **ssl, const sslKeys_t *keys,
|
|
sslCertCb_t certCb,
|
|
sslSessOpts_t *options)
|
|
{
|
|
ssl_t *lssl;
|
|
|
|
if (!ssl)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (options == NULL)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
/* Add SERVER_FLAGS to versionFlag member of options */
|
|
options->versionFlag |= SSL_FLAGS_SERVER;
|
|
*ssl = NULL;
|
|
lssl = NULL;
|
|
|
|
# ifdef USE_CLIENT_AUTH
|
|
if (certCb)
|
|
{
|
|
options->versionFlag |= SSL_FLAGS_CLIENT_AUTH;
|
|
if (matrixSslNewSession(&lssl, keys, NULL, options) < 0)
|
|
{
|
|
goto NEW_SVR_ERROR;
|
|
}
|
|
matrixSslSetCertValidator(lssl, (sslCertCb_t) certCb);
|
|
}
|
|
else if (matrixSslNewSession(&lssl, keys, NULL, options) < 0)
|
|
{
|
|
goto NEW_SVR_ERROR;
|
|
}
|
|
# else
|
|
if (matrixSslNewSession(&lssl, keys, NULL, options) < 0)
|
|
{
|
|
goto NEW_SVR_ERROR;
|
|
}
|
|
# endif /* USE_CLIENT_AUTH */
|
|
|
|
/*
|
|
For the server, ssl->expectedName can only be populated with
|
|
the server name parsed from the Server Name Indication
|
|
extension sent by the client. Clearly, the client cert
|
|
should not be validated against that.
|
|
*/
|
|
lssl->validateCertsOpts.flags |= VCERTS_FLAG_SKIP_EXPECTED_NAME_VALIDATION;
|
|
|
|
lssl->userPtr = options->userPtr;
|
|
if (options->maxFragLen < 0)
|
|
{
|
|
/* User wants to deny a client request for changing max frag len */
|
|
lssl->extFlags.deny_max_fragment_len = 1;
|
|
}
|
|
lssl->maxPtFrag = SSL_MAX_PLAINTEXT_LEN;
|
|
|
|
if (options->truncHmac < 0)
|
|
{
|
|
lssl->extFlags.deny_truncated_hmac = 1;
|
|
}
|
|
|
|
/* Extended master secret is enabled by default. If user sets to 1 this
|
|
is a flag to REQUIRE its use */
|
|
if (options->extendedMasterSecret > 0)
|
|
{
|
|
lssl->extFlags.require_extended_master_secret = 1;
|
|
}
|
|
#ifdef USE_TLS_1_3
|
|
if (options->tls13SessionMaxEarlyData <= 16384)
|
|
{
|
|
lssl->tls13SessionMaxEarlyData = options->tls13SessionMaxEarlyData;
|
|
}
|
|
else
|
|
{
|
|
psTraceIntInfo("matrixSslNewServerSession: Too large " \
|
|
"tls13SessionMaxEarlyData: %d\n",
|
|
options->tls13SessionMaxEarlyData);
|
|
goto NEW_SVR_ERROR;
|
|
}
|
|
#endif
|
|
*ssl = lssl;
|
|
return MATRIXSSL_SUCCESS;
|
|
|
|
NEW_SVR_ERROR:
|
|
if (lssl)
|
|
{
|
|
matrixSslDeleteSession(lssl);
|
|
}
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
void matrixSslRegisterSNICallback(ssl_t *ssl, sniCb_t sni_cb)
|
|
{
|
|
ssl->sni_cb = sni_cb;
|
|
}
|
|
|
|
# ifdef USE_ALPN
|
|
void matrixSslRegisterALPNCallback(ssl_t *ssl,
|
|
void (*srv_alpn_cb)(void *ssl, short protoCount,
|
|
char *proto[MAX_PROTO_EXT], int32 protoLen[MAX_PROTO_EXT],
|
|
int32 *index))
|
|
{
|
|
ssl->srv_alpn_cb = srv_alpn_cb;
|
|
}
|
|
# endif
|
|
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Caller is asking for allocated buffer storage to recv data into
|
|
|
|
buf Populated with a transient area where data can be read into
|
|
|
|
Return > 0, size of 'buf' in bytes
|
|
<= 0 on error
|
|
*/
|
|
int32 matrixSslGetReadbuf(ssl_t *ssl, unsigned char **buf)
|
|
{
|
|
if (!ssl || !buf)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
psAssert(ssl && ssl->insize > 0 && ssl->inbuf != NULL);
|
|
/* If there's unprocessed data in inbuf, have caller append to it */
|
|
*buf = ssl->inbuf + ssl->inlen;
|
|
return ssl->insize - ssl->inlen;
|
|
}
|
|
|
|
/* If the required size is known, grow the buffer here so the caller doesn't
|
|
have to go through the REQUEST_RECV logic of matrixSslReceivedData
|
|
|
|
The return value MAY be larger than the requested size if the inbuf
|
|
is already larger than what was requested.
|
|
*/
|
|
int32 matrixSslGetReadbufOfSize(ssl_t *ssl, int32 size, unsigned char **buf)
|
|
{
|
|
unsigned char *p;
|
|
int32 res_sz;
|
|
|
|
if (!ssl || !buf)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
psAssert(ssl && ssl->insize > 0 && ssl->inbuf != NULL);
|
|
|
|
if ((ssl->insize - ssl->inlen) >= size)
|
|
{
|
|
/* Already enough room in current buffer */
|
|
return matrixSslGetReadbuf(ssl, buf);
|
|
}
|
|
|
|
/* Going to have to grow... but do we have to realloc to save data? */
|
|
if (ssl->inlen == 0)
|
|
{
|
|
/* buffer is empty anyway so can free before alloc and help keep high
|
|
water mark down */
|
|
psFree(ssl->inbuf, ssl->bufferPool);
|
|
ssl->inbuf = NULL;
|
|
if ((ssl->inbuf = psMalloc(ssl->bufferPool, size)) == NULL)
|
|
{
|
|
ssl->insize = 0;
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->insize = size;
|
|
*buf = ssl->inbuf;
|
|
res_sz = ssl->insize;
|
|
}
|
|
else
|
|
{
|
|
/* realloc with: total size = current size + requested size */
|
|
if ((p = psRealloc(ssl->inbuf, ssl->inlen + size, ssl->bufferPool))
|
|
== NULL)
|
|
{
|
|
ssl->inbuf = NULL; ssl->insize = 0; ssl->inlen = 0;
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->inbuf = p;
|
|
ssl->insize = ssl->inlen + size;
|
|
*buf = ssl->inbuf + ssl->inlen;
|
|
res_sz = size;
|
|
}
|
|
|
|
return res_sz;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Caller is asking if there is any encoded, outgoing SSL data that should be
|
|
sent out the transport layer.
|
|
|
|
buf if provided, is updated to point to the data to be sent
|
|
|
|
Return > 0, the number of bytes to send
|
|
0 if there is no pending data
|
|
< 0 on error
|
|
*/
|
|
int32 matrixSslGetOutdata(ssl_t *ssl, unsigned char **buf)
|
|
{
|
|
if (!ssl)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
if (buf)
|
|
{
|
|
*buf = ssl->outbuf;
|
|
}
|
|
return ssl->outlen; /* Can be 0 */
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Caller is asking for an allocated buffer to write plaintext into.
|
|
That plaintext will then be encoded when the caller subsequently calls
|
|
matrixSslEncodeWritebuf()
|
|
|
|
This is also explicitly called by matrixSslEncodeToOutdata
|
|
|
|
ssl SSL session context
|
|
|
|
buf The data storage to write into will be populated here on success
|
|
|
|
requestedLen The amount of buffer space the caller would like to use
|
|
|
|
Return > 0, success returns # bytes available for plaintext at buf
|
|
PS_MEM_FAIL if requiredLen too large for current memory
|
|
<= 0 on error
|
|
*/
|
|
int32 matrixSslGetWritebuf(ssl_t *ssl, unsigned char **buf, uint32 requestedLen)
|
|
{
|
|
uint32 requiredLen, sz, overhead;
|
|
|
|
# ifdef USE_DTLS
|
|
int32 pmtu;
|
|
# endif
|
|
unsigned char *p;
|
|
|
|
if (!ssl || !buf)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
|
|
# ifdef USE_BEAST_WORKAROUND
|
|
/* This is a client-only feature */
|
|
if (!(ssl->flags & SSL_FLAGS_SERVER))
|
|
{
|
|
/* Not a problem at all beginning in TLS 1.1 (version 3.2) and never
|
|
a problem on stream ciphers */
|
|
if (NGTD_VER(ssl, v_tls_need_beast_workaround)
|
|
&& (ssl->enBlockSize > 1) && (requestedLen > 1) &&
|
|
!(ssl->bFlags & BFLAG_STOP_BEAST))
|
|
{
|
|
ssl->bFlags |= BFLAG_STOP_BEAST;
|
|
}
|
|
}
|
|
# endif
|
|
|
|
/*
|
|
First thing is to ensure under the maximum allowed plaintext len according
|
|
to the SSL specification (or the negotiated max). If not, set it to the
|
|
max for the calculations and make sure that exact max is returned to the
|
|
caller. The responsibilty for fragmenting the message is left to them
|
|
*/
|
|
if (requestedLen > (uint32) ssl->maxPtFrag)
|
|
{
|
|
requestedLen = ssl->maxPtFrag;
|
|
}
|
|
|
|
/*
|
|
What is the total encoded size for a plaintext requestedLen. The overhead
|
|
includes leading header as well as trailing MAC and pad
|
|
|
|
We want to tweak the overhead an extra block to account for a
|
|
padding miscalculation in matrixSslGetEncodedSize. If that call was
|
|
made on an exact-sized message and the user decides to use a
|
|
different record size than requested, we'll need to make sure
|
|
there is enough available room for any potential padding length.
|
|
*/
|
|
requiredLen = matrixSslGetEncodedSize(ssl, requestedLen + ssl->enBlockSize);
|
|
|
|
psAssert(requiredLen >= requestedLen);
|
|
overhead = requiredLen - requestedLen;
|
|
|
|
# ifdef USE_DTLS
|
|
if (ACTV_VER(ssl, v_dtls_any))
|
|
{
|
|
pmtu = matrixDtlsGetPmtu();
|
|
if (requiredLen > (uint32) pmtu)
|
|
{
|
|
overhead = matrixSslGetEncodedSize(ssl, 0) + ssl->enBlockSize;
|
|
requiredLen = matrixSslGetEncodedSize(ssl, pmtu - overhead);
|
|
}
|
|
}
|
|
# endif
|
|
|
|
/*
|
|
Get current available space in outbuf
|
|
*/
|
|
if (ssl->outsize < ssl->outlen)
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
sz = ssl->outsize - ssl->outlen;
|
|
|
|
/*
|
|
If not enough free space for requiredLen, grow the buffer
|
|
*/
|
|
if (sz < requiredLen)
|
|
{
|
|
if ((p = psRealloc(ssl->outbuf, ssl->outsize +
|
|
(requiredLen - sz), ssl->bufferPool)) == NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->outbuf = p;
|
|
ssl->outsize = ssl->outsize + (requiredLen - sz);
|
|
/*
|
|
Recalculate available free space
|
|
*/
|
|
if (ssl->outsize < ssl->outlen)
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
sz = ssl->outsize - ssl->outlen;
|
|
}
|
|
|
|
/*
|
|
Now that requiredLen has been confirmed/created, return number of available
|
|
plaintext bytes
|
|
*/
|
|
if (requestedLen <= (uint32) ssl->maxPtFrag)
|
|
{
|
|
requestedLen = sz - overhead;
|
|
if (requestedLen > (uint32) ssl->maxPtFrag)
|
|
{
|
|
requestedLen = ssl->maxPtFrag;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Now return the pointer that has skipped past the record header
|
|
*/
|
|
# ifdef USE_TLS_1_1
|
|
/*
|
|
If a block cipher is being used TLS 1.1 requires the use
|
|
of an explicit IV. This is an extra random block of data
|
|
prepended to the plaintext before encryption. Account for
|
|
that extra length here.
|
|
*/
|
|
if ((ssl->flags & SSL_FLAGS_WRITE_SECURE) &&
|
|
NGTD_VER(ssl, v_tls_explicit_iv) && (ssl->enBlockSize > 1))
|
|
{
|
|
*buf = ssl->outbuf + ssl->outlen + ssl->recordHeadLen;
|
|
*buf += ssl->enBlockSize;
|
|
return requestedLen; /* may not be what was passed in */
|
|
}
|
|
/* GCM mode will need to save room for the nonce */
|
|
if (ssl->flags & SSL_FLAGS_AEAD_W)
|
|
{
|
|
*buf = ssl->outbuf + ssl->outlen + ssl->recordHeadLen;
|
|
if (!(USING_TLS_1_3(ssl)))
|
|
{
|
|
*buf += AEAD_NONCE_LEN(ssl);
|
|
}
|
|
return requestedLen; /* may not be what was passed in */
|
|
}
|
|
# endif /* USE_TLS_1_1 */
|
|
|
|
# ifdef USE_BEAST_WORKAROUND
|
|
if (ssl->bFlags & BFLAG_STOP_BEAST)
|
|
{
|
|
/* The reserved space accounts for a full encoding of a 1 byte record.
|
|
The final -1 is so that when the second encrypt arrives it will
|
|
land as an in-situ */
|
|
overhead = ((ssl->enMacSize + 1) % ssl->enBlockSize) ?
|
|
ssl->enBlockSize : 0;
|
|
*buf = ssl->outbuf + ssl->outlen + (2 * ssl->recordHeadLen) + overhead +
|
|
(ssl->enBlockSize * ((ssl->enMacSize + 1) / ssl->enBlockSize)) - 1;
|
|
}
|
|
else
|
|
{
|
|
*buf = ssl->outbuf + ssl->outlen + ssl->recordHeadLen;
|
|
}
|
|
# else
|
|
*buf = ssl->outbuf + ssl->outlen + ssl->recordHeadLen;
|
|
# endif
|
|
return requestedLen; /* may not be what was passed in */
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
ptBuf = plaintext buffer
|
|
ptLen = length of plaintext in bytes
|
|
ctBuf = allocated ciphertext destination buffer
|
|
ctLen = INPUT ctBuf buffer size and OUTPUT is length of ciphertext
|
|
|
|
Return value = SUCCESS is > 0 and FAILURE is < 0
|
|
*/
|
|
int32 matrixSslEncodeToUserBuf(ssl_t *ssl, unsigned char *ptBuf, uint32 ptLen,
|
|
unsigned char *ctBuf, uint32 *ctLen)
|
|
{
|
|
int32 rc;
|
|
|
|
rc = matrixSslEncode(ssl, ctBuf, *ctLen, ptBuf, &ptLen);
|
|
if (rc > 0)
|
|
{
|
|
*ctLen = ptLen;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Encode (encrypt) 'len' bytes of plaintext data that has been placed into
|
|
the buffer given by matrixSslGetWritebuf(). This is an in-situ encode.
|
|
|
|
CAN ONLY BE CALLED AFTER A PREVIOUS CALL TO matrixSslGetWritebuf
|
|
|
|
len >= 0.If len is zero, we send out a blank ssl record
|
|
len must be <= size returned by matrixSslGetWritebuf()
|
|
|
|
Returns < 0 on error, total #bytes in outgoing data buf on success
|
|
*/
|
|
int32 matrixSslEncodeWritebuf(ssl_t *ssl, uint32 len)
|
|
{
|
|
unsigned char *origbuf;
|
|
int32 rc, reserved;
|
|
|
|
if (!ssl || ((int32) len < 0))
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (ssl->bFlags & BFLAG_CLOSE_AFTER_SENT)
|
|
{
|
|
return PS_PROTOCOL_FAIL;
|
|
}
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
/* Caller was given proper locations and lengths in GetWritebuf() */
|
|
origbuf = ssl->outbuf + ssl->outlen;
|
|
if (ssl->outbuf == NULL || (ssl->outsize - ssl->outlen) < (int32) len)
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
reserved = ssl->recordHeadLen;
|
|
# ifdef USE_BEAST_WORKAROUND
|
|
if (ssl->bFlags & BFLAG_STOP_BEAST)
|
|
{
|
|
rc = ((ssl->enMacSize + 1) % ssl->enBlockSize) ? ssl->enBlockSize : 0;
|
|
reserved += ssl->recordHeadLen + rc +
|
|
(ssl->enBlockSize * ((ssl->enMacSize + 1) / ssl->enBlockSize)) - 1;
|
|
}
|
|
# endif
|
|
|
|
# ifdef USE_TLS_1_1
|
|
/*
|
|
If a block cipher is being used TLS 1.1 requires the use
|
|
of an explicit IV. This is an extra random block of data
|
|
prepended to the plaintext before encryption. Account for
|
|
that extra length here.
|
|
*/
|
|
if ((ssl->flags & SSL_FLAGS_WRITE_SECURE) &&
|
|
ACTV_VER(ssl, v_tls_explicit_iv) && (ssl->enBlockSize > 1))
|
|
{
|
|
reserved += ssl->enBlockSize;
|
|
}
|
|
if ((ssl->flags & SSL_FLAGS_AEAD_W) && !(USING_TLS_1_3(ssl)))
|
|
{
|
|
reserved += AEAD_NONCE_LEN(ssl);
|
|
}
|
|
# endif /* USE_TLS_1_1 */
|
|
|
|
rc = matrixSslEncode(ssl, origbuf, (ssl->outsize - ssl->outlen),
|
|
origbuf + reserved, &len);
|
|
if (rc < 0)
|
|
{
|
|
psAssert(rc != SSL_FULL); /* should not happen */
|
|
return PS_FAILURE;
|
|
}
|
|
# ifdef USE_MATRIXSSL_STATS
|
|
matrixsslUpdateStat(ssl, APP_DATA_SENT_STAT, len);
|
|
# endif
|
|
ssl->outlen += len;
|
|
return ssl->outlen;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
This public API allows the user to encrypt the plaintext buffer of their
|
|
choice into the internal outbuf that is retrieved when matrixSslGetOutdata
|
|
is called. This is non-in-situ support and will leave the callers
|
|
plaintext buffer intact
|
|
|
|
ptBuf The plaintext buffer to be converted into an SSL application data
|
|
record.
|
|
len The length, in bytes, of the ptBuf plaintext data
|
|
|
|
Returns < 0 on error, total #bytes in outgoing data buf on success
|
|
*/
|
|
int32 matrixSslEncodeToOutdata(ssl_t *ssl, unsigned char *ptBuf, uint32 len)
|
|
{
|
|
unsigned char *internalBuf;
|
|
int32 rc, fragLen, recLen, index;
|
|
|
|
if (!ssl || !ptBuf)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (ssl->bFlags & BFLAG_CLOSE_AFTER_SENT)
|
|
{
|
|
return PS_PROTOCOL_FAIL;
|
|
}
|
|
|
|
# ifdef USE_DTLS
|
|
if (ACTV_VER(ssl, v_dtls_any))
|
|
{
|
|
rc = matrixSslGetEncodedSize(ssl, len);
|
|
if (rc > matrixDtlsGetPmtu())
|
|
{
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
}
|
|
# endif
|
|
|
|
/* Fragmentation support */
|
|
index = 0;
|
|
while (len > 0)
|
|
{
|
|
/* We just call matrixSslGetWritebuf to prepare the buffer */
|
|
if ((rc = matrixSslGetWritebuf(ssl, &internalBuf, len)) < 0)
|
|
{
|
|
psTraceIntInfo("matrixSslEncodeToOutbuf allocation error: %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
recLen = fragLen = min((uint32) rc, len);
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
|
|
if (ssl->outbuf == NULL ||
|
|
(ssl->outsize - ssl->outlen) < (int32) fragLen)
|
|
{
|
|
return PS_FAILURE;
|
|
}
|
|
internalBuf = ssl->outbuf + ssl->outlen;
|
|
|
|
rc = matrixSslEncode(ssl, internalBuf, (ssl->outsize - ssl->outlen),
|
|
ptBuf + index, (uint32 *) &fragLen);
|
|
if (rc < 0)
|
|
{
|
|
psAssert(rc != SSL_FULL); /* should not happen */
|
|
return PS_FAILURE;
|
|
}
|
|
index += recLen;
|
|
len -= recLen;
|
|
# ifdef USE_MATRIXSSL_STATS
|
|
matrixsslUpdateStat(ssl, APP_DATA_SENT_STAT, fragLen);
|
|
# endif
|
|
ssl->outlen += fragLen;
|
|
}
|
|
return ssl->outlen;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Helper to shrink buffers down to default size
|
|
*/
|
|
#define SSL_INBUF 0
|
|
#define SSL_OUTBUF 1
|
|
|
|
static void revertToDefaultBufsize(ssl_t *ssl, uint16 inOrOut)
|
|
{
|
|
int32 defaultSize;
|
|
|
|
unsigned char *p;
|
|
|
|
if (inOrOut == SSL_INBUF)
|
|
{
|
|
#ifdef USE_DTLS
|
|
if (ACTV_VER(ssl, v_dtls_any))
|
|
{
|
|
defaultSize = matrixDtlsGetPmtu();
|
|
}
|
|
else
|
|
{
|
|
defaultSize = SSL_DEFAULT_IN_BUF_SIZE;
|
|
}
|
|
#else
|
|
defaultSize = SSL_DEFAULT_IN_BUF_SIZE;
|
|
#endif
|
|
if (ssl->insize > defaultSize && ssl->inlen < defaultSize)
|
|
{
|
|
/* It's not fatal if we can't realloc it smaller */
|
|
if ((p = psRealloc(ssl->inbuf, defaultSize, ssl->bufferPool))
|
|
!= NULL)
|
|
{
|
|
ssl->inbuf = p;
|
|
ssl->insize = defaultSize;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef USE_DTLS
|
|
if (ACTV_VER(ssl, v_dtls_any))
|
|
{
|
|
defaultSize = matrixDtlsGetPmtu();
|
|
}
|
|
else
|
|
{
|
|
defaultSize = SSL_DEFAULT_OUT_BUF_SIZE;
|
|
}
|
|
#else
|
|
defaultSize = SSL_DEFAULT_OUT_BUF_SIZE;
|
|
#endif
|
|
if (ssl->outsize > defaultSize && ssl->outlen < defaultSize)
|
|
{
|
|
/* It's not fatal if we can't realloc it smaller */
|
|
if ((p = psRealloc(ssl->outbuf, defaultSize, ssl->bufferPool))
|
|
!= NULL)
|
|
{
|
|
ssl->outbuf = p;
|
|
ssl->outsize = defaultSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Caller has received data from the network and is notifying the SSL layer
|
|
*/
|
|
int32 matrixSslReceivedData(ssl_t *ssl, uint32 bytes, unsigned char **ptbuf,
|
|
uint32 *ptlen)
|
|
{
|
|
unsigned char *buf, *prevBuf;
|
|
int32 rc, decodeRet, size, sanity, decodeErr;
|
|
uint32 processed, start, len, reqLen;
|
|
unsigned char alertLevel, alertDesc;
|
|
|
|
unsigned char *p;
|
|
|
|
if (!ssl || !ptbuf || !ptlen)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
psAssert(ssl->insize > 0 && ssl->inbuf != NULL);
|
|
*ptbuf = NULL;
|
|
*ptlen = 0;
|
|
ssl->inlen += bytes;
|
|
if (ssl->inlen == 0)
|
|
{
|
|
return PS_SUCCESS; /* Nothing to do. Basically a poll */
|
|
}
|
|
/* This is outside the loop b/c we may want to parse within inbuf later */
|
|
buf = ssl->inbuf;
|
|
DECODE_MORE:
|
|
/* Parameterized sanity check to avoid infinite loops */
|
|
if (matrixSslHandshakeIsComplete(ssl))
|
|
{
|
|
/* Minimum possible record size once negotiated */
|
|
sanity = ssl->inlen / (SSL3_HEADER_LEN + MD5_HASH_SIZE);
|
|
}
|
|
else
|
|
{
|
|
/* Even with an SSLv2 hello, the sanity check will let 1 pass through */
|
|
sanity = ssl->inlen / (SSL3_HEADER_LEN + SSL3_HANDSHAKE_HEADER_LEN);
|
|
}
|
|
if (sanity-- < 0)
|
|
{
|
|
return PS_PROTOCOL_FAIL; /* We've tried to decode too many times */
|
|
}
|
|
len = ssl->inlen;
|
|
size = ssl->insize - (buf - ssl->inbuf);
|
|
prevBuf = buf;
|
|
decodeRet = matrixSslDecode(ssl, &buf, &len, size, &start, &reqLen,
|
|
&decodeErr, &alertLevel, &alertDesc);
|
|
|
|
#if defined(USE_HARDWARE_CRYPTO_RECORD) || defined(USE_HARDWARE_CRYPTO_PKA) || defined(USE_EXT_CLIENT_CERT_KEY_LOADING)
|
|
if (decodeRet == PS_PENDING || decodeRet == PS_EAGAIN)
|
|
{
|
|
return decodeRet;
|
|
}
|
|
#endif
|
|
/*
|
|
Convenience for the cases that expect buf to have moved
|
|
- calculate the number of encoded bytes that were decoded
|
|
*/
|
|
processed = buf - prevBuf;
|
|
rc = PS_PROTOCOL_FAIL;
|
|
switch (decodeRet)
|
|
{
|
|
|
|
case MATRIXSSL_SUCCESS:
|
|
|
|
ssl->inlen -= processed;
|
|
if (ssl->inlen > 0)
|
|
{
|
|
psAssert(buf > ssl->inbuf);
|
|
/*
|
|
Pack ssl->inbuf so there is immediate maximum room for potential
|
|
outgoing data that needs to be written
|
|
*/
|
|
Memmove(ssl->inbuf, buf, ssl->inlen);
|
|
buf = ssl->inbuf;
|
|
goto DECODE_MORE; /* More data in buffer to process */
|
|
}
|
|
/*
|
|
In this case, we've parsed a finished message and no additional data is
|
|
available to parse. We let the client know the handshake is complete,
|
|
which can be used as a trigger to begin for example a HTTP request.
|
|
*/
|
|
if (!(ssl->bFlags & BFLAG_HS_COMPLETE))
|
|
{
|
|
if (matrixSslHandshakeIsComplete(ssl))
|
|
{
|
|
ssl->bFlags |= BFLAG_HS_COMPLETE;
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
matrixSslGetSessionId(ssl, ssl->sid);
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
rc = MATRIXSSL_HANDSHAKE_COMPLETE;
|
|
}
|
|
else
|
|
{
|
|
/* Need to recv more handshake data */
|
|
rc = MATRIXSSL_REQUEST_RECV;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if defined(USE_DTLS) || defined(USE_TLS_1_3)
|
|
if (USING_TLS_1_3(ssl))
|
|
{
|
|
if (matrixSslHandshakeIsComplete(ssl))
|
|
{
|
|
rc = MATRIXSSL_HANDSHAKE_COMPLETE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* DTLS: Got FINISHED without CCS */
|
|
rc = MATRIXSSL_REQUEST_RECV;
|
|
}
|
|
#else
|
|
/* This is an error - we shouldn't get here */
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
#ifdef USE_DTLS
|
|
case DTLS_RETRANSMIT:
|
|
/* Only request a resend if last record in buffer */
|
|
ssl->inlen -= processed;
|
|
if (ssl->inlen > 0)
|
|
{
|
|
psAssert(buf > ssl->inbuf);
|
|
/*
|
|
Pack ssl->inbuf so there is immediate maximum room for potential
|
|
outgoing data that needs to be written
|
|
*/
|
|
Memmove(ssl->inbuf, buf, ssl->inlen);
|
|
buf = ssl->inbuf;
|
|
goto DECODE_MORE; /* More data in buffer to process */
|
|
}
|
|
|
|
/* Flight will be rebuilt when matrixDtlsGetOutdata is called while
|
|
outbuf is empty. This is the return case where we are actually
|
|
seeing a repeat handshake message so we know something was lost in
|
|
flight. */
|
|
return MATRIXSSL_REQUEST_SEND;
|
|
#endif
|
|
|
|
case SSL_SEND_RESPONSE:
|
|
#ifdef ENABLE_FALSE_START
|
|
/*
|
|
If FALSE START is supported, there may be APPLICATION_DATA directly
|
|
following the FINISHED message, even though we haven't sent our
|
|
CHANGE_CIPHER_SPEC or FINISHED message. This is signalled by buf
|
|
having been moved forward, and our response being put directly into
|
|
ssl->outbuf, rather than in buf (ssl->inbuf). Return a REQUEST_SEND
|
|
so that the data in outbuf is flushed before the remaining data in
|
|
ssl->inbuf is parsed.
|
|
*/
|
|
if ((ssl->flags & SSL_FLAGS_FALSE_START) && buf != prevBuf)
|
|
{
|
|
ssl->inlen -= processed;
|
|
psAssert(ssl->inlen > 0);
|
|
psAssert((uint32) ssl->inlen == start);
|
|
psAssert(buf > ssl->inbuf);
|
|
Memmove(ssl->inbuf, buf, ssl->inlen); /* Pack ssl->inbuf */
|
|
buf = ssl->inbuf;
|
|
return MATRIXSSL_REQUEST_SEND;
|
|
}
|
|
#endif
|
|
/*
|
|
This must be handshake data (or alert) or we'd be in PROCESS_DATA
|
|
so there is no way there is anything left inside inbuf to process.
|
|
...so processed isn't valid because the output params are outbuf
|
|
related and we simply reset inlen
|
|
*/
|
|
ssl->inlen = 0;
|
|
|
|
/* If alert, close connection after sending */
|
|
if (alertDesc != SSL_ALERT_NONE)
|
|
{
|
|
ssl->bFlags |= BFLAG_CLOSE_AFTER_SENT;
|
|
}
|
|
psAssert(ssl->insize >= (int32) len);
|
|
psAssert(start == 0);
|
|
if (!USING_TLS_1_3(ssl))
|
|
{
|
|
psAssert(prevBuf == buf);
|
|
psAssert(buf == ssl->inbuf);
|
|
}
|
|
if (ssl->outlen > 0)
|
|
{
|
|
/* If data's in outbuf, append inbuf. This is a corner case that
|
|
can happen if application data is queued but then incoming data
|
|
is processed and discovered to be a re-handshake request.
|
|
matrixSslDecode will have constructed the response flight but
|
|
we don't want to forget about the app data we haven't sent */
|
|
if (ssl->outlen + (int32) len > ssl->outsize)
|
|
{
|
|
if ((p = psRealloc(ssl->outbuf, ssl->outlen + len,
|
|
ssl->bufferPool)) == NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->outbuf = p;
|
|
ssl->outsize = ssl->outlen + len;
|
|
}
|
|
Memcpy(ssl->outbuf + ssl->outlen, ssl->inbuf, len);
|
|
ssl->outlen += len;
|
|
}
|
|
else /* otherwise, swap inbuf and outbuf */
|
|
{
|
|
buf = ssl->outbuf; ssl->outbuf = ssl->inbuf; ssl->inbuf = buf;
|
|
ssl->outlen = len;
|
|
len = ssl->outsize; ssl->outsize = ssl->insize; ssl->insize = len;
|
|
buf = ssl->inbuf;
|
|
len = ssl->outlen;
|
|
}
|
|
rc = MATRIXSSL_REQUEST_SEND; /* We queued data to send out */
|
|
break;
|
|
|
|
case MATRIXSSL_ERROR:
|
|
if (decodeErr >= 0)
|
|
{
|
|
/* Printf("THIS SHOULD BE A NEGATIVE VALUE?\n"); */
|
|
}
|
|
return decodeErr; /* Will be a negative value */
|
|
|
|
case SSL_ALERT:
|
|
if (alertLevel == SSL_ALERT_LEVEL_FATAL)
|
|
{
|
|
psTraceIntInfo("Received FATAL alert %d.\n", alertDesc);
|
|
}
|
|
else
|
|
{
|
|
/* Closure notify is the normal case */
|
|
if (alertDesc == SSL_ALERT_CLOSE_NOTIFY)
|
|
{
|
|
psTraceInfo("Normal SSL closure alert\n");
|
|
}
|
|
else
|
|
{
|
|
psTraceIntInfo("Received WARNING alert %d\n", alertDesc);
|
|
}
|
|
}
|
|
/* Let caller access the 2 data bytes (severity and description) */
|
|
# ifdef USE_TLS_1_1
|
|
/* Been ignoring the explicit IV up to this final return point. */
|
|
if ((ssl->flags & SSL_FLAGS_READ_SECURE) &&
|
|
ACTV_VER(ssl, v_tls_explicit_iv) && (ssl->enBlockSize > 1))
|
|
{
|
|
prevBuf += ssl->enBlockSize;
|
|
}
|
|
# endif /* USE_TLS_1_1 */
|
|
psAssert(len == 2);
|
|
*ptbuf = prevBuf;
|
|
*ptlen = len;
|
|
ssl->inlen -= processed;
|
|
return MATRIXSSL_RECEIVED_ALERT;
|
|
|
|
case SSL_PARTIAL:
|
|
if (reqLen > SSL_MAX_BUF_SIZE)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
if (reqLen > (uint32) ssl->insize)
|
|
{
|
|
if ((p = psRealloc(ssl->inbuf, reqLen, ssl->bufferPool)) == NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->inbuf = p;
|
|
ssl->insize = reqLen;
|
|
buf = ssl->inbuf;
|
|
/* Don't need to change inlen */
|
|
}
|
|
|
|
rc = MATRIXSSL_REQUEST_RECV; /* Expecting more data */
|
|
break;
|
|
|
|
/* We've got outgoing data that's larger than our buffer */
|
|
case SSL_FULL:
|
|
if (reqLen > SSL_MAX_BUF_SIZE)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
/*
|
|
Can't envision any possible case where there is remaining data
|
|
in inbuf to process and are getting SSL_FULL.
|
|
*/
|
|
ssl->inlen = 0;
|
|
|
|
/* Grow inbuf */
|
|
if (reqLen > (uint32) ssl->insize)
|
|
{
|
|
len = ssl->inbuf - buf;
|
|
if ((p = psRealloc(ssl->inbuf, reqLen, ssl->bufferPool)) == NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
psTraceIntInfo("** New ssl->inbuf size : %d **\n", reqLen);
|
|
ssl->inbuf = p;
|
|
ssl->insize = reqLen;
|
|
buf = ssl->inbuf + len;
|
|
/* Note we leave inlen untouched here */
|
|
}
|
|
else
|
|
{
|
|
psTraceErrr("Encoding error. Possible wrong flight messagSize\n");
|
|
return PS_PROTOCOL_FAIL; /* error in our encoding */
|
|
}
|
|
goto DECODE_MORE;
|
|
|
|
case SSL_PROCESS_DATA:
|
|
/*
|
|
Possible we received a finished message and app data in the same
|
|
flight. In this case, the caller is not notified that the handshake
|
|
is complete, but rather is notified that there is application data to
|
|
process.
|
|
*/
|
|
if (!(ssl->bFlags & BFLAG_HS_COMPLETE) &&
|
|
matrixSslHandshakeIsComplete(ssl))
|
|
{
|
|
ssl->bFlags |= BFLAG_HS_COMPLETE;
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
matrixSslGetSessionId(ssl, ssl->sid);
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
}
|
|
/*
|
|
. prevbuf points to start of unencrypted data
|
|
. buf points to start of any remaining unencrypted data
|
|
. start is length of remaining encrypted data yet to decode
|
|
. len is length of unencrypted data ready for user processing
|
|
*/
|
|
ssl->inlen -= processed;
|
|
psAssert((uint32) ssl->inlen == start);
|
|
|
|
/* Call user plaintext data handler */
|
|
#ifdef USE_TLS_1_1
|
|
/* Been ignoring the explicit IV up to this final return point. */
|
|
/* NOTE: This test has been on enBlockSize for a very long time but
|
|
it looks like it should be on deBlockSize since this a decryption.
|
|
Changed and added an assert to see if these ever don't match */
|
|
psAssert(ssl->enBlockSize == ssl->deBlockSize);
|
|
if ((ssl->flags & SSL_FLAGS_READ_SECURE) &&
|
|
ACTV_VER(ssl, v_tls_explicit_iv) && (ssl->deBlockSize > 1))
|
|
{
|
|
len -= ssl->deBlockSize;
|
|
prevBuf += ssl->deBlockSize;
|
|
}
|
|
/* END enBlockSize to deBlockSize change */
|
|
#endif /* USE_TLS_1_1 */
|
|
*ptbuf = prevBuf;
|
|
*ptlen = len;
|
|
#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 getting app data we
|
|
know for certain we are out of the hs states. Testing HandshakeComplete
|
|
is not enough because you never know if the other side got FINISHED.
|
|
*/
|
|
if (ACTV_VER(ssl, v_dtls_any))
|
|
{
|
|
ssl->appDataExch = 1;
|
|
}
|
|
#endif
|
|
#ifdef USE_ZLIB_COMPRESSION
|
|
if (ssl->compression > 0)
|
|
{
|
|
return MATRIXSSL_APP_DATA_COMPRESSED;
|
|
}
|
|
#endif
|
|
return MATRIXSSL_APP_DATA;
|
|
} /* switch decodeRet */
|
|
|
|
if (ssl->inlen > 0 && (buf != ssl->inbuf))
|
|
{
|
|
psAssert(0);
|
|
}
|
|
/*
|
|
Shrink inbuf to default size once inlen < default size, and we aren't
|
|
expecting any more data in the buffer. If SSL_PARTIAL, don't shrink the
|
|
buffer, since we expect to fill it up shortly.
|
|
*/
|
|
if (decodeRet != SSL_PARTIAL)
|
|
{
|
|
revertToDefaultBufsize(ssl, SSL_INBUF);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Plaintext data has been processed as a response to MATRIXSSL_APP_DATA or
|
|
MATRIXSSL_RECEIVED_ALERT return codes from matrixSslReceivedData()
|
|
Return:
|
|
< 0 on error
|
|
0 if there is no more incoming ssl data in the buffer
|
|
Caller should take whatever action is appropriate to the specific
|
|
protocol implementation, eg. read for more data, close, etc.
|
|
> 0 error code is same meaning as from matrixSslReceivedData()
|
|
In this case, ptbuf and ptlen will be modified and caller should
|
|
handle return code identically as from matrixSslReceivedData()
|
|
This is the case when more than one SSL record is in the buffer
|
|
*/
|
|
int32 matrixSslProcessedData(ssl_t *ssl, unsigned char **ptbuf, uint32 *ptlen)
|
|
{
|
|
uint32 ctlen;
|
|
|
|
if (!ssl || !ptbuf || !ptlen)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
*ptbuf = NULL;
|
|
*ptlen = 0;
|
|
|
|
psAssert(ssl->insize > 0 && ssl->inbuf != NULL);
|
|
/* Move any remaining data to the beginning of the buffer */
|
|
if (ssl->inbuf && ssl->inlen > 0)
|
|
{
|
|
ctlen = ssl->rec.len + ssl->recordHeadLen;
|
|
if (ssl->flags & SSL_FLAGS_AEAD_R)
|
|
{
|
|
/* This overhead was removed from rec.len after the decryption
|
|
to keep buffer logic working. */
|
|
if (!(USING_TLS_1_3(ssl)))
|
|
{
|
|
ctlen += AEAD_TAG_LEN(ssl);
|
|
ctlen += AEAD_NONCE_LEN(ssl);
|
|
}
|
|
}
|
|
Memmove(ssl->inbuf, ssl->inbuf + ctlen, ssl->inlen);
|
|
}
|
|
/* Shrink inbuf to default size once inlen < default size */
|
|
revertToDefaultBufsize(ssl, SSL_INBUF);
|
|
|
|
/* If there's more data, try to decode it here and return that code */
|
|
if (ssl->inlen > 0)
|
|
{
|
|
/* NOTE: ReceivedData cannot return 0 */
|
|
return matrixSslReceivedData(ssl, 0, ptbuf, ptlen);
|
|
}
|
|
#ifdef USE_TLS_1_3
|
|
/* There might be outgoing handshake data in outbuf in case of TLS1.3
|
|
early data */
|
|
if (ssl->outlen > 0)
|
|
{
|
|
return MATRIXSSL_REQUEST_SEND;
|
|
}
|
|
else if (!matrixSslHandshakeIsComplete(ssl))
|
|
{
|
|
/* We have supplied early_data to caller and there is nothing
|
|
in outbuf or inbuf (checked above) and handshake is not completed.
|
|
Need more data. */
|
|
return MATRIXSSL_REQUEST_RECV;
|
|
}
|
|
|
|
#endif
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Returns < 0 on error
|
|
*/
|
|
int32 matrixSslEncodeClosureAlert(ssl_t *ssl)
|
|
{
|
|
sslBuf_t sbuf;
|
|
int32 rc;
|
|
uint32 reqLen, newLen;
|
|
|
|
unsigned char *p;
|
|
|
|
if (!ssl)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
/*
|
|
Only encode the closure alert if we aren't already flagged for close
|
|
If we are flagged, we do not want to send any more data
|
|
*/
|
|
newLen = 0;
|
|
if (!(ssl->bFlags & BFLAG_CLOSE_AFTER_SENT))
|
|
{
|
|
ssl->bFlags |= BFLAG_CLOSE_AFTER_SENT;
|
|
L_CLOSUREALERT:
|
|
sbuf.buf = sbuf.start = sbuf.end = ssl->outbuf + ssl->outlen;
|
|
sbuf.size = ssl->outsize - ssl->outlen;
|
|
rc = sslEncodeClosureAlert(ssl, &sbuf, &reqLen);
|
|
if (rc == SSL_FULL && newLen == 0)
|
|
{
|
|
newLen = ssl->outlen + reqLen;
|
|
if ((p = psRealloc(ssl->outbuf, newLen, ssl->bufferPool)) == NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->outbuf = p;
|
|
ssl->outsize = newLen;
|
|
goto L_CLOSUREALERT; /* Try one more time */
|
|
}
|
|
else if (rc != PS_SUCCESS)
|
|
{
|
|
return rc;
|
|
}
|
|
ssl->outlen += sbuf.end - sbuf.start;
|
|
}
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
|
|
# ifdef SSL_REHANDSHAKES_ENABLED
|
|
/******************************************************************************/
|
|
/*
|
|
Encode a CLIENT_HELLO or HELLO_REQUEST to re-handshake an existing
|
|
connection.
|
|
|
|
Can't "downgrade" the re-handshake. This means if keys or certCb are
|
|
NULL we stick with whatever the session already has loaded.
|
|
|
|
keys should be NULL if no change in key material is being made
|
|
|
|
cipherSpec is only used by clients
|
|
*/
|
|
int32_t matrixSslEncodeRehandshake(ssl_t *ssl, sslKeys_t *keys,
|
|
int32 (*certCb)(ssl_t *ssl, psX509Cert_t *cert, int32 alert),
|
|
uint32 sessionOption,
|
|
const psCipher16_t cipherSpec[], uint8_t cipherSpecLen)
|
|
{
|
|
sslBuf_t sbuf;
|
|
int32 rc, i;
|
|
uint32 reqLen, newLen;
|
|
|
|
unsigned char *p;
|
|
sslSessOpts_t options;
|
|
|
|
/* Clear extFlags for rehandshakes */
|
|
ssl->extFlags.truncated_hmac = 0;
|
|
ssl->extFlags.sni = 0;
|
|
|
|
if (ssl == NULL || ssl->cipher == NULL)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (cipherSpecLen > 0 && (cipherSpec == NULL || cipherSpec[0] == 0))
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (ssl->bFlags & BFLAG_CLOSE_AFTER_SENT)
|
|
{
|
|
return PS_PROTOCOL_FAIL;
|
|
}
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
|
|
# ifdef DISABLE_DTLS_CLIENT_CHANGE_CIPHER_FROM_GCM_TO_GCM
|
|
# endif /* DISABLE_DTLS_CLIENT_CHANGE_CIPHER_FROM_GCM_TO_GCM */
|
|
|
|
# ifdef USE_ZLIB_COMPRESSION
|
|
/* Re-handshakes are not currently supported for compressed sessions. */
|
|
if (ssl->compression > 0)
|
|
{
|
|
psTraceErrr("Re-handshakes not supported for compressed sessions\n");
|
|
return PS_UNSUPPORTED_FAIL;
|
|
}
|
|
# endif
|
|
/*
|
|
The only explicit option that can be passsed in is
|
|
SSL_OPTION_FULL_HANDSHAKE to indicate no resumption is allowed
|
|
*/
|
|
if (sessionOption == SSL_OPTION_FULL_HANDSHAKE)
|
|
{
|
|
matrixSslSetSessionOption(ssl, sessionOption, NULL);
|
|
}
|
|
/*
|
|
If the key material or cert callback are provided we have to assume it
|
|
was intentional to "upgrade" the re-handshake and we force full handshake
|
|
No big overhead calling SetSessionOption with FULL_HS multiple times.
|
|
*/
|
|
if (keys != NULL)
|
|
{
|
|
ssl->keys = keys;
|
|
matrixSslSetSessionOption(ssl, SSL_OPTION_FULL_HANDSHAKE, NULL);
|
|
}
|
|
|
|
# ifdef USE_TLS_1_3
|
|
/*
|
|
Otherwise we'll keep adding more to the list on each rehandshake.
|
|
Note that elliptic_curves entries are added to the list even
|
|
when not using TLS 1.3 (rehandshakes are not allowed with TLS 1.3).*/
|
|
tls13ClearPeerSupportedGroupList(ssl);
|
|
# endif
|
|
|
|
# ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
if (certCb != NULL)
|
|
{
|
|
matrixSslSetSessionOption(ssl, SSL_OPTION_FULL_HANDSHAKE, NULL);
|
|
# if defined(USE_CLIENT_AUTH) || defined(USE_CLIENT_SIDE_SSL)
|
|
matrixSslSetCertValidator(ssl, certCb);
|
|
# endif /* USE_CLIENT_AUTH || USE_CLIENT_SIDE_SSL */
|
|
# if defined(USE_CLIENT_AUTH) && defined(USE_SERVER_SIDE_SSL)
|
|
/*
|
|
If server, a certCb is an explicit flag to set client auth just as
|
|
it is in matrixSslNewServerSession
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_SERVER)
|
|
{
|
|
matrixSslSetSessionOption(ssl, SSL_OPTION_ENABLE_CLIENT_AUTH, NULL);
|
|
}
|
|
# endif /* USE_CLIENT_AUTH && USE_SERVER_SIDE_SSL */
|
|
}
|
|
# endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
|
|
/*
|
|
If cipher spec is explicitly different from current, force a full handshake
|
|
*/
|
|
if (!(ssl->flags & SSL_FLAGS_SERVER))
|
|
{
|
|
rc = 0;
|
|
if (cipherSpecLen > 0)
|
|
{
|
|
rc = 1;
|
|
for (i = 0; i < cipherSpecLen; i++)
|
|
{
|
|
if (cipherSpec[i] == ssl->cipher->ident)
|
|
{
|
|
rc = 0;
|
|
}
|
|
}
|
|
}
|
|
if (rc)
|
|
{
|
|
matrixSslSetSessionOption(ssl, SSL_OPTION_FULL_HANDSHAKE, NULL);
|
|
}
|
|
}
|
|
# ifdef USE_DTLS
|
|
if (ACTV_VER(ssl, v_dtls_any))
|
|
{
|
|
/* Resend epoch should be brought up-to-date with new epoch */
|
|
ssl->resendEpoch[0] = ssl->epoch[0];
|
|
ssl->resendEpoch[1] = ssl->epoch[1];
|
|
|
|
ssl->msn = ssl->resendMsn = 0;
|
|
}
|
|
# endif /* USE_DTLS */
|
|
/*
|
|
Options are set. Encode the HELLO message
|
|
*/
|
|
newLen = 0;
|
|
L_REHANDSHAKE:
|
|
if (ssl->flags & SSL_FLAGS_SERVER)
|
|
{
|
|
sbuf.buf = sbuf.start = sbuf.end = ssl->outbuf + ssl->outlen;
|
|
sbuf.size = ssl->outsize - ssl->outlen;
|
|
if ((rc = matrixSslEncodeHelloRequest(ssl, &sbuf, &reqLen)) < 0)
|
|
{
|
|
if (rc == SSL_FULL && newLen == 0)
|
|
{
|
|
newLen = ssl->outlen + reqLen;
|
|
if (newLen < SSL_MAX_BUF_SIZE)
|
|
{
|
|
if ((p = psRealloc(ssl->outbuf, newLen, ssl->bufferPool))
|
|
== NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->outbuf = p;
|
|
ssl->outsize = newLen;
|
|
goto L_REHANDSHAKE;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sbuf.buf = sbuf.start = sbuf.end = ssl->outbuf + ssl->outlen;
|
|
sbuf.size = ssl->outsize - ssl->outlen;
|
|
Memset(&options, 0x0, sizeof(sslSessOpts_t));
|
|
# ifdef USE_ECC_CIPHER_SUITE
|
|
options.ecFlags = ssl->ecInfo.ecFlags;
|
|
# endif
|
|
/* Use extended master secret if original connection used it */
|
|
if (ssl->extFlags.extended_master_secret == 1)
|
|
{
|
|
options.extendedMasterSecret = 1;
|
|
ssl->extFlags.extended_master_secret = 0;
|
|
}
|
|
else
|
|
{
|
|
options.extendedMasterSecret = -1;
|
|
}
|
|
|
|
if ((rc = matrixSslEncodeClientHello(ssl, &sbuf, cipherSpec,
|
|
cipherSpecLen, &reqLen, NULL, &options)) < 0)
|
|
{
|
|
if (rc == SSL_FULL && newLen == 0)
|
|
{
|
|
newLen = ssl->outlen + reqLen;
|
|
if (newLen < SSL_MAX_BUF_SIZE)
|
|
{
|
|
if ((p = psRealloc(ssl->outbuf, newLen, ssl->bufferPool))
|
|
== NULL)
|
|
{
|
|
return PS_MEM_FAIL;
|
|
}
|
|
ssl->outbuf = p;
|
|
ssl->outsize = newLen;
|
|
goto L_REHANDSHAKE;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
}
|
|
ssl->outlen += sbuf.end - sbuf.start;
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
|
|
/* Disabling and re-enabling of re-handshakes is a receive feature. In other
|
|
words, a NO_RENEGOTIATION alert will be sent if a request is sent from the
|
|
peer. */
|
|
int32 matrixSslDisableRehandshakes(ssl_t *ssl)
|
|
{
|
|
if (ssl == NULL)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
matrixSslSetSessionOption(ssl, SSL_OPTION_DISABLE_REHANDSHAKES, NULL);
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
int32 matrixSslReEnableRehandshakes(ssl_t *ssl)
|
|
{
|
|
if (ssl == NULL)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
matrixSslSetSessionOption(ssl, SSL_OPTION_REENABLE_REHANDSHAKES, NULL);
|
|
return PS_SUCCESS;
|
|
}
|
|
|
|
/* Undocumented helper functions to manage rehandshake credits for testing */
|
|
int32 matrixSslGetRehandshakeCredits(ssl_t *ssl)
|
|
{
|
|
if (ssl)
|
|
{
|
|
return ssl->rehandshakeCount;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void matrixSslAddRehandshakeCredits(ssl_t *ssl, int32 credits)
|
|
{
|
|
if (ssl)
|
|
{
|
|
/* User must re-enable rehandshaking before adding credits */
|
|
if (ssl->rehandshakeCount >= 0)
|
|
{
|
|
if ((ssl->rehandshakeCount + credits) < 0x8000)
|
|
{
|
|
ssl->rehandshakeCount += credits;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# else /* !SSL_REHANDSHAKES_ENABLED */
|
|
int32_t matrixSslEncodeRehandshake(ssl_t *ssl, sslKeys_t *keys,
|
|
int32 (*certCb)(ssl_t *ssl, psX509Cert_t *cert, int32 alert),
|
|
uint32_t sessionOption,
|
|
const psCipher16_t cipherSpec[], uint8_t cipherSpecLen)
|
|
{
|
|
psTraceErrr("Rehandshaking is disabled. matrixSslEncodeRehandshake off\n");
|
|
return PS_FAILURE;
|
|
}
|
|
int32 matrixSslDisableRehandshakes(ssl_t *ssl)
|
|
{
|
|
psTraceErrr("Rehandshaking is not compiled into library at all.\n");
|
|
return PS_FAILURE;
|
|
}
|
|
|
|
int32 matrixSslReEnableRehandshakes(ssl_t *ssl)
|
|
{
|
|
psTraceErrr("Rehandshaking is not compiled into library at all.\n");
|
|
return PS_FAILURE;
|
|
}
|
|
# endif /* SSL_REHANDSHAKES_ENABLED */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Caller is indicating 'bytes' of data was written
|
|
*/
|
|
int32 matrixSslSentData(ssl_t *ssl, uint32 bytes)
|
|
{
|
|
int32 rc;
|
|
|
|
if (!ssl)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (bytes == 0)
|
|
{
|
|
if (ssl->outlen > 0)
|
|
{
|
|
return MATRIXSSL_REQUEST_SEND;
|
|
}
|
|
else
|
|
{
|
|
return MATRIXSSL_SUCCESS; /* Nothing to do */
|
|
}
|
|
}
|
|
psAssert(ssl->outsize > 0 && ssl->outbuf != NULL);
|
|
ssl->outlen -= bytes;
|
|
|
|
rc = MATRIXSSL_SUCCESS;
|
|
if (ssl->outbuf && ssl->outlen > 0)
|
|
{
|
|
Memmove(ssl->outbuf, ssl->outbuf + bytes, ssl->outlen);
|
|
/* This was changed during 3.7.1 DTLS work. The line below used to be:
|
|
rc = MATRIXSSL_REQUEST_SEND; and it was possible for it to be
|
|
overridden with HANDSHAKE_COMPLETE below. This was a problem
|
|
if only the ChangeCipherSpec portion of the final flight was
|
|
just set becuase matrixSslHandshakeIsComplete would return 1
|
|
because the state looks right. However, there would still be a
|
|
FINISHED message sitting in outbuf when COMPLETE is returned.
|
|
|
|
This seemed like a bigger problem than just the DTLS test case
|
|
that caught it. If the transport layer of straight TLS sent off
|
|
only the CCS message for some reason, this would cause the same
|
|
odd combo of COMPLETE but with a FINISHED message that hasn't
|
|
been sent.
|
|
|
|
It seems fine to return REQUEST_SEND whenever there is data left
|
|
in the outgoing buffer but it is suspecious it wasn't written
|
|
this way to begin with so maybe there was another corner case
|
|
the COMPLETE was solving. Hope not.
|
|
|
|
In any case, it looks safe to make this a global change but if you
|
|
are reading this because you are trying to track down a change in
|
|
behavior in matrixSslSentData, maybe this documentation will help.
|
|
*/
|
|
return MATRIXSSL_REQUEST_SEND;
|
|
}
|
|
/* If there's nothing left to flush, reallocate the buffer smaller. */
|
|
if ((ssl->outlen == 0) && (ssl->bFlags & BFLAG_CLOSE_AFTER_SENT))
|
|
{
|
|
/* We want to close the connection now */
|
|
rc = MATRIXSSL_REQUEST_CLOSE;
|
|
}
|
|
else
|
|
{
|
|
revertToDefaultBufsize(ssl, SSL_OUTBUF);
|
|
}
|
|
/* Indicate the handshake is complete, in this case, the finished message
|
|
is being/has been just sent. Occurs in session resumption. */
|
|
if (!(ssl->bFlags & BFLAG_HS_COMPLETE) &&
|
|
matrixSslHandshakeIsComplete(ssl))
|
|
{
|
|
ssl->bFlags |= BFLAG_HS_COMPLETE;
|
|
# ifdef USE_CLIENT_SIDE_SSL
|
|
matrixSslGetSessionId(ssl, ssl->sid);
|
|
# endif /* USE_CLIENT_SIDE_SSL */
|
|
rc = MATRIXSSL_HANDSHAKE_COMPLETE;
|
|
# ifdef USE_SSL_INFORMATIONAL_TRACE
|
|
/* Client side resumed completion or server standard completion */
|
|
matrixSslPrintHSDetails(ssl);
|
|
# endif
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
# ifdef USE_ZLIB_COMPRESSION
|
|
int32 matrixSslIsSessionCompressionOn(ssl_t *ssl)
|
|
{
|
|
if (ssl->compression > 0)
|
|
{
|
|
return PS_TRUE;
|
|
}
|
|
return PS_FALSE;
|
|
}
|
|
# endif
|
|
|
|
# ifdef USE_TLS_1_3
|
|
int32_t matrixSslSetTls13BlockPadding(ssl_t *ssl, psSizeL_t blockSize)
|
|
{
|
|
if (blockSize == 0)
|
|
{
|
|
return PS_ARG_FAIL;
|
|
}
|
|
if (blockSize > TLS_1_3_MAX_PLAINTEXT_FRAGMENT_LEN)
|
|
{
|
|
psTraceInfo("Error: cannot pad to larger than max plaintext size\n");
|
|
return PS_ARG_FAIL;
|
|
}
|
|
ssl->tls13BlockSize = blockSize;
|
|
|
|
/* tls13PadLen and tls13BlockSize are mutually exclusive. */
|
|
ssl->tls13PadLen = 0;
|
|
|
|
return PS_SUCCESS;
|
|
}
|
|
# endif /* USE_TLS_1_3 */
|
|
|
|
/******************************************************************************/
|