Files
mars-matrixssl/matrixssl/dtls.c
Janne Johansson f0b0d0a5c3 MatrixSSL 4.2.2.
2019-09-11 15:38:19 +03:00

1249 lines
37 KiB
C

/**
* @file dtls.c
* @version $Format:%h%d$
*
* DTLS specific code.
*/
/*
* Copyright (c) 2013-2017 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"
#ifdef USE_DTLS
/******************************************************************************/
static int32 globalPmtu;
# ifdef USE_SERVER_SIDE_SSL
# ifndef DTLS_COOKIE_KEY_SIZE
# define DTLS_COOKIE_KEY_SIZE 32
# endif /* DTLS_COOKIE_KEY_SIZE */
# if DTLS_COOKIE_KEY_SIZE < 16
# error "DTLS_COOKIE_KEY_SIZE too small (recommended 32 or more)."
# endif /* DTLS_COOKIE_KEY_SIZE */
/* The cookie key is generated once on startup. */
static unsigned char cookie_key[DTLS_COOKIE_KEY_SIZE] = { 0 };
int32 dtlsGenCookieSecret(void)
{
int i;
int32 res;
/* Check if cookie_key appears ok from first octets.
Retry at most three times if values appear not ok (too many zeroes). */
for (i = 0; i < 4; i++)
{
res = psGetPrngLocked(cookie_key, DTLS_COOKIE_KEY_SIZE,
NULL);
if ((cookie_key[0] | cookie_key[1] | cookie_key[2] |
cookie_key[3]) != 0)
{
return res;
}
}
return PS_FAILURE; /* Unable to get cookie_key from RNG. */
}
int32_t dtlsComputeCookie(ssl_t *ssl, unsigned char *helloBytes, int32 helloLen)
{
unsigned char hmacKey[SHA256_HASHLEN];
unsigned char out[SHA256_HASHLEN];
psSize_t hmacKeyLen;
int32_t rc;
/* Ensure cookie_key has been initialized.
(Initialization at dtlsGenCookieSecret() makes sure one of first
four bytes is non-zero if initialization succeeded. */
if ((cookie_key[0] | cookie_key[1] | cookie_key[2] | cookie_key[3]) == 0)
{
return PS_FAIL;
}
# ifdef USE_HMAC_SHA256
# if DTLS_COOKIE_SIZE > SHA256_HASHLEN
# error "DTLS_COOKIE_SIZE too large"
# endif
rc = psHmacSha256(cookie_key, DTLS_COOKIE_KEY_SIZE, helloBytes, helloLen,
out, hmacKey, &hmacKeyLen);
# elif defined (USE_HMAC_SHA1)
# if DTLS_COOKIE_SIZE > SHA1_HASHLEN
# error "DTLS_COOKIE_SIZE too large"
# endif
rc = psHmacSha1(cookie_key, DTLS_COOKIE_KEY_SIZE, helloBytes, helloLen,
out, hmacKey, &hmacKeyLen);
# else
# error Must define HMAC_SHA256 or HMAC_SHA1 with DTLS
# endif
if (rc >= 0)
{
/* Truncate hash output if necessary;
Use the first four bytes as srvCookie valid indicator. */
if ((out[0] | out[1] | out[2] | out[3]) == 0)
{
out[0] = 1; /* All bits are zero: Set one bit. */
}
Memcpy(ssl->srvCookie, out, DTLS_COOKIE_SIZE);
}
memzero_s(out, DTLS_COOKIE_SIZE);
return rc;
}
# endif /* USE_SERVER_SIDE_SSL */
/******************************************************************************/
/*
The PS_MIN_PMTU value is enforced to keep all handshake messages other
than CERTIFIATE to contained within a single datagram
*/
int32 matrixDtlsSetPmtu(int32 pmtu)
{
if (pmtu < 0)
{
globalPmtu = DTLS_PMTU;
}
else
{
globalPmtu = pmtu;
}
if (globalPmtu < PS_MIN_PMTU)
{
psTraceIntDtls("PMTU set too small. Defaulting to %d\n", PS_MIN_PMTU);
globalPmtu = PS_MIN_PMTU;
}
return globalPmtu;
}
int32 matrixDtlsGetPmtu(void)
{
return globalPmtu;
}
# ifndef USE_ONLY_PSK_CIPHER_SUITE
# if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
static int32 fragmentHSMessage(ssl_t *ssl, unsigned char *msg, int32 msgLen,
int32 hsType, unsigned char *c);
static int32 postponeEncryptFragRecord(ssl_t *ssl, int32 padLen,
int32 fragCount, int32 fragLen, int32 msgLen, int32 type,
int32 hsMsg, unsigned char *encryptStart, unsigned char **c);
# endif
/******************************************************************************/
/*
The certificate message spans records
*/
int32 dtlsWriteCertificate(ssl_t *ssl, int32 certLen, int32 lsize,
unsigned char *c)
{
# if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
psX509Cert_t *cert;
unsigned char *tmp, *tmpStart;
int32 tmpLen, wLen;
/*
DTLS fragmented writes are done backwards. The message info is written
to a temp buffer and the hs header and record header are wrapped around
that as the fragmentation is done.
*/
tmpLen = certLen + lsize;
tmpStart = tmp = psMalloc(NULL, tmpLen);
if (tmpStart == NULL)
{
return SSL_MEM_ERROR;
}
*tmp = (unsigned char) (((certLen + (lsize - 3)) & 0xFF0000) >> 16); tmp++;
*tmp = ((certLen + (lsize - 3)) & 0xFF00) >> 8; tmp++;
*tmp = ((certLen + (lsize - 3)) & 0xFF); tmp++;
# ifdef USE_IDENTITY_CERTIFICATES
if (certLen > 0)
{
cert = ssl->chosenIdentity->cert;
while (cert)
{
certLen = cert->binLen;
if (certLen > 0)
{
*tmp = (unsigned char) ((certLen & 0xFF0000) >> 16); tmp++;
*tmp = (certLen & 0xFF00) >> 8; tmp++;
*tmp = (certLen & 0xFF); tmp++;
Memcpy(tmp, cert->unparsedBin, certLen);
tmp += certLen;
}
cert = cert->next;
}
}
# endif
/*
Fragment it
*/
wLen = fragmentHSMessage(ssl, tmpStart, tmpLen, SSL_HS_CERTIFICATE, c);
psFree(tmpStart, NULL);
return wLen;
# else
/* Wrapping in defines here to keep sslEncode a bit more clear. Need
this because 'cert' is not available on straight USE_CLIENT_SIDE
and no danger here at all because an empty CERTIFICATE message will
never need fragmentation. This is just a compile issue */
return 0;
# endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
}
# if defined(USE_SERVER_SIDE_SSL) && defined(USE_CLIENT_AUTH)
/******************************************************************************/
/*
The certificate_request message spans records
*/
int32 dtlsWriteCertificateRequest(psPool_t *pool, ssl_t *ssl, int32 certLen,
int32 certCount, int32 sigHashLen, unsigned char *c)
{
psX509Cert_t *cert;
unsigned char *tmp, *tmpStart;
int32 tmpLen, wLen;
/*
DTLS fragmented writes are done backwards. The message info is written
to a temp buffer and the hs header and record header are wrapped around
that as the fragmentation is done.
*/
tmpLen = certLen + 4 + (certCount * 2) + sigHashLen;
# ifdef USE_ECC
tmpLen++;
# endif
tmpStart = tmp = psMalloc(pool, tmpLen);
if (tmpStart == NULL)
{
return SSL_MEM_ERROR;
}
# ifdef USE_ECC
*tmp++ = 2;
*tmp++ = ECDSA_SIGN;
# else
*tmp++ = 1;
# endif
*tmp++ = RSA_SIGN;
# ifdef USE_TLS_1_2
if (NGTD_VER(ssl, v_tls_with_signature_algorithms))
{
/* RFC: "The interaction of the certificate_types and
supported_signature_algorithms fields is somewhat complicated.
certificate_types has been present in TLS since SSLv3, but was
somewhat underspecified. Much of its functionality is superseded
by supported_signature_algorithms."
The spec says the cert must support the hash/sig algorithm but
it's a bit confusing what this means for the hash portion.
Just going to use SHA1, SHA256, and SHA384 support.
We're just sending the raw list of all sig algorithms that are
compiled into the library. It might be smart to look through the
individual CA files here only send the pub key operations that
they use but the CA info is sent explicitly anyway so the client
can confirm they have a proper match.
If a new algorithm is added here it will require additions to
messageSize directly above in this function and in the flight
calculation in sslEncodeResponse */
*tmp++ = 0x0;
*tmp++ = sigHashLen - 2;
# ifdef USE_ECC
# ifdef USE_SHA384
*tmp++ = 0x5; /* SHA384 */
*tmp++ = 0x3; /* ECDSA */
*tmp++ = 0x4; /* SHA256 */
*tmp++ = 0x3; /* ECDSA */
*tmp++ = 0x2; /* SHA1 */
*tmp++ = 0x3; /* ECDSA */
# else
*tmp++ = 0x4; /* SHA256 */
*tmp++ = 0x3; /* ECDSA */
*tmp++ = 0x2; /* SHA1 */
*tmp++ = 0x3; /* ECDSA */
# endif
# endif
# ifdef USE_RSA
# ifdef USE_SHA384
*tmp++ = 0x5; /* SHA384 */
*tmp++ = 0x1; /* RSA */
*tmp++ = 0x4; /* SHA256 */
*tmp++ = 0x1; /* RSA */
*tmp++ = 0x2; /* SHA1 */
*tmp++ = 0x1; /* RSA */
# else
*tmp++ = 0x4; /* SHA256 */
*tmp++ = 0x1; /* RSA */
*tmp++ = 0x2; /* SHA1 */
*tmp++ = 0x1; /* RSA */
# endif
# endif /* USE_RSA */
}
# endif /* TLS_1_2 */
# if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
cert = ssl->keys->CAcerts;
# else
cert = NULL;
# endif
if (cert)
{
*tmp = ((certLen + (certCount * 2)) & 0xFF00) >> 8; tmp++;
*tmp = (certLen + (certCount * 2)) & 0xFF; tmp++;
while (cert)
{
*tmp = (cert->subject.dnencLen & 0xFF00) >> 8; tmp++;
*tmp = cert->subject.dnencLen & 0xFF; tmp++;
Memcpy(tmp, cert->subject.dnenc, cert->subject.dnencLen);
tmp += cert->subject.dnencLen;
cert = cert->next;
}
}
else
{
*tmp++ = 0; /* Cert len */
*tmp++ = 0;
}
/*
Fragment it
*/
wLen = fragmentHSMessage(ssl, tmpStart, tmpLen, SSL_HS_CERTIFICATE_REQUEST,
c);
psFree(tmpStart, pool);
return wLen;
}
# endif /* USE_SERVER_SIDE_SSL && USE_CLIENT_AUTH */
# if defined(USE_SERVER_SIDE_SSL) || defined(USE_CLIENT_AUTH)
/******************************************************************************/
/*
Given the data message and lengths, chunk up the message into a
multi-record handshake message
*/
static int32 fragmentHSMessage(ssl_t *ssl, unsigned char *msg, int32 msgLen,
int32 hsType, unsigned char *c)
{
unsigned char *msgStart, *encryptStart;
int32 tmpLen, recordLen, fragLen, offset, fragCount,
padLen, overhead, secureOverhead;
offset = fragCount = padLen = secureOverhead = 0;
msgStart = c;
tmpLen = msgLen;
overhead = ssl->recordHeadLen + ssl->hshakeHeadLen;
if ((ssl->flags & SSL_FLAGS_WRITE_SECURE) && (ssl->enBlockSize > 1))
{
secureOverhead = ssl->enMacSize + /* handshake msg hash */
(ssl->enBlockSize * 2); /* explictIV and max pad */
}
if (ssl->flags & SSL_FLAGS_AEAD_W)
{
secureOverhead += AEAD_TAG_LEN(ssl) + AEAD_NONCE_LEN(ssl);
}
while (tmpLen > 0)
{
if (tmpLen >= (ssl->pmtu - overhead - secureOverhead))
{
recordLen = ssl->pmtu - ssl->recordHeadLen;
fragLen = ssl->pmtu - overhead - secureOverhead;
}
else
{
recordLen = tmpLen + ssl->hshakeHeadLen;
fragLen = tmpLen;
}
/* Make secure adjustments */
if ((ssl->flags & SSL_FLAGS_WRITE_SECURE) && (ssl->enBlockSize > 1))
{
recordLen = fragLen + ssl->hshakeHeadLen + ssl->enMacSize +
ssl->enBlockSize;
padLen = psPadLenPwr2(recordLen, ssl->enBlockSize);
recordLen += padLen;
}
/* Make secure adjustments for final fragment */
if (ssl->flags & SSL_FLAGS_AEAD_W)
{
recordLen = fragLen + ssl->hshakeHeadLen + secureOverhead;
}
c += psWriteRecordInfo(ssl, SSL_RECORD_TYPE_HANDSHAKE, recordLen, c,
hsType);
encryptStart = c;
if ((ssl->flags & SSL_FLAGS_WRITE_SECURE) && (ssl->enBlockSize > 1))
{
if (psGetPrngLocked(c, ssl->enBlockSize, ssl->userPtr) < 0)
{
psTraceDtls("WARNING: psGetPrngLocked failed\n");
}
c += ssl->enBlockSize;
}
c += psWriteHandshakeHeader(ssl, (unsigned char) hsType, msgLen,
ssl->msn, offset, fragLen, c);
Memcpy(c, msg, fragLen);
msg += fragLen;
c += fragLen;
offset += fragLen;
tmpLen -= fragLen;
postponeEncryptFragRecord(ssl, padLen, fragCount, fragLen,
msgLen, SSL_RECORD_TYPE_HANDSHAKE, hsType, encryptStart, &c);
fragCount++;
}
/*
Can now safely increment the MSN now that all fragments are written
*/
ssl->msn++;
/*
In theory, there is no reason the sender of the fragmented messages
should care how many fragments it used but we should warn if
MAX_FRAGMENTS has been exceeded because if the DTLS peer was also
linked with the same MatrixSSL library, there will be a problem on
that side when parsing
*/
if (fragCount > MAX_FRAGMENTS)
{
psTraceIntDtls("Warning: MAX_FRAGMENTS exceeded %d\n",
MAX_FRAGMENTS);
}
return (int32) (c - msgStart);
}
static int32 postponeEncryptFragRecord(ssl_t *ssl, int32 padLen,
int32 fragCount, int32 fragLen, int32 msgLen, int32 type,
int32 hsMsg, unsigned char *encryptStart, unsigned char **c)
{
flightEncode_t *flight, *prev;
if ((flight = psMalloc(ssl->flightPool, sizeof(flightEncode_t))) == NULL)
{
return PS_MEM_FAIL;
}
Memset(flight, 0x0, sizeof(flightEncode_t));
if (ssl->flightEncode == NULL)
{
ssl->flightEncode = flight;
}
else
{
prev = ssl->flightEncode;
while (prev->next)
{
prev = prev->next;
}
prev->next = flight;
}
flight->start = encryptStart;
flight->len = fragLen;
flight->type = type;
flight->padLen = padLen;
flight->messageSize = msgLen;
flight->hsMsg = hsMsg;
flight->seqDelay = ssl->seqDelay;
flight->fragCount = ++fragCount; /* Add one to differentiate from 0 based */
*c += ssl->enMacSize;
*c += padLen;
if (hsMsg == SSL_HS_FINISHED)
{
if (ssl->cipher->flags & (CRYPTO_FLAGS_GCM | CRYPTO_FLAGS_CHACHA | CRYPTO_FLAGS_CCM))
{
*c += AEAD_TAG_LEN(ssl);
}
}
else if (ssl->flags & SSL_FLAGS_AEAD_W)
{
*c += AEAD_TAG_LEN(ssl); /* c is tracking end of record here and the
tag has not yet been accounted for */
}
return PS_SUCCESS;
}
int32 dtlsEncryptFragRecord(ssl_t *ssl, flightEncode_t *msg,
sslBuf_t *out, unsigned char **c)
{
unsigned char *updateHash, *encryptStart;
unsigned char fakeHeader[SSL3_HANDSHAKE_HEADER_LEN + DTLS_HEADER_ADD_LEN];
encryptStart = out->end + ssl->recordHeadLen;
updateHash = msg->start;
if ((ssl->flags & SSL_FLAGS_WRITE_SECURE) && (ssl->enBlockSize > 1))
{
updateHash += ssl->enBlockSize;
/* The data has already been bypassed in c at this point, FYI */
*c += ssl->enBlockSize;
}
if (msg->fragCount == 1)
{
/*
Can snapshot hs hash now. A bit tricky because we have to treat
the message as if there is no fragmentation at all. This means
we actually are hashing a header format that isn't really
sent at all. We deal with this by flaging the first fragment
and manually tweaking the fragLen value.
*/
Memcpy(fakeHeader, updateHash, ssl->hshakeHeadLen);
/*
A bit ugly. The fragLen is the final three bytes of the header
*/
fakeHeader[ssl->hshakeHeadLen - 3] =
(unsigned char) ((msg->messageSize & 0xFF0000) >> 16);
fakeHeader[ssl->hshakeHeadLen - 2] = (msg->messageSize & 0xFF00) >> 8;
fakeHeader[ssl->hshakeHeadLen - 1] = (msg->messageSize & 0xFF);
sslUpdateHSHash(ssl, fakeHeader, ssl->hshakeHeadLen);
}
sslUpdateHSHash(ssl, updateHash + ssl->hshakeHeadLen, msg->len);
*c += ssl->hshakeHeadLen;
if (ssl->flags & SSL_FLAGS_AEAD_W)
{
encryptStart += AEAD_NONCE_LEN(ssl);
ssl->outRecType = (unsigned char) msg->type;
}
if ((ssl->flags & SSL_FLAGS_WRITE_SECURE) && (ssl->enBlockSize > 1))
{
*c += ssl->generateMac(ssl, (unsigned char) msg->type,
encryptStart + ssl->enBlockSize,
(int32) (*c - encryptStart) - ssl->enBlockSize, *c);
}
*c += sslWritePad(*c, (unsigned char) msg->padLen);
if (ssl->flags & SSL_FLAGS_AEAD_W)
{
*c += AEAD_TAG_LEN(ssl); /* c is tracking end of record here and the
tag has not yet been accounted for */
}
if (ssl->encrypt(ssl, encryptStart, encryptStart,
(int32) (*c - encryptStart)) < 0)
{
psTraceErrr("Error encrypting message for write\n");
return PS_FAILURE;
}
dtlsIncrRsn(ssl);
return 0;
}
# endif /* USE_SERVER_SIDE_SSL || USE_CLIENT_AUTH */
# endif /* !USE_ONLY_PSK_CIPHER_SUITES */
/******************************************************************************/
/*
Have all the fragments and their headers. Feed them through the
UpdateHSHash routine in the order they were on the other side
*/
int32 dtlsHsHashFragMsg(ssl_t *ssl)
{
unsigned char fakeHeader[SSL3_HANDSHAKE_HEADER_LEN + DTLS_HEADER_ADD_LEN];
int32 i, nextOffset, headLen, totalLen;
/*
Construct the message from the fragments (may be out of order)
*/
nextOffset = i = totalLen = 0;
while (i < MAX_FRAGMENTS)
{
if (ssl->fragHeaders[i].offset == nextOffset)
{
/*
We must send this message through the handshake hash mechanism
as if there was no fragmentation at all. A nextOffset value
of 0 will always mean this is the first fragment. This header
has everything correct except the fragLen value. This must
be manually changed to be the full 'len' value. The remainder
of the fragment headers are not used.
*/
if (nextOffset == 0)
{
headLen = SSL3_HANDSHAKE_HEADER_LEN + DTLS_HEADER_ADD_LEN;
Memcpy(fakeHeader, ssl->fragHeaders[i].hsHeader, headLen);
/*
First byte is 'type'. Next three are total length. Final
three are fragLen.
*/
fakeHeader[headLen - 3] = fakeHeader[1];
fakeHeader[headLen - 2] = fakeHeader[2];
fakeHeader[headLen - 1] = fakeHeader[3];
sslUpdateHSHash(ssl, fakeHeader, headLen);
/*
Also grabbing the total length so we can make a smart loop exit
*/
totalLen = fakeHeader[1] << 16;
totalLen += fakeHeader[2] << 8;
totalLen += fakeHeader[3];
}
/*
The remainder of the UpdateHS hash is simply the data from each frag
*/
sslUpdateHSHash(ssl, ssl->fragMessage + ssl->fragHeaders[i].offset,
ssl->fragHeaders[i].fragLen);
nextOffset += ssl->fragHeaders[i].fragLen;
i = 0;
}
else
{
if (nextOffset != 0 && nextOffset == totalLen)
{
break;
}
i++;
}
}
return 0;
}
/******************************************************************************/
/*
* Replay detection, per IPSec
* http://www.faqs.org/rfcs/rfc2401.html Appendix C
* Returns 0 if packet disallowed, 1 if packet permitted
*/
enum
{
ReplayWindowSize = 32
};
int32 dtlsChkReplayWindow(ssl_t *ssl, unsigned char *seq64)
{
uint32_t diff, seq, lastSeq;
unsigned char *ls64;
/*
TODO DTLS - We truncate 48 bit sequence to 32bits to make it simpler here
*/
seq = ((uint32_t) seq64[2] << 24) + ((uint32_t) seq64[3] << 16) +
((uint32_t) seq64[4] << 8) + (uint32_t) seq64[5];
ls64 = ssl->lastRsn;
lastSeq = ((uint32_t) ls64[2] << 24) + ((uint32_t) ls64[3] << 16) +
((uint32_t) ls64[4] << 8) + (uint32_t) ls64[5];
if (seq == 0)
{
/* Need to differentiate between initial, duplicate, and epoch shift */
if (lastSeq == 0 && ssl->rec.epoch[0] == 0 && ssl->rec.epoch[1] == 0)
{
ssl->dtlsBitmap = 0;
return 1; /* initial one */
}
if (dtlsCompareEpoch(ssl->rec.epoch, ssl->expectedEpoch) >= 0 &&
lastSeq > 0)
{
ssl->dtlsBitmap = 0;
return 1; /* epoch shift */
}
if (lastSeq == 0xFFFFFFF)
{
ssl->dtlsBitmap = 0;
return 1; /* wrapped */
}
return 0; /* duplicate */
}
if (seq > lastSeq) /* new larger sequence number */
{
diff = seq - lastSeq;
if (diff < ReplayWindowSize) /* In window */
{
ssl->dtlsBitmap <<= diff;
ssl->dtlsBitmap |= 1; /* set bit for this packet */
}
else
{
ssl->lastRsn[0] = 1; /* This packet has a "way larger" */
}
Memcpy(ssl->lastRsn, seq64, 6);
return 1; /* larger is good */
}
diff = lastSeq - seq;
if (diff >= ReplayWindowSize)
{
return 0; /* too old or wrapped */
}
if (ssl->dtlsBitmap & ((int32) 1 << diff))
{
return 0; /* already seen */
}
ssl->dtlsBitmap |= ((unsigned long) 1 << diff); /* mark as seen */
return 1; /* out of order but good */
}
/******************************************************************************/
/*
Init ssl_t fragment members
*/
void dtlsInitFrag(ssl_t *ssl)
{
int32 i;
/*
This is also used for re-init so there may be memory to free here.
*/
ssl->fragTotal = 0;
for (i = 0; i < MAX_FRAGMENTS; i++)
{
ssl->fragHeaders[i].offset = -1;
if (ssl->fragHeaders[i].hsHeader != NULL)
{
psFree(ssl->fragHeaders[i].hsHeader, ssl->hsPool);
ssl->fragHeaders[i].hsHeader = NULL;
}
}
}
/******************************************************************************/
/*
Return 1 if this fragment has been seen before. Just reads the
fragHeaders member. Does not update.
*/
int32 dtlsSeenFrag(ssl_t *ssl, int32 fragOffset, int32 *hdrIndex)
{
int32 i;
for (i = 0; i < MAX_FRAGMENTS; i++)
{
if (ssl->fragHeaders[i].offset == -1)
{
*hdrIndex = i;
return 0;
}
if (ssl->fragHeaders[i].offset == fragOffset)
{
return 1;
}
}
/*
Max fragments exceeded error
*/
return -1;
}
/******************************************************************************/
/*
Treating this unsigned char array as a 48bit uint
*/
void dtlsIncrRsn(ssl_t *ssl)
{
int32 i;
for (i = 5; i >= 0; i--)
{
if ((int) ssl->rsn[i] < 0xFF)
{
ssl->rsn[i]++;
if (ssl->rsn[i] > ssl->largestRsn[i])
{
ssl->largestRsn[i] = ssl->rsn[i];
}
break;
}
ssl->rsn[i] = 0;
}
}
/*
Treating this 2 byte unsigned char array as a uint16
*/
void incrTwoByte(ssl_t *ssl, unsigned char *c, int sending)
{
int32 i;
if (sending)
{
c[0] = ssl->largestEpoch[0];
c[1] = ssl->largestEpoch[1];
}
for (i = 1; i >= 0; i--)
{
if ((int) c[i] < 0xFF)
{
c[i]++;
if (sending)
{
if (c[i] > ssl->largestEpoch[i])
{
ssl->largestEpoch[i] = c[i];
}
}
break;
}
c[i] = 0;
}
}
void zeroTwoByte(unsigned char *c)
{
c[0] = 0;
c[1] = 0;
}
void zeroSixByte(unsigned char *c)
{
int32 i;
for (i = 5; i >= 0; i--)
{
c[i] = 0;
}
}
int32 dtlsCompareEpoch(unsigned char *incoming, unsigned char *expected)
{
int32 i;
for (i = 0; i < 2; i++)
{
if (incoming[i] < expected[i])
{
return -1;
}
if (incoming[i] > expected[i])
{
return 1;
}
}
return 0;
}
/******************************************************************************/
/*
Rewinding our context
*/
static int32 dtlsRevertWriteCipher(ssl_t *ssl)
{
ssl->encrypt = ssl->oencrypt;
ssl->generateMac = ssl->ogenerateMac;
ssl->enMacSize = ssl->oenMacSize;
ssl->nativeEnMacSize = ssl->oenNativeHmacSize;
ssl->enIvSize = ssl->oenIvSize;
ssl->enBlockSize = ssl->oenBlockSize;
Memcpy(ssl->sec.writeIV, ssl->owriteIV, sizeof(ssl->owriteIV));
Memcpy(ssl->sec.writeKey, ssl->owriteKey, sizeof(ssl->owriteKey));
# ifdef USE_NATIVE_TLS_ALGS
Memcpy(ssl->sec.writeMAC, ssl->owriteMAC, ssl->oenMacSize);
Memcpy(&ssl->sec.encryptCtx, &ssl->oencryptCtx,
sizeof(psCipherContext_t));
# endif
# ifdef ENABLE_SECURE_REHANDSHAKES
Memcpy(ssl->myVerifyData, ssl->omyVerifyData, ssl->omyVerifyDataLen);
ssl->myVerifyDataLen = ssl->omyVerifyDataLen;
# endif /* ENABLE_SECURE_REHANDSHAKES */
/*
If the old versions of the write state indicate that we were in an
unencrypted state, remove the SECURE_WRITE flags. Block, MAC, and IV are
sufficient tests for all potential cipher suites since only block ciphers
are allowed for DTLS
*/
if (ssl->oenMacSize == 0 && ssl->oenBlockSize == 0 && ssl->oenIvSize == 0)
{
ssl->flags &= ~SSL_FLAGS_WRITE_SECURE;
# ifdef USE_TLS_1_2
ssl->flags &= ~SSL_FLAGS_AEAD_W;
ssl->flags &= ~SSL_FLAGS_NONCE_W;
# endif /* USE_TLS_1_2 */
}
/* Case where moved to GCM on write FINISHED from CBC-SHA and need to get
back to CBC-SHA */
if (ssl->flags & SSL_FLAGS_AEAD_W && ssl->oenMacSize > 0)
{
ssl->flags &= ~SSL_FLAGS_AEAD_W;
ssl->flags &= ~SSL_FLAGS_NONCE_W;
}
/* Case where moved to CBC-SHA on write FINISHED from GCM and need to get
back to GCM */
if (!(ssl->flags & SSL_FLAGS_AEAD_W) && ssl->oenMacSize == 0 &&
ssl->oenIvSize > 0)
{
ssl->flags |= SSL_FLAGS_AEAD_W;
ssl->flags |= SSL_FLAGS_NONCE_W;
}
/*
Toggle flag to reset the logic for determing HANDSHAKE_COMPLETE in
the top level APIs
*/
ssl->bFlags &= ~BFLAG_HS_COMPLETE;
return 0;
}
/******************************************************************************/
/*
*/
static int32 dtlsResendFlight(ssl_t *ssl, psBuf_t *out)
{
int32 rc;
uint32 requiredLen = 0; /* only added so far to get to compile */
/*
Reset to the MSN and epoch of the first message in the current flight
*/
ssl->msn = ssl->resendMsn;
rc = dtlsCompareEpoch(ssl->epoch, ssl->resendEpoch);
if (rc != 0)
{
/*
We are dealing with a CHANGE_CIPHER_SPEC flight resend so we must
revert to the old write cipher
*/
dtlsRevertWriteCipher(ssl);
/*
It is also necessary to make sure rsn is updated to the largest
previously sent record number because if this flight is a resend that
includes the CHANGE_CIPHER_SPEC message so the rsn has been reset
*/
ssl->rsn[0] = ssl->largestRsn[0];
ssl->rsn[1] = ssl->largestRsn[1];
ssl->rsn[2] = ssl->largestRsn[2];
ssl->rsn[3] = ssl->largestRsn[3];
ssl->rsn[4] = ssl->largestRsn[4];
ssl->rsn[5] = ssl->largestRsn[5];
}
ssl->epoch[0] = ssl->resendEpoch[0];
ssl->epoch[1] = ssl->resendEpoch[1];
encode:
ssl->retransmit = 1;
rc = sslEncodeResponse(ssl, out, &requiredLen);
ssl->retransmit = 0;
if (rc == PS_SUCCESS)
{
if (ssl->err != SSL_ALERT_NONE)
{
ssl->flags |= SSL_FLAGS_ERROR;
return PS_FAILURE;
}
}
if (rc == SSL_FULL)
{
psFree(out->buf, ssl->bufferPool);
if ((out->buf = psMalloc(ssl->bufferPool, requiredLen)) == NULL)
{
return PS_MEM_FAIL;
}
out->start = out->end = out->buf;
out->size = requiredLen;
goto encode;
}
return PS_SUCCESS;
}
/******************************************************************************/
/*
Takes a 'flight' of records and returns the length of how many full
records from the front of the buffer can fit in a single pmtu. Used
by the sockets layer to send out an already fragmented flight with
the proper byte lengths.
*/
static int32 dtlsGetNextRecordLen(ssl_t *ssl, int32 pmtu, sslBuf_t *out,
int32 *recordLen)
{
int32 tlen, len;
unsigned char *newend;
newend = out->start;
/*
If pmtu is <= 0 the user wants a single record regardless
*/
if (pmtu <= 0)
{
newend += ssl->recordHeadLen - 2; /* Find the last two bytes of len */
len = (int32) (newend[0]) << 8;
len += newend[1];
len += ssl->recordHeadLen; /* add record header length to the total */
*recordLen = len;
return 0;
}
/*
Could be called for non-fragmented cases as well. Check to see if the
whole thing will fit in a mtu and return that info if so
*/
if ((out->end - out->start) < pmtu)
{
*recordLen = (int32) (out->end - out->start);
return 0;
}
/*
Otherwise, send as much as will fit
*/
tlen = len = 0;
while (out->end > newend)
{
newend += ssl->recordHeadLen - 2; /* Find the last two bytes of len */
len = (int32) * newend << 8; newend++;
len += (int32) * newend; newend++;
newend += len;
len += ssl->recordHeadLen; /* add record header length to the total */
/*
See if more records can fit in this single write. Just storing
current length in temps and reading off the next one. If it doesn't
fit, just go with the temps.
*/
if ((len + tlen) <= pmtu)
{
tlen += len;
continue;
}
break;
}
*recordLen = tlen;
return 0;
}
/******************************************************************************/
static bool canResend(ssl_t *ssl)
{
bool canSend = false;
if (ssl->flags & SSL_FLAGS_SERVER)
{
if (ssl->hsState == SSL_HS_FINISHED)
canSend = 1;
if (ssl->hsState == SSL_HS_CLIENT_HELLO)
{
canSend = 1; /* any handshake type */
}
if (!(ssl->flags & SSL_FLAGS_RESUMED))
{
if (ssl->hsState == SSL_HS_DONE)
{
canSend = 1; /* DONE set on parse of peer FINISHED */
}
}
/* Different client auth boundary for second flight */
if (ssl->flags & SSL_FLAGS_CLIENT_AUTH)
{
if (ssl->hsState == SSL_HS_CERTIFICATE)
{
canSend = 1;
}
}
else
{
if (ssl->hsState == SSL_HS_CLIENT_KEY_EXCHANGE)
{
canSend = 1;
}
}
if (ssl->flags & SSL_FLAGS_RESUMED)
{
if (ssl->hsState == SSL_HS_FINISHED)
{
canSend = 1;
}
}
}
else
{
#if 0
/* Client tests */
if (ssl->hsState == SSL_HS_SERVER_HELLO)
{
canSend = 1;
}
if (!(ssl->flags & SSL_FLAGS_RESUMED))
{
if (ssl->hsState == SSL_HS_FINISHED)
{
canSend = 1;
}
}
if (ssl->hsState == SSL_HS_DONE)
{
canSend = 1; /* Done is set on parse of peer FINISHED */
}
#else
canSend = 1; /* Why wouldnt't it be safe to resend aways when in doubt */
#endif
}
return canSend;
}
/*
Manages the DTLS flight buffer to make sure each call will return data
that is less than the PMTU (dtlsGetNextRecordLen). Also, plays a role
in determining whether a flight resend needs to happen. Basically, if there
is no data in outbuf and this is the first call to this function under
that condition, we assume this is the standard processing loop and return
0 to indicate we are done with the flight. Subsequent calls to this
function in which no outdata exists (and not in an explicit app data mode)
will rebuild the last handshake flight and start feeding it out again
NOTE: This function must be called in a loop with a corresponding
matrixDtlsSentData until this routine returns 0.
*/
int32 matrixDtlsGetOutdata(ssl_t *ssl, unsigned char **buf)
{
psBuf_t tmp;
int32 bytesToSend, rc;
int16 safeToResend;
if (!ssl || !buf)
{
return PS_ARG_FAIL;
}
tmp.buf = tmp.start = ssl->outbuf;
tmp.end = tmp.buf + ssl->outlen;
tmp.size = ssl->outsize;
/*
First test is just to see if we are already in an app data mode with
nothing in the outbuffer to send. Don't want to resend a handshake flight
*/
if ((tmp.end == tmp.start) && (ssl->appDataExch == 1))
{
*buf = NULL;
return 0;
}
/*
Next, if ssl->outbuf is empty and flightDone is set, this is the mechansim
for the handshake loop to know the full flight has been sent. Return
0 and flag the flight as needing a resend for the cases in which we
come back in here with a zero buffer (a timeout happen and resend needed)
*/
if (ssl->outlen == 0 && ssl->flightDone == 1)
{
ssl->flightDone = 0;
*buf = NULL;
return 0;
}
/*
If ssl->outbuf is empty and not in appDataExch mode this is a flight resend
*/
if (ssl->outlen == 0 && ssl->appDataExch == 0)
{
/* And now the ugly part. If we have been receiving records that
are sent individually and we are successfully midway through an
incomming flight, we don't want to resend our previous flight. We
are still just waiting for the remainder of the flight from the peer.
The only way to figure this out is to test our specific state to
make sure we are on a flight boundary. This gets somewhat complicated
simply due to the different types of handshakes (standard, resumed,
and client auth) having different flight boundaries.
The state is always the handshake message you expect to be receiving
from the peer.
*/
safeToResend = canResend(ssl);
if (safeToResend == 0)
{
psTraceIntDtls("Refused a resend due to state %d\n", ssl->hsState);
*buf = NULL;
return 0;
}
/* A true flight resend is needed */
if ((rc = dtlsResendFlight(ssl, &tmp)) < 0)
{
return rc;
}
ssl->outbuf = tmp.buf;
ssl->outlen = tmp.end - tmp.start;
ssl->outsize = tmp.size;
}
/*
Sending records individually is more of a test mode for being able to
drop more packets to test re-ordering and resend logic. In either case,
PMTU size will always be adhered to.
*/
# ifdef DTLS_SEND_RECORDS_INDIVIDUALLY
dtlsGetNextRecordLen(ssl, 0, &tmp, &bytesToSend);
# else
dtlsGetNextRecordLen(ssl, matrixDtlsGetPmtu(), &tmp, &bytesToSend);
# endif
*buf = ssl->outbuf;
return bytesToSend;
}
/******************************************************************************/
/*
Plays the other half in determining flight resends. If this send ate all
the bytes in outbuf AND we are known not to be in a appDataExch state, we
flag flightDone so the next call to matrixDtlsGetOutdata will know to
return 0 instead of resending the flight.
NOTE: If this peer ONLY sends app data and never receives any in return it
is possible to get into a state where flightDone is set after each app
data send since appDataExch will never be flagged. The consequence of this
scenario would be that the ChangeCipherSpec and Finished message will be
sent if matrixDtlsGetOutdata is called with no application data to send.
Not really a big deal, actually, since it should be ignored by the peer
*/
int32 matrixDtlsSentData(ssl_t *ssl, uint32 bytes)
{
int32 rc;
/* tis decrements bytes from length of constructed output */
rc = matrixSslSentData(ssl, bytes);
if (ssl->outlen == 0 && ssl->appDataExch == 0)
{
ssl->flightDone = 1;
}
return rc;
}
#endif /* USE_DTLS */