2475 lines
79 KiB
C
2475 lines
79 KiB
C
/**
|
|
* @file sslDecode.c
|
|
* @version $Format:%h%d$
|
|
*
|
|
* Secure Sockets Layer protocol message decoding portion of MatrixSSL.
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013-2016 INSIDE Secure Corporation
|
|
* Copyright (c) PeerSec Networks, 2002-2011
|
|
* All Rights Reserved
|
|
*
|
|
* The latest version of this code is available at http://www.matrixssl.org
|
|
*
|
|
* This software is open source; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This General Public License does NOT permit incorporating this software
|
|
* into proprietary programs. If you are unable to comply with the GPL, a
|
|
* commercial license for this software may be purchased from INSIDE at
|
|
* http://www.insidesecure.com/
|
|
*
|
|
* This program is distributed in WITHOUT ANY WARRANTY; without even the
|
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
#include "matrixsslApi.h"
|
|
|
|
/******************************************************************************/
|
|
|
|
#define LUCKY13
|
|
|
|
#define SSL_MAX_IGNORED_MESSAGE_COUNT 1024
|
|
|
|
static int32 parseSSLHandshake(ssl_t *ssl, char *inbuf, uint32 len);
|
|
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
static int32 parseSingleCert(ssl_t *ssl, unsigned char *c, unsigned char *end,
|
|
int32 certLen);
|
|
#endif /* USE_CERT_CHAIN_PARSING */
|
|
|
|
#ifdef LUCKY13
|
|
static int32 addCompressCount(ssl_t *ssl, int32 padLen);
|
|
#endif
|
|
|
|
#ifdef USE_ZLIB_COMPRESSION
|
|
/* Does not need to be a large value because we're only inflating the 16
|
|
byte FINISHED message. In fact, compression will grow 16 bytes but
|
|
this is a good reminder that FUTURE support will need to account for
|
|
likely data growth here */
|
|
#define MATRIX_INFLATE_FINISHED_OH 128
|
|
#endif
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Parse incoming data per http://wp.netscape.com/eng/ssl3
|
|
|
|
Input parameters to decode:
|
|
. buf points to the start of data to decode
|
|
. len points to the length in bytes of data to decode
|
|
. size is the number of allocated bytes that follow buf
|
|
|
|
|
|
|
|
Meaningful parameters after the call to decode:
|
|
MATRIXSSL_SUCCESS
|
|
. buf will point to the first undecoded byte (could==inbuf or inbuf+inlen)
|
|
. remaining will indicate how many more bytes of undecoded data remain
|
|
* call again if more to decode or return if handshake is complete
|
|
|
|
SSL_PARTIAL
|
|
. buf will not have moved (because partials start parse over)
|
|
. reqLen will indicate how many bytes the entire full record is
|
|
* get more data from peer and call again
|
|
|
|
SSL_FULL (implies decode completed fully but couldn't fit response)
|
|
. buf will not have moved (it is reset to the front of final record)
|
|
. len will be 0 to indicate no remaining unprocessed data
|
|
. reqLen will inform how large buf should be grown before re-invoking
|
|
* realloc the buf to the reqLen size and call again
|
|
|
|
SSL_SEND_RESPONSE
|
|
. buf will point to the encoded handshake data to send
|
|
. len will be length of data to send (from start offset)
|
|
* pass the buf to the transport layer for sending to peer
|
|
|
|
SSL_ALERT
|
|
. buf will point to start of received alert (2 bytes alert level and desc)
|
|
. len will be length of alert data (should be 2)
|
|
. alertLevel will be 1 (warning) or 2 (fatal)
|
|
. alertDesc will be SSL specified alert code
|
|
|
|
MATRIXSSL_ERROR (unrecoverable failure)
|
|
. decodeErr is internal parse err code
|
|
|
|
SSL_PROCESS_DATA (ONLY CASE WITH DECRYPTED DATA AND POSSIBLE UNENCRYPTED)
|
|
. unencrypted user data ready for processing is at prevBuf
|
|
. buf points to start of any remaining unencrypted data
|
|
. remaining is length of remaining encrypted data yet to decode
|
|
. len is length of unencrypted data ready for user processing
|
|
* pass unencypted data to application level
|
|
* call decode again if more encrypted data remaining
|
|
|
|
*/
|
|
int32 matrixSslDecode(ssl_t *ssl, unsigned char **buf, uint32 *len,
|
|
uint32 size, uint32 *remaining, uint32 *requiredLen,
|
|
int32 *error, unsigned char *alertLevel,
|
|
unsigned char *alertDescription)
|
|
{
|
|
unsigned char *c, *p, *end, *pend, *ctStart, *origbuf;
|
|
unsigned char *mac;
|
|
unsigned char macError;
|
|
int32 rc;
|
|
unsigned char padLen;
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
sslSessOpts_t options;
|
|
#endif
|
|
psBuf_t tmpout;
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
int32 certlen, i, nextCertLen;
|
|
#endif /* USE_CERT_CHAIN_PARSING */
|
|
#ifdef USE_ZLIB_COMPRESSION
|
|
int32 preInflateLen, postInflateLen, currLen;
|
|
int zret;
|
|
#endif
|
|
/*
|
|
If we've had a protocol error, don't allow further use of the session
|
|
*/
|
|
*error = PS_SUCCESS;
|
|
if (ssl->flags & SSL_FLAGS_ERROR || ssl->flags & SSL_FLAGS_CLOSED) {
|
|
psTraceInfo("Can't use matrixSslDecode on closed/error-flagged sess\n");
|
|
*error = PS_PROTOCOL_FAIL;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
|
|
origbuf = *buf; /* Save the original buffer location */
|
|
|
|
p = pend = mac = ctStart = NULL;
|
|
padLen = 0;
|
|
|
|
/*
|
|
This flag is set if the previous call to this routine returned an SSL_FULL
|
|
error from encodeResponse, indicating that there is data to be encoded,
|
|
but the out buffer was not big enough to handle it. If we fall in this
|
|
case, the user has increased the out buffer size and is re-calling this
|
|
routine
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_NEED_ENCODE) {
|
|
ssl->flags &= ~SSL_FLAGS_NEED_ENCODE;
|
|
goto encodeResponse;
|
|
}
|
|
*requiredLen = 0;
|
|
c = *buf; /* c is record parse pointer */
|
|
end = *buf + *len;
|
|
|
|
/*
|
|
Processing the SSL Record header.
|
|
If the high bit of the first byte is set and this is the first
|
|
message we've seen, we parse the request as an SSLv2 request
|
|
@see http://wp.netscape.com/eng/security/SSL_2.html
|
|
SSLv2 also supports a 3 byte header when padding is used, but this should
|
|
not be required for the initial plaintext message, so we don't support it.
|
|
|
|
@security SSLV2 ClientHello is deprecated and no longer supported.
|
|
|
|
v2 Header:
|
|
2 bytes length (ignore high bit)
|
|
v3 Header:
|
|
1 byte type
|
|
1 byte major version
|
|
1 byte minor version
|
|
2 bytes length
|
|
*/
|
|
#ifdef USE_DTLS
|
|
decodeMore:
|
|
#endif
|
|
if (end - c == 0) {
|
|
/*
|
|
This case could happen if change cipher spec was last
|
|
message in the buffer or if there is a zero-length record
|
|
at the end of a multi-record application data buffer.
|
|
*/
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
/* Even for SSLv2, we want at least 5 bytes in the record to continue */
|
|
if (end - c < SSL3_HEADER_LEN) {
|
|
*requiredLen = SSL3_HEADER_LEN;
|
|
return SSL_PARTIAL;
|
|
}
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
/*
|
|
If we're in process of parsing a partial record, then skip the
|
|
usual record header parse. Currently we're only supporting
|
|
partial parsing for the certificate messages since they are the
|
|
largest in size.
|
|
*/
|
|
if (ssl->rec.partial != 0x0) {
|
|
psAssert(ssl->rec.type == SSL_RECORD_TYPE_HANDSHAKE);
|
|
psAssert(ssl->hsState == SSL_HS_CERTIFICATE);
|
|
/*
|
|
Get this next record length based on the certificate size,
|
|
which will always be the first three bytes of a partial here
|
|
*/
|
|
ssl->rec.len = c[0] << 16;
|
|
ssl->rec.len |= c[1] << 8;
|
|
ssl->rec.len |= c[2];
|
|
ssl->rec.len += 3;
|
|
goto SKIP_RECORD_PARSE;
|
|
}
|
|
#endif /* USE_CERT_CHAIN_PARSING */
|
|
|
|
if (ssl->majVer != 0 || (*c & 0x80) == 0) {
|
|
if (end - c < ssl->recordHeadLen) {
|
|
*requiredLen = ssl->recordHeadLen;
|
|
return SSL_PARTIAL;
|
|
}
|
|
ssl->rec.type = *c; c++;
|
|
ssl->rec.majVer = *c; c++;
|
|
ssl->rec.minVer = *c; c++;
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
if (ssl->rec.majVer == DTLS_MAJ_VER &&
|
|
ssl->rec.minVer >= DTLS_1_2_MIN_VER) {
|
|
ssl->rec.epoch[0] = *c++;
|
|
ssl->rec.epoch[1] = *c++;
|
|
ssl->rec.rsn[0] = *c++;
|
|
ssl->rec.rsn[1] = *c++;
|
|
ssl->rec.rsn[2] = *c++;
|
|
ssl->rec.rsn[3] = *c++;
|
|
ssl->rec.rsn[4] = *c++;
|
|
ssl->rec.rsn[5] = *c++;
|
|
} else {
|
|
psTraceIntDtls("Expecting DTLS record version. Got %d\n",
|
|
ssl->rec.majVer);
|
|
*error = PS_PROTOCOL_FAIL;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
}
|
|
#endif /* USE_DTLS */
|
|
ssl->rec.len = *c << 8; c++;
|
|
ssl->rec.len += *c; c++;
|
|
} else {
|
|
/* OpenSSL 0.9.8 will send a SSLv2 CLIENT_HELLO. Use the -no_ssl2
|
|
option when running a 0.9.8 client to prevent this */
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("SSLv2 records not supported\n");
|
|
goto encodeResponse;
|
|
}
|
|
/*
|
|
Validate the various record headers. The type must be valid,
|
|
the major and minor versions must match the negotiated versions (if we're
|
|
past ClientHello) and the length must be < 16K and > 0
|
|
*/
|
|
switch (ssl->rec.type) {
|
|
case SSL_RECORD_TYPE_CHANGE_CIPHER_SPEC:
|
|
case SSL_RECORD_TYPE_ALERT:
|
|
case SSL_RECORD_TYPE_HANDSHAKE:
|
|
case SSL_RECORD_TYPE_APPLICATION_DATA:
|
|
break;
|
|
/* Any other case is unrecognized */
|
|
default:
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceIntInfo("Record header type not valid: %d\n", ssl->rec.type);
|
|
goto encodeResponse;
|
|
}
|
|
|
|
/*
|
|
Verify the record version numbers unless this is the first record we're
|
|
reading.
|
|
*/
|
|
if (ssl->hsState != SSL_HS_SERVER_HELLO &&
|
|
ssl->hsState != SSL_HS_CLIENT_HELLO) {
|
|
if (ssl->rec.majVer != ssl->majVer || ssl->rec.minVer != ssl->minVer) {
|
|
#ifdef SSL_REHANDSHAKES_ENABLED
|
|
/* If in DONE state and this version doesn't match the previously
|
|
negotiated one that can be OK because a CLIENT_HELLO for a
|
|
rehandshake might be acting like a first time send and using
|
|
a lower version to get to the parsing phase. Unsupported
|
|
versions will be weeded out at CLIENT_HELLO parse time */
|
|
if (ssl->hsState != SSL_HS_DONE ||
|
|
ssl->rec.type != SSL_RECORD_TYPE_HANDSHAKE) {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("Record header version not valid\n");
|
|
goto encodeResponse;
|
|
}
|
|
#else
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("Record header version not valid\n");
|
|
goto encodeResponse;
|
|
#endif
|
|
}
|
|
}
|
|
/*
|
|
Verify max and min record lengths
|
|
*/
|
|
if (ssl->rec.len > SSL_MAX_RECORD_LEN || ssl->rec.len == 0) {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceIntInfo("Record header length not valid: %d\n", ssl->rec.len);
|
|
goto encodeResponse;
|
|
}
|
|
/*
|
|
This implementation requires the entire SSL record to be in the 'in' buffer
|
|
before we parse it. This is because we need to MAC the entire record before
|
|
allowing it to be used by the caller.
|
|
*/
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
SKIP_RECORD_PARSE:
|
|
if ((end - c < ssl->rec.len) || ssl->rec.partial) {
|
|
/*
|
|
This feature will only work if the CERTIFICATE message is sent in a
|
|
different record from the SERVER_HELLO message.
|
|
*/
|
|
if (ssl->hsState != SSL_HS_CERTIFICATE) {
|
|
ssl->rec.partial = 0x0;
|
|
*requiredLen = ssl->rec.len + ssl->recordHeadLen;
|
|
return SSL_PARTIAL;
|
|
}
|
|
/*
|
|
Not supporting cert stream parsing for re-handshake. This is
|
|
important because the block cipher assumes a single pass is a record
|
|
and will use explicit IV each pass
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_READ_SECURE) {
|
|
ssl->rec.partial = 0x0;
|
|
*requiredLen = ssl->rec.len + ssl->recordHeadLen;
|
|
return SSL_PARTIAL;
|
|
}
|
|
/*
|
|
Manipulate the rec.len for partial handling
|
|
*/
|
|
i = 0;
|
|
if (ssl->rec.partial == 0x0) {
|
|
/*
|
|
Initialization for partial parse counters
|
|
*/
|
|
ssl->rec.hsBytesHashed = 0;
|
|
ssl->rec.hsBytesParsed = 0;
|
|
ssl->rec.partial = 0x1;
|
|
ssl->rec.trueLen = ssl->rec.len + ssl->recordHeadLen;
|
|
ssl->rec.len = 0;
|
|
/*
|
|
Best to identify and isolate full certificate boundaries
|
|
ASAP to keep parsing logic as high level as possible.
|
|
|
|
Current state of record buffer: pointer at start of HS record
|
|
which begins with 4 bytes of hsType(1) and hsLen(3). After
|
|
the header are 3 bytes of certchainlen and 3 bytes of first
|
|
cert len. Make sure we have at least one full cert here before
|
|
allowing the partial parse.
|
|
*/
|
|
if (end - c < (ssl->hshakeHeadLen + 6)) { /* 3*2 cert chain len */
|
|
ssl->rec.partial = 0x0; /* Unusable. Reset */
|
|
*requiredLen = ssl->hshakeHeadLen + 6;
|
|
return SSL_PARTIAL;
|
|
}
|
|
ssl->rec.len += (ssl->hshakeHeadLen + 3);
|
|
i = ssl->hshakeHeadLen;
|
|
certlen = c[i] << 16; i++;
|
|
certlen |= c[i] << 8; i++;
|
|
certlen |= c[i]; i++;
|
|
/*
|
|
This feature only works if the CERTIFICATE message is the only
|
|
message in the record. Test this by seeing that trueLen doesn't
|
|
claim there is more to follow
|
|
*/
|
|
if (ssl->rec.trueLen != (certlen + 3 + ssl->hshakeHeadLen +
|
|
ssl->recordHeadLen)) {
|
|
ssl->rec.partial = 0x0; /* Unusable. Reset */
|
|
*requiredLen = ssl->rec.trueLen;
|
|
return SSL_PARTIAL;
|
|
}
|
|
/* First cert length */
|
|
ssl->rec.len += 3;
|
|
certlen = c[i] << 16; i++;
|
|
certlen |= c[i] << 8; i++;
|
|
certlen |= c[i];
|
|
ssl->rec.len += certlen;
|
|
}
|
|
|
|
/* One complete cert? */
|
|
if (end - c < ssl->rec.len) {
|
|
/*
|
|
If there isn't a full cert in the first partial, we reset and
|
|
handle as the standard SSL_PARTIAL case.
|
|
*/
|
|
if (ssl->rec.hsBytesParsed == 0) {
|
|
ssl->rec.partial = 0x0; /* Unusable. Reset */
|
|
*requiredLen = ssl->rec.len + ssl->recordHeadLen;
|
|
} else {
|
|
/* Record header has already been parsed */
|
|
*requiredLen = ssl->rec.len;
|
|
}
|
|
return SSL_PARTIAL; /* Standard partial case */
|
|
}
|
|
|
|
/* More than one complete cert? */
|
|
while (end - c > ssl->rec.len)
|
|
{
|
|
if (ssl->rec.len + ssl->rec.hsBytesParsed == ssl->rec.trueLen) {
|
|
/*
|
|
Don't try to read another cert if the total of already parsed
|
|
record and the length of the current record match the 'trueLen'.
|
|
If they are equal, we know we are on the final cert and don't
|
|
need to look for more
|
|
*/
|
|
break;
|
|
}
|
|
psAssert(ssl->rec.len + ssl->rec.hsBytesParsed <= ssl->rec.trueLen);
|
|
nextCertLen = c[ssl->rec.len] << 16;
|
|
nextCertLen |= c[ssl->rec.len + 1] << 8;
|
|
nextCertLen |= c[ssl->rec.len + 2];
|
|
if (end - c > (ssl->rec.len + nextCertLen + 3)) {
|
|
ssl->rec.len += (nextCertLen + 3);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
if (end - c < ssl->rec.len) {
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
psTraceInfo("DTLS error: Received PARTIAL record from peer.\n");
|
|
psTraceInfo("This indicates a PMTU mismatch\n");
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
goto encodeResponse;
|
|
}
|
|
#endif /* USE_DTLS */
|
|
*requiredLen = ssl->rec.len + ssl->recordHeadLen;
|
|
return SSL_PARTIAL;
|
|
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
|
|
/* Epoch and RSN validation. Silently ignore most mismatches (SUCCESS) */
|
|
rc = dtlsCompareEpoch(ssl->rec.epoch, ssl->expectedEpoch);
|
|
/* These cases have become pretty complex due to a code change in which
|
|
the epoch is always incremented when CCS is sent. We used to
|
|
reset the FINISHED epoch back to +1 of the current epoch when
|
|
resending the FINISHED flight but a customer had a problem with this
|
|
because they thought every single message must be unique for epoch
|
|
and sequence number. They were probably correct but now it's a
|
|
real mess trying to keep the expectedEpoch up-to-date when we can't
|
|
possibly know how many epoch increments the peer has made before we
|
|
receive a FINISHED message or an APPLICATION DATA record */
|
|
if (rc == 1 && ssl->rec.type == SSL_RECORD_TYPE_HANDSHAKE &&
|
|
ssl->hsState == SSL_HS_FINISHED) {
|
|
/* Special handlers for these CCS/Finished cases because epoch
|
|
could be larger for a good reason */
|
|
|
|
/* This is the case where we are getting a finished without having
|
|
seen a CCS. Preumably they will be trying again since this is
|
|
an indication that they are aware they are the senders */
|
|
if (ssl->parsedCCS == 0) {
|
|
c += ssl->rec.len;
|
|
*buf = c;
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
|
|
/* This is the case where we didn't receive a combo CCS/FINISHED
|
|
flight from the peer and they have resent with a larger epoch
|
|
for the resent FINISHED message (so as not to send a epoch/seqNo
|
|
duplicate on this resend). Make the expected epoch the new one
|
|
to reset the test for any future rehandshakes */
|
|
ssl->expectedEpoch[0] = ssl->rec.epoch[0];
|
|
ssl->expectedEpoch[1] = ssl->rec.epoch[1];
|
|
} else if (rc != 0) {
|
|
psTraceIntDtls("Epoch mismatch %d ", ssl->rec.epoch[1]);
|
|
psTraceIntDtls("on a record type of %d\n", ssl->rec.type);
|
|
|
|
/* Another corner case where the peer has sent repeat FINISHED
|
|
messages when we are in the state where we are finished.
|
|
Need to keep the expectedEpoch up-to-date then because when
|
|
the peer finally gets around to sending application data it
|
|
will be sending it on the last epoch it sent for the final
|
|
FINISHED. */
|
|
if (rc == 1 && ssl->rec.type == SSL_RECORD_TYPE_HANDSHAKE &&
|
|
ssl->hsState == SSL_HS_DONE) {
|
|
ssl->expectedEpoch[0] = ssl->rec.epoch[0];
|
|
ssl->expectedEpoch[1] = ssl->rec.epoch[1];
|
|
}
|
|
|
|
/* Yet another corner case where we are receiving application data
|
|
that has an epoch larger than we were expecting. This could
|
|
happen if the peer has been sending "duplicate" FINISHED
|
|
messages in which we have already parsed an earlier one and we
|
|
are in the done state. If we didn't receive those duplicate
|
|
FINISHED messages and are now getting an APPLICATION record,
|
|
let's just try to decrypt it and get this communication going */
|
|
if (rc == 1 && ssl->rec.type == SSL_RECORD_TYPE_APPLICATION_DATA &&
|
|
ssl->hsState == SSL_HS_DONE) {
|
|
ssl->expectedEpoch[0] = ssl->rec.epoch[0];
|
|
ssl->expectedEpoch[1] = ssl->rec.epoch[1];
|
|
goto ADVANCE_TO_APP_DATA;
|
|
}
|
|
|
|
/* Now just skip the record as a duplicate */
|
|
|
|
c += ssl->rec.len;
|
|
*buf = c;
|
|
/* If this is a ChangeCipherSpec message from the peer
|
|
and we have never received encypted application data this is
|
|
probably the 'endgame' problem in which the peer never received
|
|
our final handshake flight. Trigger a resend in this specific
|
|
case */
|
|
if ((ssl->rec.type == SSL_RECORD_TYPE_CHANGE_CIPHER_SPEC) &&
|
|
(ssl->appDataExch == 0)) {
|
|
/* Need to make sure we mark the rest of this buffer as read.
|
|
The CCS message can be passed in here with the FINISHED tacked
|
|
on. OpenSSL sends them separately but most wouldn't */
|
|
if (end != c) {
|
|
psAssert(*c == SSL_RECORD_TYPE_HANDSHAKE); /* Finished */
|
|
c += 11; /* Skip type, version, epoch to get to length */
|
|
/* borrow rc since we will be leaving here anyway */
|
|
rc = *c << 8; c++;
|
|
rc += *c; c++;
|
|
c += rc; /* Skip FINISHED message we've already accepted */
|
|
*buf = c;
|
|
}
|
|
return DTLS_RETRANSMIT;
|
|
}
|
|
if (end - c > 0) {
|
|
goto decodeMore;
|
|
}
|
|
|
|
/* Next, check if this is a record on a session that the server
|
|
has already closed. Server timed out this client completely and then
|
|
the client decides to send a new encoded client hello or app data
|
|
on an epoch that it thinks is fine. If we are getting an epoch
|
|
greater than ours and we don't even have a state for this client,
|
|
an error should be returned so the ssl session can be deleted */
|
|
if (rc == 1 && ssl->flags & SSL_FLAGS_SERVER &&
|
|
ssl->hsState == SSL_HS_CLIENT_HELLO) {
|
|
*buf = origbuf;
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceDtls("Client sending record on closed session\n");
|
|
goto encodeResponse;
|
|
}
|
|
|
|
/* If getting epoch that is less than expected, we'll resend */
|
|
if (rc == -1) {
|
|
return DTLS_RETRANSMIT;
|
|
}
|
|
/* Got FINISHED message without ever getting a change cipher spec */
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
|
|
if (dtlsChkReplayWindow(ssl, ssl->rec.rsn) != 1) {
|
|
psTraceIntDtls("Seen this record before %d\n", ssl->rec.rsn[5]);
|
|
c += ssl->rec.len;
|
|
*buf = c;
|
|
if (end - c > 0) {
|
|
goto decodeMore;
|
|
}
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
}
|
|
ADVANCE_TO_APP_DATA:
|
|
#endif /* USE_DTLS */
|
|
|
|
#ifdef USE_MATRIXSSL_STATS
|
|
if (ssl->rec.type == SSL_RECORD_TYPE_APPLICATION_DATA) {
|
|
matrixsslUpdateStat(ssl, APP_DATA_RECV_STAT, ssl->rec.len +
|
|
ssl->recordHeadLen);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
Decrypt the entire record contents. The record length should be
|
|
a multiple of block size, or decrypt will return an error
|
|
If we're still handshaking and sending plaintext, the decryption
|
|
callback will point to a null provider that passes the data unchanged
|
|
*/
|
|
|
|
ctStart = origbuf; /* Clear-text start. Decrypt to the front */
|
|
|
|
/* Sanity check ct len. Step 1 of Lucky 13 MEE-TLS-CBC decryption.
|
|
max{b, t + 1} is always "t + 1" because largest possible blocksize
|
|
is 16 and smallest possible tag len is 16. Multiple of block size test
|
|
is done in decrypt. We return the identical error as if the mac failed,
|
|
since this is a sanity check for pad and mac verification. */
|
|
if ((ssl->flags & SSL_FLAGS_READ_SECURE) && (ssl->deBlockSize > 1) &&
|
|
!(ssl->flags & SSL_FLAGS_AEAD_R)) {
|
|
#ifdef USE_TLS_1_1
|
|
if (ssl->flags & SSL_FLAGS_TLS_1_1) {
|
|
if (ssl->rec.len < (ssl->deMacSize + 1 + ssl->deBlockSize)) {
|
|
ssl->err = SSL_ALERT_BAD_RECORD_MAC;
|
|
psTraceInfo("Ciphertext length failed sanity\n");
|
|
goto encodeResponse;
|
|
}
|
|
} else {
|
|
if (ssl->rec.len < (ssl->deMacSize + 1)) {
|
|
ssl->err = SSL_ALERT_BAD_RECORD_MAC;
|
|
psTraceInfo("Ciphertext length failed sanity\n");
|
|
goto encodeResponse;
|
|
}
|
|
}
|
|
#else
|
|
if (ssl->rec.len < (ssl->deMacSize + 1)) {
|
|
ssl->err = SSL_ALERT_BAD_RECORD_MAC;
|
|
psTraceInfo("Ciphertext length failed sanity\n");
|
|
goto encodeResponse;
|
|
}
|
|
#endif /* USE_TLS_1_1 */
|
|
}
|
|
|
|
/* CT to PT */
|
|
if (ssl->decrypt(ssl, c, ctStart, ssl->rec.len) < 0) {
|
|
ssl->err = SSL_ALERT_DECRYPT_ERROR;
|
|
psTraceInfo("Couldn't decrypt record data 2\n");
|
|
goto encodeResponse;
|
|
}
|
|
|
|
c += ssl->rec.len;
|
|
|
|
if (ssl->flags & SSL_FLAGS_AEAD_R) {
|
|
/* AEAD needs a bit of manual length manipulation for buffer mgmnt */
|
|
ssl->rec.len -= AEAD_TAG_LEN(ssl);
|
|
if (ssl->flags & SSL_FLAGS_NONCE_R) {
|
|
ssl->rec.len -= TLS_EXPLICIT_NONCE_LEN;
|
|
}
|
|
}
|
|
/*
|
|
If we're reading a secure message, we need to validate the MAC and
|
|
padding (if using a block cipher). Insecure messages do not have
|
|
a trailing MAC or any padding.
|
|
|
|
SECURITY - There are several vulnerabilities in block cipher padding
|
|
that we handle in the below code. For more information see:
|
|
http://www.openssl.org/~bodo/tls-cbc.txt
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_READ_SECURE && !(ssl->flags & SSL_FLAGS_AEAD_R)){
|
|
/*
|
|
Start tracking MAC errors, rather then immediately catching them to
|
|
stop timing and alert description attacks that differentiate between
|
|
a padding error and a MAC error.
|
|
*/
|
|
macError = 0;
|
|
/*
|
|
Decode padding only if blocksize is > 0 (we're using a block cipher),
|
|
otherwise no padding will be present, and the mac is the last
|
|
macSize bytes of the record.
|
|
*/
|
|
if (ssl->deBlockSize <= 1) {
|
|
mac = ctStart + ssl->rec.len - ssl->deMacSize;
|
|
} else {
|
|
/*
|
|
The goal from here through completion of ssl->verifyMac call is a
|
|
constant processing time for a given record length. Going to
|
|
follow the suggestions of the Lucky 13 research paper section
|
|
"Careful implementation of MEE-TLS-CBC decryption".
|
|
http://www.isg.rhul.ac.uk/tls/TLStiming.pdf
|
|
|
|
Consistent timing is still a "goal" here. This implementation
|
|
accounts for the largest timing discrepencies but is not a
|
|
strict "clock cycles" equalizer. The complexity of the attack
|
|
circumstances and plaintext recovery possibilities using these
|
|
techniques is almost entirely in the academic realm. Improvements
|
|
to this code will be an ongoing process as research uncovers
|
|
more practical plaintext recovery threats.
|
|
|
|
Verify the pad data for block ciphers
|
|
c points within the cipher text, p points within the plaintext
|
|
The last byte of the record is the pad length
|
|
*/
|
|
p = ctStart + ssl->rec.len;
|
|
padLen = *(p - 1);
|
|
/*
|
|
SSL3.0 requires the pad length to be less than blockSize
|
|
TLS can have a pad length up to 255 for obfuscating the data len
|
|
*/
|
|
if (ssl->majVer == SSL3_MAJ_VER && ssl->minVer == SSL3_MIN_VER &&
|
|
padLen >= ssl->deBlockSize) {
|
|
macError = 1;
|
|
}
|
|
/*
|
|
The minimum record length is the size of the mac, plus pad bytes
|
|
plus one length byte, plus explicit IV if TLS 1.1 or above
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_TLS_1_1) {
|
|
if (ssl->rec.len < ssl->deMacSize + padLen + 1 + ssl->deBlockSize) {
|
|
macError = 2;
|
|
}
|
|
} else {
|
|
if (ssl->rec.len < ssl->deMacSize + padLen + 1) {
|
|
macError = 3;
|
|
}
|
|
}
|
|
if (macError) {
|
|
/* Step 3 of Lucky 13 MEE-TLS-CBC decryption: Run a loop as
|
|
if there were 256 bytes of padding, with a dummy check
|
|
in each iteration*/
|
|
for (rc = 255; rc >= 0; rc--) {
|
|
/* make the test a moving target so it doesn't get
|
|
optimized out at compile. The loop is written
|
|
this way so the macError assignment will be done
|
|
only once */
|
|
if ((unsigned char)rc == padLen) {
|
|
macError = 1; /* No incr to avoid any wraps */
|
|
}
|
|
}
|
|
}
|
|
#ifdef USE_TLS
|
|
/*
|
|
TLS specifies that all pad bytes must have the same value
|
|
as the final pad length byte. Some SSL3 implementations also
|
|
do this by convention, but some just fill with random bytes.
|
|
(We're just overloading the 'mac' ptr here, this has nothing to
|
|
do with real MAC.)
|
|
*/
|
|
if (!macError && ssl->majVer == TLS_MAJ_VER &&
|
|
ssl->minVer >= TLS_MIN_VER) {
|
|
for (mac = p - padLen - 1; mac < p; mac++) {
|
|
if (*mac != padLen) {
|
|
macError = 1;
|
|
}
|
|
}
|
|
/* Lucky 13 step 4. If this fails, then run a loop as if there
|
|
were 256 - padlen - 1 bytes of padding, with a dummy
|
|
check in each iteration */
|
|
if (macError) {
|
|
for (rc = 256 - padLen - 1; rc > 0; rc--) {
|
|
/* make the test a moving target so it doesn't get
|
|
optimized out at compile. Again, make it so
|
|
the loop condition doesn't get hit more than
|
|
once. */
|
|
if ((unsigned char)rc == padLen) {
|
|
macError = 2; /* change value for smart compilers */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* USE_TLS */
|
|
/*
|
|
The mac starts macSize bytes before the padding and length byte.
|
|
If we have a macError, just fake the mac as the last macSize bytes
|
|
of the record, so we are sure to have enough bytes to verify
|
|
against, we'll fail anyway, so the actual contents don't matter.
|
|
*/
|
|
if (!macError) {
|
|
/* No padding errors */
|
|
mac = p - padLen - 1 - ssl->deMacSize;
|
|
/* Lucky 13 step 5: Otherwise (the padding is now correctly
|
|
formatted) run a loop as if there were 256 - padlen - 1
|
|
bytes of padding, doing a dummy check in each iteration */
|
|
for (rc = (256 - padLen) - 1; rc > 0; rc--) {
|
|
/* make this test look like the others */
|
|
if ((unsigned char)rc == padLen) {
|
|
/* coverity[assigned_value] */
|
|
macError = 1; /* not really an error. reset below */
|
|
}
|
|
}
|
|
(void)macError; /* Suppress static analysis warnings */
|
|
macError = 0;
|
|
} else {
|
|
/* Lucky 13 step 3 and 4 condition: Then let P' denote the first
|
|
plen - t bytes of P, compute a MAC on SQN||HDR||P' and do a
|
|
constant-time comparison of the computed MAC with the
|
|
last t bytes of P. Return fatal error. */
|
|
mac = origbuf + ssl->rec.len - ssl->deMacSize;
|
|
}
|
|
}
|
|
/*
|
|
Verify the MAC of the message by calculating our own MAC of the message
|
|
and comparing it to the one in the message. We do this step regardless
|
|
of whether or not we've already set macError to stop timing attacks.
|
|
Clear the mac in the callers buffer if we're successful
|
|
*/
|
|
#ifdef USE_TLS_1_1
|
|
if ((ssl->flags & SSL_FLAGS_TLS_1_1) && (ssl->deBlockSize > 1)) {
|
|
ctStart += ssl->deBlockSize; /* skip explicit IV */
|
|
}
|
|
#endif
|
|
|
|
#ifdef LUCKY13
|
|
/*
|
|
Lucky 13 Step 5. If using a block cipher, blind the mac operation.
|
|
Doing this extra MAC compression here rather
|
|
than inside the real verify to keep this code patch at the
|
|
protocol level.
|
|
The Sha Update calls are with an exact state size for the
|
|
hash, so the compress function will be called 1:1 with the Update.
|
|
*/
|
|
if (ssl->deBlockSize > 1) {
|
|
/* Run this helper regardless of error status thus far */
|
|
rc = addCompressCount(ssl, padLen);
|
|
if (macError == 0) {
|
|
#ifndef USE_CL_DIGESTS
|
|
psDigestContext_t md;
|
|
unsigned char tmp[128];
|
|
switch (ssl->deMacSize) {
|
|
#ifdef USE_SHA256
|
|
case SHA256_HASH_SIZE:
|
|
psSha256Init(&md.sha256);
|
|
while (rc > 0) {
|
|
psSha256Update(&md.sha256, tmp, 64);
|
|
rc--;
|
|
}
|
|
psSha256Final(&md.sha256, tmp);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA384
|
|
case SHA384_HASH_SIZE:
|
|
psSha384Init(&md.sha384);
|
|
while (rc > 0) {
|
|
psSha384Update(&md.sha384, tmp, 128);
|
|
rc--;
|
|
}
|
|
psSha384Final(&md.sha384, tmp);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SHA1
|
|
case SHA1_HASH_SIZE:
|
|
psSha1Init(&md.sha1);
|
|
while (rc > 0) {
|
|
psSha1Update(&md.sha1, tmp, 64);
|
|
rc--;
|
|
}
|
|
psSha1Final(&md.sha1, tmp);
|
|
break;
|
|
#endif
|
|
default:
|
|
psAssert(0);
|
|
break;
|
|
}
|
|
#else
|
|
/* With FIPS implementation,
|
|
psHmacSha*Tls functions shall be used to
|
|
mitigate Lucky13. */
|
|
#endif /* USE_CL_DIGESTS */
|
|
}
|
|
}
|
|
#endif /* LUCKY13 */
|
|
|
|
if (ssl->verifyMac(ssl, ssl->rec.type, ctStart,
|
|
(uint32)(mac - ctStart), mac) < 0 || macError) {
|
|
ssl->err = SSL_ALERT_BAD_RECORD_MAC;
|
|
psTraceInfo("Couldn't verify MAC or pad of record data\n");
|
|
goto encodeResponse;
|
|
}
|
|
|
|
memset(mac, 0x0, ssl->deMacSize);
|
|
|
|
/* Record data starts at ctStart and ends at mac */
|
|
p = ctStart;
|
|
pend = mac;
|
|
} else {
|
|
/*
|
|
The record data is the entire record as there is no MAC or padding
|
|
*/
|
|
p = ctStart;
|
|
pend = mac = ctStart + ssl->rec.len;
|
|
}
|
|
|
|
#ifdef USE_ZLIB_COMPRESSION
|
|
/* Currently only supporting compression of FINISHED message.
|
|
Compressed application data is handled outside MatrixSSL.
|
|
Re-handshakes are not allowed with compression and we've
|
|
incremented ssl->compression if we've already been through here
|
|
so we'll know */
|
|
if (ssl->compression == 2 && ssl->flags & SSL_FLAGS_READ_SECURE &&
|
|
ssl->rec.type == SSL_RECORD_TYPE_HANDSHAKE) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
psTraceInfo("Re-handshakes not supported on compressed sessions\n");
|
|
goto encodeResponse;
|
|
}
|
|
if (ssl->compression && ssl->flags & SSL_FLAGS_READ_SECURE &&
|
|
ssl->rec.type == SSL_RECORD_TYPE_HANDSHAKE) {
|
|
/* TODO - handle the cases below where the buffer has to grow */
|
|
currLen = ssl->inflate.total_out;
|
|
preInflateLen = (int32)(pend - p);
|
|
ssl->zlibBuffer = psMalloc(ssl->bufferPool, preInflateLen +
|
|
MATRIX_INFLATE_FINISHED_OH);
|
|
if (ssl->zlibBuffer == NULL) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
psTraceInfo("Couldn't allocate compressed scratch pad\n");
|
|
goto encodeResponse;
|
|
}
|
|
memset(ssl->zlibBuffer, 0, preInflateLen + MATRIX_INFLATE_FINISHED_OH);
|
|
if (preInflateLen > 0) { /* zero length record possible */
|
|
/* psTraceBytes("pre inflate", ctStart, preInflateLen); */
|
|
ssl->inflate.next_in = ctStart;
|
|
ssl->inflate.avail_in = preInflateLen;
|
|
ssl->inflate.next_out = ssl->zlibBuffer;
|
|
ssl->inflate.avail_out = SSL_MAX_PLAINTEXT_LEN;
|
|
if ((zret = inflate(&ssl->inflate, Z_SYNC_FLUSH)) != Z_OK) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
psFree(ssl->zlibBuffer, ssl->bufferPool); ssl->zlibBuffer =NULL;
|
|
inflateEnd(&ssl->inflate);
|
|
psTraceIntInfo("ZLIB inflate failed %d\n", zret);
|
|
goto encodeResponse;
|
|
}
|
|
if (ssl->inflate.avail_in != 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
psFree(ssl->zlibBuffer, ssl->bufferPool); ssl->zlibBuffer =NULL;
|
|
inflateEnd(&ssl->inflate);
|
|
psTraceInfo("ZLIB inflate didn't work in one pass\n");
|
|
goto encodeResponse;
|
|
}
|
|
postInflateLen = ssl->inflate.total_out - currLen;
|
|
|
|
/* psTraceBytes("post inflate", ssl->zlibBuffer,
|
|
postInflateLen); */
|
|
|
|
if (postInflateLen <= preInflateLen) {
|
|
/* Easy case where compressed data was actually larger.
|
|
Don't need to update c or inlen because the next
|
|
good data is already correctly being pointed to */
|
|
memcpy(p, ssl->zlibBuffer, postInflateLen);
|
|
mac = p + postInflateLen;
|
|
pend = mac;
|
|
} else {
|
|
/* Data expanded. Fit it in the buffer and update all
|
|
the associated lengths and pointers
|
|
|
|
Add back in the MAC and pad to preInflate so we're
|
|
looking at the useful boundaries of the buffers */
|
|
preInflateLen += (int32)(c - mac);
|
|
/* reusing currLen var. Now the difference in lengths */
|
|
currLen = postInflateLen - preInflateLen;
|
|
if ((int32)(c - ssl->inbuf) == ssl->inlen) {
|
|
/* Good, this was the only data in the buffer. Just
|
|
check there is room to append */
|
|
if ((ssl->insize - ssl->inlen) >= postInflateLen) {
|
|
memcpy(p, ssl->zlibBuffer, postInflateLen);
|
|
c += currLen;
|
|
mac = p + postInflateLen;
|
|
pend = mac;
|
|
} else {
|
|
/* Only one here but not enough room to store it */
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
psFree(ssl->zlibBuffer, ssl->bufferPool);
|
|
ssl->zlibBuffer = NULL;
|
|
inflateEnd(&ssl->inflate);
|
|
psTraceInfo("ZLIB buffer management needed\n");
|
|
goto encodeResponse;
|
|
}
|
|
} else {
|
|
/* Push any existing data further back in the buffer to
|
|
make room for this uncompressed length. c pointing
|
|
to start of next record that needs to be pushed
|
|
back. currLen is how far to push back.
|
|
p pointing to where zlibBuffer should copy
|
|
to. postInflateLen is amount to copy there. */
|
|
if (currLen < (ssl->insize - ssl->inlen)) {
|
|
/* Good, fits in current buffer. Move all valid
|
|
data back currLen */
|
|
memmove(c + currLen, c,
|
|
ssl->inlen - (int32)(c - ssl->inbuf));
|
|
c += currLen;
|
|
memcpy(p, ssl->zlibBuffer, postInflateLen);
|
|
mac = p + postInflateLen;
|
|
pend = mac;
|
|
} else {
|
|
/* Need to realloc more space AND push the records
|
|
back */
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
psFree(ssl->zlibBuffer, ssl->bufferPool);
|
|
ssl->zlibBuffer = NULL;
|
|
inflateEnd(&ssl->inflate);
|
|
psTraceInfo("ZLIB buffer management needed\n");
|
|
goto encodeResponse;
|
|
}
|
|
}
|
|
/* Finally increase inlen and *len to account for it now */
|
|
ssl->inlen += currLen;
|
|
*len += currLen;
|
|
ssl->rec.len += currLen;
|
|
}
|
|
}
|
|
psFree(ssl->zlibBuffer, ssl->bufferPool); ssl->zlibBuffer = NULL;
|
|
/* Will not need the context any longer since FINISHED is the only
|
|
supported message */
|
|
inflateEnd(&ssl->inflate);
|
|
ssl->compression = 2;
|
|
}
|
|
#endif /* USE_ZLIB_COMPRESSION */
|
|
|
|
/* Check now for maximum plaintext length of 16kb. */
|
|
if (ssl->maxPtFrag == 0xFF) { /* Still negotiating size */
|
|
if ((int32)(pend - p) > SSL_MAX_PLAINTEXT_LEN) {
|
|
ssl->err = SSL_ALERT_RECORD_OVERFLOW;
|
|
psTraceInfo("Record overflow\n");
|
|
goto encodeResponse;
|
|
}
|
|
} else {
|
|
if ((int32)(pend - p) > ssl->maxPtFrag) {
|
|
ssl->err = SSL_ALERT_RECORD_OVERFLOW;
|
|
psTraceInfo("Record overflow\n");
|
|
goto encodeResponse;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Take action based on the actual record type we're dealing with
|
|
'p' points to the start of the data, and 'pend' points to the end
|
|
*/
|
|
switch (ssl->rec.type) {
|
|
case SSL_RECORD_TYPE_CHANGE_CIPHER_SPEC:
|
|
psTraceStrHs(">>> %s parsing CHANGE_CIPHER_SPEC message\n",
|
|
(ssl->flags & SSL_FLAGS_SERVER) ? "Server" : "Client");
|
|
/*
|
|
Body is single byte with value 1 to indicate that the next message
|
|
will be encrypted using the negotiated cipher suite
|
|
*/
|
|
if (pend - p < 1) {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("Invalid length for CipherSpec\n");
|
|
goto encodeResponse;
|
|
}
|
|
if (*p == 1) {
|
|
p++;
|
|
} else {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("Invalid value for CipherSpec\n");
|
|
goto encodeResponse;
|
|
}
|
|
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
if (ssl->hsState != SSL_HS_FINISHED) {
|
|
/* Possible to get the changeCipherSpec message out of order */
|
|
psTraceIntInfo("Got out of order CCS: state %d\n", ssl->hsState);
|
|
*buf = c;
|
|
goto decodeMore;
|
|
}
|
|
/* The epoch corner cases surrounding the CHANGE_CIPHER_SPEC
|
|
message are complex. Let's just finally create a clear signal
|
|
that the CCS was parsed. The general problem is that our
|
|
state machine is FINISHED when expecting either the CCS or
|
|
the FINISHED message (probably goes back to CCS having some
|
|
special record type in the specs). This will just be set
|
|
between CCS parse and FINISHED parse */
|
|
ssl->parsedCCS = 1;
|
|
}
|
|
/*
|
|
Expect epoch to increment after successful CCS parse
|
|
*/
|
|
incrTwoByte(ssl, ssl->expectedEpoch, 0);
|
|
#endif /* USE_DTLS */
|
|
|
|
*remaining = *len - (c - origbuf);
|
|
*buf = c;
|
|
/*
|
|
If we're expecting finished, then this is the right place to get
|
|
this record. It is really part of the handshake but it has its
|
|
own record type.
|
|
Activate the read cipher callbacks, so we will decrypt incoming
|
|
data from now on.
|
|
*/
|
|
if (ssl->hsState == SSL_HS_FINISHED) {
|
|
if (sslActivateReadCipher(ssl) < 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
goto encodeResponse;
|
|
}
|
|
} else {
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
/* RFC 5077 allows the server to not acknowlege whether or not it
|
|
accepted our session ticket in the SERVER_HELLO extension so
|
|
there was no place prior to recieving this CCS to find out.
|
|
Different cipher suites types will be in different states */
|
|
if (ssl->hsState == SSL_HS_CERTIFICATE && ssl->sid &&
|
|
ssl->sid->sessionTicketState == SESS_TICKET_STATE_IN_LIMBO) {
|
|
/* Do all the things that should have been done earlier */
|
|
ssl->flags |= SSL_FLAGS_RESUMED;
|
|
#ifdef USE_MATRIXSSL_STATS
|
|
matrixsslUpdateStat(ssl, RESUMPTIONS_STAT, 1);
|
|
#endif
|
|
if (sslCreateKeys(ssl) < 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
goto encodeResponse;
|
|
}
|
|
ssl->hsState = SSL_HS_FINISHED;
|
|
if (sslActivateReadCipher(ssl) < 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
goto encodeResponse;
|
|
}
|
|
ssl->sid->sessionTicketState = SESS_TICKET_STATE_INIT;
|
|
#ifdef USE_ANON_DH_CIPHER_SUITE
|
|
/* Anon DH could be in SERVER_KEY_EXCHANGE state */
|
|
} else if ((ssl->flags & SSL_FLAGS_ANON_CIPHER) &&
|
|
(ssl->hsState == SSL_HS_SERVER_KEY_EXCHANGE) && ssl->sid &&
|
|
ssl->sid->sessionTicketState == SESS_TICKET_STATE_IN_LIMBO) {
|
|
/* Do all the things that should have been done earlier */
|
|
ssl->flags |= SSL_FLAGS_RESUMED;
|
|
#ifdef USE_MATRIXSSL_STATS
|
|
matrixsslUpdateStat(ssl, RESUMPTIONS_STAT, 1);
|
|
#endif
|
|
if (sslCreateKeys(ssl) < 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
goto encodeResponse;
|
|
}
|
|
ssl->hsState = SSL_HS_FINISHED;
|
|
if (sslActivateReadCipher(ssl) < 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
goto encodeResponse;
|
|
}
|
|
ssl->sid->sessionTicketState = SESS_TICKET_STATE_INIT;
|
|
#endif /* USE_ANON_DH_CIPHER_SUITE */
|
|
#ifdef USE_PSK_CIPHER_SUITE
|
|
/* PSK could be in SERVER_KEY_EXCHANGE state */
|
|
} else if ((ssl->flags & SSL_FLAGS_PSK_CIPHER) &&
|
|
(ssl->hsState == SSL_HS_SERVER_KEY_EXCHANGE) && ssl->sid &&
|
|
ssl->sid->sessionTicketState == SESS_TICKET_STATE_IN_LIMBO) {
|
|
/* Do all the things that should have been done earlier */
|
|
ssl->flags |= SSL_FLAGS_RESUMED;
|
|
#ifdef USE_MATRIXSSL_STATS
|
|
matrixsslUpdateStat(ssl, RESUMPTIONS_STAT, 1);
|
|
#endif
|
|
if (sslCreateKeys(ssl) < 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
goto encodeResponse;
|
|
}
|
|
ssl->hsState = SSL_HS_FINISHED;
|
|
if (sslActivateReadCipher(ssl) < 0) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
goto encodeResponse;
|
|
}
|
|
ssl->sid->sessionTicketState = SESS_TICKET_STATE_INIT;
|
|
#endif /* USE_PSK_CIPHER_SUITE */
|
|
} else {
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceIntInfo("Invalid CipherSpec order: %d\n", ssl->hsState);
|
|
goto encodeResponse;
|
|
}
|
|
#else
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceIntInfo("Invalid CipherSpec order: %d\n", ssl->hsState);
|
|
goto encodeResponse;
|
|
#endif
|
|
}
|
|
ssl->decState = SSL_HS_CCC;
|
|
return MATRIXSSL_SUCCESS;
|
|
|
|
case SSL_RECORD_TYPE_ALERT:
|
|
/*
|
|
Decoded an alert
|
|
1 byte alert level (warning or fatal)
|
|
1 byte alert description corresponding to SSL_ALERT_*
|
|
*/
|
|
if (pend - p < 2) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Error in length of alert record\n");
|
|
goto encodeResponse;
|
|
}
|
|
*alertLevel = *p; p++;
|
|
*alertDescription = *p; p++;
|
|
*len = 2;
|
|
#ifdef USE_SSL_HANDSHAKE_MSG_TRACE
|
|
if (ssl->flags & SSL_FLAGS_SERVER) {
|
|
psTraceHs(">>> Server");
|
|
} else {
|
|
psTraceHs(">>> Client");
|
|
}
|
|
if (*alertDescription == SSL_ALERT_CLOSE_NOTIFY) {
|
|
psTraceHs(" parsing ALERT (CLOSE_NOTIFY) message\n");
|
|
} else {
|
|
psTraceHs(" parsing ALERT message\n");
|
|
}
|
|
#endif
|
|
psTraceIntInfo("Received alert %d\n", (int32)(*alertDescription));
|
|
/*
|
|
If the alert is fatal, or is a close message (usually a warning),
|
|
flag the session with ERROR so it cannot be used anymore.
|
|
Caller can decide whether or not to close on other warnings.
|
|
*/
|
|
if (*alertLevel == SSL_ALERT_LEVEL_FATAL) {
|
|
ssl->flags |= SSL_FLAGS_ERROR;
|
|
}
|
|
if (*alertDescription == SSL_ALERT_CLOSE_NOTIFY) {
|
|
ssl->flags |= SSL_FLAGS_CLOSED;
|
|
}
|
|
*buf = c;
|
|
ssl->decState = SSL_HS_ALERT;
|
|
return SSL_ALERT;
|
|
|
|
case SSL_RECORD_TYPE_HANDSHAKE:
|
|
/*
|
|
We've got one or more handshake messages in the record data.
|
|
The handshake parsing function will take care of all messages
|
|
and return an error if there is any problem.
|
|
If there is a response to be sent (either a return handshake
|
|
or an error alert, send it). If the message was parsed, but no
|
|
response is needed, loop up and try to parse another message
|
|
*/
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
if (ssl->rec.partial) {
|
|
if (ssl->rec.hsBytesParsed == 0) {
|
|
/*
|
|
Account for the SSL record header for first pass
|
|
*/
|
|
ssl->rec.hsBytesParsed = ssl->recordHeadLen;
|
|
}
|
|
}
|
|
#endif
|
|
rc = parseSSLHandshake(ssl, (char*)p, (uint32)(pend - p));
|
|
/* If the entire fragment is present, the parse has occured */
|
|
if (ssl->fragMessage != NULL) {
|
|
if (ssl->fragIndex == ssl->fragTotal) {
|
|
psFree(ssl->fragMessage, ssl->hsPool);
|
|
ssl->fragMessage = NULL;
|
|
ssl->fragIndex = ssl->fragTotal = 0;
|
|
}
|
|
}
|
|
switch (rc) {
|
|
case MATRIXSSL_SUCCESS:
|
|
*remaining = *len - (c - origbuf);
|
|
*buf = c;
|
|
return MATRIXSSL_SUCCESS;
|
|
|
|
#ifdef USE_DTLS
|
|
case DTLS_RETRANSMIT:
|
|
/* The idea here is to only return retransmit if
|
|
we are seeing the final message in the inbuf as repeat. Otherwise
|
|
the next msg right in this flight might be able to move our state
|
|
forward without a resend. */
|
|
*remaining = *len - (c - origbuf);
|
|
*buf = c;
|
|
if (*remaining == 0) {
|
|
return DTLS_RETRANSMIT;
|
|
} else {
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
case SSL_PROCESS_DATA:
|
|
/*
|
|
We're here when we've processed an SSL header that requires
|
|
a response. In all cases (except FALSE START), we would not
|
|
expect to have any data remaining in the incoming buffer, since
|
|
the peer would be waiting for our response.
|
|
*/
|
|
#ifdef ENABLE_FALSE_START
|
|
if (c < origbuf + *len) {
|
|
/*
|
|
If there's still incoming data in the buffer, it could be
|
|
FALSE START app data immediately after the FINISHED message,
|
|
and before we've had a chance to encode and send our
|
|
CHANGE_CIPHER_SPEC and FINISHED message. We hack around
|
|
some values to support this case.
|
|
http://tools.ietf.org/html/draft-bmoeller-tls-falsestart-00
|
|
*/
|
|
if (*c == SSL_RECORD_TYPE_APPLICATION_DATA &&
|
|
ssl->hsState == SSL_HS_DONE &&
|
|
(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
psTraceHs(">>> Server buffering FALSE START APPLICATION_DATA\n");
|
|
ssl->flags |= SSL_FLAGS_FALSE_START;
|
|
*remaining = *len - (c - origbuf);
|
|
*buf = c;
|
|
} else {
|
|
/*
|
|
Implies successful parse of supposed last message in
|
|
flight so check for the corner cases and reset the
|
|
buffer to start to write response
|
|
*/
|
|
#endif
|
|
if (*c == SSL_RECORD_TYPE_APPLICATION_DATA &&
|
|
ssl->hsState == SSL_HS_DONE &&
|
|
(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
/* If this asserts, try defining ENABLE_FALSE_START */
|
|
psAssert(origbuf + *len == c);
|
|
*buf = origbuf;
|
|
} else if (*c == SSL_RECORD_TYPE_APPLICATION_DATA &&
|
|
ssl->hsState == SSL_HS_HELLO_REQUEST &&
|
|
(c < (origbuf + *len))) {
|
|
/* message tacked on to end of HELLO_REQUEST. Very
|
|
complicated scenario for the state machine and
|
|
API so we're going to ignore the HELLO_REQUEST
|
|
(fine by the specification) and give precedence to
|
|
the app data. This backup flag data was set aside
|
|
in sslResetContext when the HELLO_REQUEST was
|
|
received */
|
|
*buf = c;
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
ssl->sec.anon = ssl->anonBk;
|
|
ssl->flags = ssl->flagsBk;
|
|
ssl->bFlags = ssl->bFlagsBk;
|
|
#endif
|
|
ssl->hsState = SSL_HS_DONE;
|
|
return MATRIXSSL_SUCCESS;
|
|
} else {
|
|
/* If this asserts, please report the values of the
|
|
*c byte and ssl->hsState to support */
|
|
psAssert(origbuf + *len == c);
|
|
*buf = origbuf;
|
|
}
|
|
#ifdef ENABLE_FALSE_START
|
|
}
|
|
} else {
|
|
*buf = origbuf;
|
|
}
|
|
#endif
|
|
goto encodeResponse;
|
|
|
|
case MATRIXSSL_ERROR:
|
|
case SSL_MEM_ERROR:
|
|
if (ssl->err == SSL_ALERT_NONE) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
}
|
|
goto encodeResponse;
|
|
default:
|
|
psTraceIntInfo("Unknown return %d from parseSSLHandshake!\n", rc);
|
|
if (ssl->err == SSL_ALERT_NONE) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
}
|
|
goto encodeResponse;
|
|
}
|
|
break;
|
|
|
|
case SSL_RECORD_TYPE_APPLICATION_DATA:
|
|
/*
|
|
Data is in the out buffer, let user handle it
|
|
Don't allow application data until handshake is complete, and we are
|
|
secure. It is ok to let application data through on the client
|
|
if we are in the SERVER_HELLO state because this could mean that
|
|
the client has sent a CLIENT_HELLO message for a rehandshake
|
|
and is awaiting reply.
|
|
*/
|
|
if ((ssl->hsState != SSL_HS_DONE && ssl->hsState != SSL_HS_SERVER_HELLO)
|
|
|| !(ssl->flags & SSL_FLAGS_READ_SECURE)) {
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceIntInfo("Incomplete handshake: %d\n", ssl->hsState);
|
|
goto encodeResponse;
|
|
}
|
|
/*
|
|
Insitu for application data is more tricky than it is for SSL handshake
|
|
messages. This is because there is never going to be any 'out' data
|
|
for handshake messages until the final record of a flight is parsed.
|
|
Whereas application data necessarily has an 'out' for every 'in'
|
|
record because it is the decrypted data of the 'in'. So, the managed
|
|
cases result anytime there is more than 1 app record in the 'in' buffer
|
|
where the insitu must hold BOTH a decrypted buffer and the next
|
|
encrypted record.
|
|
|
|
Create so that:
|
|
. 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
|
|
|
|
*/
|
|
*buf = c;
|
|
*remaining = *len - (c - origbuf);
|
|
*len = mac - origbuf;
|
|
/*
|
|
SECURITY - If the mac is at the current out->end, then there is no data
|
|
in the record. These records are valid, but are usually not sent by
|
|
the application layer protocol. Rather, they are initiated within the
|
|
remote SSL protocol implementation to avoid some types of attacks when
|
|
using block ciphers. For more information see:
|
|
http://www.openssl.org/~bodo/tls-cbc.txt
|
|
|
|
SECURITY - Returning blank messages has the potential
|
|
for denial of service, because we are not changing the state of the
|
|
system in any way when processing these messages, (although the upper
|
|
level protocol may). To counteract this, we maintain a counter
|
|
that we share with other types of ignored messages. If too many in a
|
|
row occur, an alert will be sent and the connection closed.
|
|
We implement this as a leaky bucket, so if a non-blank message comes
|
|
in, the ignored message count is decremented, ensuring that we only
|
|
error on a large number of consecutive blanks.
|
|
*/
|
|
if (ctStart == mac) {
|
|
if (ssl->ignoredMessageCount++ >= SSL_MAX_IGNORED_MESSAGE_COUNT) {
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceIntInfo("Exceeded limit on ignored messages: %d\n",
|
|
SSL_MAX_IGNORED_MESSAGE_COUNT);
|
|
goto encodeResponse;
|
|
}
|
|
} else if (ssl->ignoredMessageCount > 0) {
|
|
ssl->ignoredMessageCount--;
|
|
}
|
|
#ifdef USE_MATRIXSSL_STATS
|
|
matrixsslUpdateStat(ssl, STAT_PT_DATA_RECV, *len);
|
|
#endif
|
|
ssl->decState = SSL_HS_DONE;
|
|
return SSL_PROCESS_DATA;
|
|
|
|
default:
|
|
/* Falls to error below */
|
|
break;
|
|
}
|
|
/*
|
|
Should not get here under normal operation
|
|
*/
|
|
psTraceIntInfo("Invalid record type in matrixSslDecode: %d\n",
|
|
ssl->rec.type);
|
|
*error = PS_PROTOCOL_FAIL;
|
|
return MATRIXSSL_ERROR;
|
|
|
|
encodeResponse:
|
|
/*
|
|
We decoded a record that needs a response, either a handshake response
|
|
or an alert if we've detected an error.
|
|
*/
|
|
#ifdef ENABLE_FALSE_START
|
|
if ((ssl->flags & SSL_FLAGS_FALSE_START) && *buf != origbuf) {
|
|
/*
|
|
Encode the output into ssl->outbuf in this case, rather than back
|
|
into origbuf, since there is still valid data in origbuf that
|
|
needs to be decoded later.
|
|
Other places in this function we do not reference the ssl inbuf
|
|
or outbuf directly, but this was the cleanest way for this hack.
|
|
Caller must test to see if *buf has been modified if
|
|
ssl->flags & SSL_FLAGS_FALSE_START
|
|
*/
|
|
tmpout.buf = tmpout.start = tmpout.end = ssl->outbuf + ssl->outlen;
|
|
tmpout.size = ssl->outsize - ssl->outlen;
|
|
memset(origbuf, 0x0, (*buf - origbuf)); /* SECURITY (see below) */
|
|
} else {
|
|
#endif
|
|
psAssert(origbuf == *buf);
|
|
tmpout.buf = tmpout.end = tmpout.start = origbuf;
|
|
tmpout.size = size;
|
|
|
|
/*
|
|
SECURITY - Clear the decoded incoming record from outbuf before encoding
|
|
the response into outbuf.
|
|
*/
|
|
memset(tmpout.buf, 0x0, tmpout.size);
|
|
|
|
#ifdef ENABLE_FALSE_START
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
if (ssl->hsState == SSL_HS_HELLO_REQUEST) {
|
|
memset(&options, 0x0, sizeof(sslSessOpts_t));
|
|
/*
|
|
Don't clear the session info. If receiving a HELLO_REQUEST from a
|
|
MatrixSSL enabled server the determination on whether to reuse the
|
|
session is made on that side, so always send the current session
|
|
*/
|
|
rc = matrixSslEncodeClientHello(ssl, &tmpout, 0, 0, requiredLen, NULL,
|
|
&options);
|
|
} else {
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
rc = sslEncodeResponse(ssl, &tmpout, requiredLen);
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
}
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
*alertDescription = SSL_ALERT_NONE;
|
|
if (rc == MATRIXSSL_SUCCESS) {
|
|
if (ssl->err != SSL_ALERT_NONE) {
|
|
/* We know this is always a fatal alert due to an error in
|
|
message parsing or creation so flag this session as error */
|
|
ssl->flags |= SSL_FLAGS_ERROR;
|
|
/*
|
|
If tmpbuf has data, it is an alert that needs to be sent so let
|
|
it fall through. Not sure how we would ever not have data in tmpout
|
|
*/
|
|
if (tmpout.buf == tmpout.end) {
|
|
psTraceInfo("Unexpected data\n");
|
|
*error = PS_PROTOCOL_FAIL;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
*alertDescription = (unsigned char)ssl->err;
|
|
*alertLevel = SSL_ALERT_LEVEL_FATAL;
|
|
}
|
|
#ifdef ENABLE_FALSE_START
|
|
if ((ssl->flags & SSL_FLAGS_FALSE_START) && *buf != origbuf) {
|
|
/* Update outlen with the data we added */
|
|
ssl->outlen += tmpout.end - tmpout.buf;
|
|
} else {
|
|
#endif
|
|
*remaining = 0;
|
|
*len = tmpout.end - tmpout.buf;
|
|
#ifdef ENABLE_FALSE_START
|
|
}
|
|
#endif
|
|
return SSL_SEND_RESPONSE;
|
|
}
|
|
if (rc == SSL_FULL) {
|
|
#ifdef ENABLE_FALSE_START
|
|
/* We don't support growing outbuf in the false start case */
|
|
if (*buf != origbuf) {
|
|
psAssert(rc != SSL_FULL);
|
|
*error = rc;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
#endif
|
|
ssl->flags |= SSL_FLAGS_NEED_ENCODE;
|
|
*len = 0; /* No data left to decode */
|
|
/* requiredLen is set by sslEncode Response or ClientHello above */
|
|
return SSL_FULL;
|
|
}
|
|
psAssert(rc < 0);
|
|
*error = rc;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
|
|
#ifdef LUCKY13
|
|
/* Return the number of additional MAC compressions that are needed to blind
|
|
the padding/hmac logic for thwarting Lucky 13 style attacks
|
|
*/
|
|
static int32 addCompressCount(ssl_t *ssl, int32 padLen)
|
|
{
|
|
int32 l1, l2, c1, c2, len;
|
|
|
|
c1 = c2 = 0;
|
|
len = ssl->rec.len;
|
|
|
|
#ifdef USE_TLS_1_1
|
|
if (ssl->flags & SSL_FLAGS_TLS_1_1) {
|
|
len -= ssl->deBlockSize; /* skip explicit IV */
|
|
}
|
|
#endif
|
|
l1 = 13 + len - ssl->deMacSize;
|
|
l2 = 13 + len - padLen - 1 - ssl->deMacSize;
|
|
|
|
if (ssl->deMacSize == SHA1_HASH_SIZE || ssl->deMacSize == SHA256_HASH_SIZE){
|
|
while (l1 > 64) {
|
|
c1++; l1 -= 64;
|
|
}
|
|
if (l1 > 56) {
|
|
c1++;
|
|
}
|
|
while (l2 > 64) {
|
|
c2++; l2 -= 64;
|
|
}
|
|
if (l2 > 56) {
|
|
c2++;
|
|
}
|
|
#ifdef USE_SHA384
|
|
} else if (ssl->deMacSize == SHA384_HASH_SIZE) {
|
|
while (l1 > 128) {
|
|
c1++; l1 -= 128;
|
|
}
|
|
if (l1 > 112) {
|
|
c1++;
|
|
}
|
|
while (l2 > 128) {
|
|
c2++; l2 -= 128;
|
|
}
|
|
if (l2 > 112) {
|
|
c2++;
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
return c1 - c2;
|
|
}
|
|
#endif /* LUCKY13 */
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
The workhorse for parsing handshake messages. Also enforces the state
|
|
machine for proper ordering of handshake messages.
|
|
Parameters:
|
|
ssl - ssl context
|
|
inbuf - buffer to read handshake message from
|
|
len - data length for the current ssl record. The ssl record
|
|
can contain multiple handshake messages, so we may need to parse
|
|
them all here.
|
|
Return:
|
|
MATRIXSSL_SUCCESS
|
|
SSL_PROCESS_DATA
|
|
MATRIXSSL_ERROR - see ssl->err for details
|
|
MEM_FAIL
|
|
-MATRIXSSL_ERROR and MEM_FAIL will be caught and an alert sent. If you
|
|
want to specifiy the alert the set ss->err. Otherwise it will
|
|
be an INTERNAL_ERROR
|
|
*/
|
|
static int32 parseSSLHandshake(ssl_t *ssl, char *inbuf, uint32 len)
|
|
{
|
|
unsigned char *c, *end;
|
|
unsigned char hsType;
|
|
int32 rc;
|
|
uint32 hsLen;
|
|
unsigned char hsMsgHash[SHA512_HASH_SIZE];
|
|
|
|
#ifdef USE_DTLS
|
|
uint32 fragLen;
|
|
int32 msn, fragOffset, j;
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
int32 hvreqMinVer, hvreqMajVer;
|
|
#endif
|
|
#endif /* USE_DTLS */
|
|
|
|
|
|
rc = MATRIXSSL_SUCCESS;
|
|
c = (unsigned char*)inbuf;
|
|
end = (unsigned char*)(inbuf + len);
|
|
|
|
/* Immediately check if we are working with a fragmented message. */
|
|
#ifdef USE_DTLS
|
|
msn = 0;
|
|
/* This is the non-DTLS fragmentation handler */
|
|
if (!(ssl->flags & SSL_FLAGS_DTLS)) {
|
|
#endif
|
|
if (ssl->fragMessage != NULL) {
|
|
/* Just borrowing hsLen variable. Is the rest here or do we still
|
|
need more? */
|
|
hsLen = min((uint32)(end - c), ssl->fragTotal - ssl->fragIndex);
|
|
memcpy(ssl->fragMessage + ssl->fragIndex, c, hsLen);
|
|
ssl->fragIndex += hsLen;
|
|
c += hsLen;
|
|
|
|
if (ssl->fragIndex == ssl->fragTotal) {
|
|
c = ssl->fragMessage + ssl->hshakeHeadLen;
|
|
end = ssl->fragMessage + ssl->fragTotal;
|
|
hsLen = ssl->fragTotal - ssl->hshakeHeadLen;
|
|
goto SKIP_HSHEADER_PARSE;
|
|
} else {
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
}
|
|
#ifdef USE_DTLS
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
if (ssl->rec.partial && (ssl->rec.hsBytesParsed > ssl->recordHeadLen)) {
|
|
goto SKIP_HSHEADER_PARSE;
|
|
}
|
|
#endif /* USE_CERT_CHAIN_PARSING */
|
|
|
|
parseHandshake:
|
|
if (end - c < 1) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid length of handshake message 1\n");
|
|
psTraceIntInfo("%d\n", (int32)(end - c));
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
hsType = *c; c++;
|
|
|
|
#ifndef SSL_REHANDSHAKES_ENABLED
|
|
/*
|
|
If all rehandshaking is disabled, just catch that here and alert.
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_SERVER) {
|
|
if (hsType == SSL_HS_CLIENT_HELLO && ssl->hsState == SSL_HS_DONE) {
|
|
psTraceInfo("Closing conn with client. Rehandshake is disabled\n");
|
|
ssl->err = SSL_ALERT_NO_RENEGOTIATION;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
} else {
|
|
if (hsType == SSL_HS_HELLO_REQUEST && ssl->hsState == SSL_HS_DONE) {
|
|
psTraceInfo("Closing conn with server. Rehandshake is disabled\n");
|
|
ssl->err = SSL_ALERT_NO_RENEGOTIATION;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
}
|
|
#endif /* SSL_REHANDSHAKES_ENABLED */
|
|
|
|
#ifdef USE_DTLS
|
|
/*
|
|
The MSN helpes keep the state machine sane prior to passing through to
|
|
the hsType exceptions because if they are received out-of-order it could
|
|
choose the wrong handshake type (client auth, rehandshake, or standard)
|
|
|
|
It is mostly important to deal with future messages here because those
|
|
are the ones that may bypass us to the wrong handshake type. Duplicates
|
|
are handled below.
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
if (end - c < 5) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid length of handshake message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
msn = c[3] << 8;
|
|
msn += c[4];
|
|
if (msn > (ssl->lastMsn + 1)) {
|
|
psTraceIntDtls("Ignoring future handshake msg %d\n", hsType);
|
|
return MATRIXSSL_SUCCESS;
|
|
} else if (msn != 0 && ssl->lastMsn >= msn) {
|
|
psTraceIntDtls("Ignoring already seen handshake msg %d\n", hsType);
|
|
return DTLS_RETRANSMIT;
|
|
}
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
/*
|
|
hsType is the received handshake type and ssl->hsState is the expected
|
|
handshake type. If it doesn't match, there are some possible cases
|
|
that are not errors. These are checked here.
|
|
*/
|
|
if (hsType != ssl->hsState &&
|
|
(hsType != SSL_HS_CLIENT_HELLO || ssl->hsState != SSL_HS_DONE)) {
|
|
|
|
/*
|
|
A mismatch is possible in the client authentication case.
|
|
The optional CERTIFICATE_REQUEST may be appearing instead of
|
|
SERVER_HELLO_DONE.
|
|
*/
|
|
if ((hsType == SSL_HS_CERTIFICATE_REQUEST) &&
|
|
(ssl->hsState == SSL_HS_SERVER_HELLO_DONE)) {
|
|
/*
|
|
This is where the client is first aware of requested client
|
|
authentication so we set the flag here.
|
|
|
|
*/
|
|
ssl->flags |= SSL_FLAGS_CLIENT_AUTH;
|
|
ssl->hsState = SSL_HS_CERTIFICATE_REQUEST;
|
|
goto hsStateDetermined;
|
|
}
|
|
/*
|
|
Another possible mismatch allowed is for a HELLO_REQEST message.
|
|
Indicates a rehandshake initiated from the server.
|
|
*/
|
|
if ((hsType == SSL_HS_HELLO_REQUEST) &&
|
|
(ssl->hsState == SSL_HS_DONE) &&
|
|
!(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
sslResetContext(ssl);
|
|
ssl->hsState = hsType;
|
|
goto hsStateDetermined;
|
|
}
|
|
|
|
/* Another possible mismatch is HELLO_REQUEST right after we sent
|
|
a re-handshake CLIENT_HELLO. Will ignore the request and
|
|
assume this was a timing issue and that the server will reply
|
|
to our CLIENT_HELLO when it is received */
|
|
if ((hsType == SSL_HS_HELLO_REQUEST) &&
|
|
(ssl->hsState == SSL_HS_SERVER_HELLO) &&
|
|
(ssl->flags & SSL_FLAGS_READ_SECURE) &&
|
|
(ssl->flags & SSL_FLAGS_WRITE_SECURE) &&
|
|
!(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
/* There is no body to the message. Confirm this and exit happily
|
|
without changing state */
|
|
if (end - c < 3) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid length of handshake message 2\n");
|
|
psTraceIntInfo("%d\n", (int32)(end - c));
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
hsLen = *c << 16; c++;
|
|
hsLen += *c << 8; c++;
|
|
hsLen += *c; c++;
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
if (end - c < 8) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid length of handshake message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
c += 8;
|
|
}
|
|
#endif
|
|
#ifdef SSL_REHANDSHAKES_ENABLED
|
|
if (ssl->rehandshakeCount <= 0) {
|
|
ssl->err = SSL_ALERT_NO_RENEGOTIATION;
|
|
psTraceInfo("Server re-handshaking denied. Out of credits.\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
ssl->rehandshakeCount--;
|
|
#endif
|
|
if (hsLen == 0) {
|
|
return MATRIXSSL_SUCCESS;
|
|
} else {
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
/* Another possible mismatch allowed is for a
|
|
SSL_HS_NEW_SESSION_TICKET message. */
|
|
if ((hsType == SSL_HS_NEW_SESSION_TICKET) &&
|
|
(ssl->hsState == SSL_HS_FINISHED) && ssl->sid &&
|
|
(ssl->sid->sessionTicketState == SESS_TICKET_STATE_RECVD_EXT) &&
|
|
!(ssl->flags & SSL_FLAGS_SERVER)) {
|
|
ssl->hsState = hsType;
|
|
goto hsStateDetermined;
|
|
}
|
|
|
|
#endif /* USE_STATELESS_SESSION_TICKETS */
|
|
|
|
#ifdef USE_OCSP
|
|
/* Another possible mismatch is server didn't send the optional
|
|
CERTIFICATE_STATUS message. Unfortunate this was not specified
|
|
to be strictly handled in the status_request extensions */
|
|
if (ssl->hsState == SSL_HS_CERTIFICATE_STATUS) {
|
|
/* The two valid states from here are identical
|
|
checking of the next state calculation at the end of the
|
|
SSL_HS_CERTIFICATE message handling.
|
|
(But in reverse order due to the precedence of DHE mode.)
|
|
*/
|
|
#ifdef USE_OCSP_MUST_STAPLE
|
|
/* This is the case where the server sent a reply to our
|
|
status_request extension but didn't actually send the
|
|
handshake message. If we are in a MUST state, time to fail */
|
|
psTraceInfo("Expecting CERTIFICATE_STATUS message\n");
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
return MATRIXSSL_ERROR;
|
|
#endif
|
|
|
|
#ifdef USE_DHE_CIPHER_SUITE
|
|
if (ssl->flags & SSL_FLAGS_DHE_KEY_EXCH &&
|
|
hsType == SSL_HS_SERVER_KEY_EXCHANGE) {
|
|
ssl->hsState = hsType;
|
|
goto hsStateDetermined;
|
|
}
|
|
#endif /* USE_DHE_CIPHER_SUITE */
|
|
if (hsType == SSL_HS_SERVER_HELLO_DONE) {
|
|
ssl->hsState = hsType;
|
|
goto hsStateDetermined;
|
|
}
|
|
}
|
|
#endif /* USE_OCSP */
|
|
|
|
#ifdef USE_PSK_CIPHER_SUITE
|
|
/*
|
|
PSK suites are probably not including SERVER_KEY_EXCHANGE message
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_PSK_CIPHER) {
|
|
if ((hsType == SSL_HS_SERVER_HELLO_DONE) &&
|
|
(ssl->hsState == SSL_HS_SERVER_KEY_EXCHANGE)) {
|
|
#ifdef USE_DHE_CIPHER_SUITE
|
|
/*
|
|
DH kex suites must be sending a SERVER_KEY_EXCHANGE message
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_DHE_KEY_EXCH) {
|
|
psTraceIntInfo("Expecting SKE message: %d\n", hsType);
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
#endif /* USE_DHE_CIPHER_SUITE */
|
|
ssl->hsState = hsType;
|
|
goto hsStateDetermined;
|
|
}
|
|
}
|
|
#endif /* USE_PSK_CIPHER_SUITE */
|
|
|
|
#ifdef USE_DTLS
|
|
/*
|
|
DTLS inserts an optional VERIFY_REQUEST back to clients
|
|
*/
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
if ((hsType == SSL_HS_HELLO_VERIFY_REQUEST) &&
|
|
(ssl->hsState == SSL_HS_SERVER_HELLO)) {
|
|
/* However, if this is a retransmit and we've already parsed
|
|
the HELLO_VERIFY_REQUEST we can safely skip it */
|
|
if (ssl->haveCookie == 0) {
|
|
ssl->hsState = hsType;
|
|
goto hsStateDetermined;
|
|
}
|
|
}
|
|
#endif
|
|
/*
|
|
A final MSN sanity test and handling of duplicate hello messages
|
|
*/
|
|
if ((ssl->lastMsn + 1) == msn) {
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceIntDtls("Correct MSN %d on unexpected HS msg ", msn);
|
|
psTraceIntDtls(" %d\n", hsType);
|
|
return MATRIXSSL_ERROR;
|
|
} else if (ssl->lastMsn >= msn) {
|
|
psTraceDtls("IGNORING ALREADY SEEN HELLO HANDSHAKE MSG\n");
|
|
return DTLS_RETRANSMIT;
|
|
}
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
psTraceIntInfo("Out-of-order handshake message: %d\n", hsType);
|
|
psTraceIntInfo("Wanted: %d\n", ssl->hsState);
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
|
|
hsStateDetermined:
|
|
if (hsType == SSL_HS_CLIENT_HELLO) {
|
|
sslInitHSHash(ssl);
|
|
if (ssl->hsState == SSL_HS_DONE) {
|
|
#ifdef SSL_REHANDSHAKES_ENABLED
|
|
/* This is a 'leaky bucket' mechanism where each X bytes of data transfer gains
|
|
you a re-handshake credit. Prevents the DOS attack of repeat
|
|
re-handshake requests */
|
|
if (ssl->rehandshakeCount <= 0) {
|
|
ssl->err = SSL_ALERT_NO_RENEGOTIATION;
|
|
psTraceInfo("Client re-handshaking denied\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
ssl->rehandshakeBytes = 0; /* reset */
|
|
ssl->rehandshakeCount--;
|
|
#endif /* SSL_REHANDSHAKES_ENABLED */
|
|
/* Rehandshake. Server receiving client hello on existing connection */
|
|
sslResetContext(ssl);
|
|
ssl->hsState = hsType;
|
|
}
|
|
}
|
|
|
|
/*
|
|
We need to get a copy of the message hashes to compare to those sent
|
|
in the finished message (which does not include a hash of itself)
|
|
before we update the handshake hashes
|
|
*/
|
|
if (ssl->hsState == SSL_HS_FINISHED) {
|
|
if (sslSnapshotHSHash(ssl, hsMsgHash,
|
|
(ssl->flags & SSL_FLAGS_SERVER) ? 0 : SSL_FLAGS_SERVER) <= 0) {
|
|
psTraceInfo("Error snapshotting HS hash\n");
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
}
|
|
#ifdef USE_CLIENT_AUTH
|
|
if (ssl->hsState == SSL_HS_CERTIFICATE_VERIFY) {
|
|
/* Same issue as above for client auth. Need a handshake snapshot
|
|
that doesn't include this message we are about to process */
|
|
if (sslSnapshotHSHash(ssl, hsMsgHash, -1) <= 0) {
|
|
psTraceInfo("Error snapshotting HS hash\n");
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
}
|
|
#endif /* USE_CLIENT_AUTH */
|
|
|
|
/*
|
|
Process the handshake header and update the ongoing handshake hash
|
|
SSLv3:
|
|
1 byte type
|
|
3 bytes length
|
|
SSLv2:
|
|
1 byte type
|
|
*/
|
|
if (ssl->rec.majVer >= SSL3_MAJ_VER) {
|
|
if (end - c < 3) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid length of handshake message 2\n");
|
|
psTraceIntInfo("%d\n", (int32)(end - c));
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
hsLen = *c << 16; c++;
|
|
hsLen += *c << 8; c++;
|
|
hsLen += *c; c++;
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
if (end - c < 8) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid length of handshake message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
msn = *c << 8; c++;
|
|
msn += *c; c++;
|
|
fragOffset = *c << 16; c++;
|
|
fragOffset += *c << 8; c++;
|
|
fragOffset += *c; c++;
|
|
fragLen = *c << 16; c++;
|
|
fragLen += *c << 8; c++;
|
|
fragLen += *c; c++;
|
|
if (fragLen != hsLen) {
|
|
/*
|
|
Have a fragmented message here. Allocate if first time
|
|
seen and assign msn. Can only deal with single fragmented
|
|
message at a time.
|
|
*/
|
|
if (ssl->fragTotal == 0) {
|
|
/*
|
|
When all the fragments are received, this allocated pointer
|
|
becomes the 'c' parsing pointer. With all the potential
|
|
exit points in the parse code from all the different
|
|
messages it is not easy to free this on the fly. So, what
|
|
happens here is that each first message fragment that is
|
|
encountered will free the previous message if it exists
|
|
(not NULL).
|
|
|
|
The final test for freeing this pointer will be in the
|
|
encoding (client) and decoding (server) of the Finished
|
|
message. At this point we know we can't possibly be
|
|
recieving any more fragments since the CCS and Finished
|
|
messages will never be so large that they would require
|
|
fragmenting. Also, the handshake pool is freed during
|
|
the encoding of Finished.
|
|
*/
|
|
if (ssl->fragMessage != NULL ) {
|
|
psFree(ssl->fragMessage, ssl->hsPool);
|
|
ssl->fragMessage = NULL;
|
|
}
|
|
ssl->fragMessage = psMalloc(ssl->hsPool, hsLen);
|
|
if (ssl->fragMessage == NULL) {
|
|
return SSL_MEM_ERROR;
|
|
}
|
|
ssl->fragMsn = msn;
|
|
}
|
|
|
|
if (ssl->fragMsn != msn) {
|
|
/*
|
|
Got a fragment from a different msg. Ignore
|
|
*/
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
/*
|
|
Still could be a duplicate fragment. Make sure we haven't
|
|
seen it before. If we haven't this routine also returns
|
|
the next open fragment header index for use below.
|
|
*/
|
|
if ((rc = dtlsSeenFrag(ssl, fragOffset, &j)) == 1) {
|
|
return MATRIXSSL_SUCCESS;
|
|
} else if (rc == -1) { /* MAX_FRAGMENTS exceeded */
|
|
dtlsInitFrag(ssl); /* init will free memory */
|
|
if (ssl->fragMessage != NULL) {
|
|
psFree(ssl->fragMessage, ssl->hsPool);
|
|
ssl->fragMessage = NULL;
|
|
}
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceIntDtls("Max fragment limit exceeded: %d\n",
|
|
MAX_FRAGMENTS);
|
|
return PS_LIMIT_FAIL;
|
|
}
|
|
/*
|
|
Need to save the hs header info aside as well so that we may
|
|
pass the fragments through the handshake hash mechanism in
|
|
the correct order. This list also keeps track of the fragment
|
|
offsets and lengths for the same reason.
|
|
*/
|
|
ssl->fragHeaders[j].hsHeader = psMalloc(ssl->hsPool,
|
|
ssl->hshakeHeadLen);
|
|
if (ssl->fragHeaders[j].hsHeader == NULL) {
|
|
dtlsInitFrag(ssl); /* init to free */
|
|
return SSL_MEM_ERROR;
|
|
}
|
|
memcpy(ssl->fragHeaders[j].hsHeader, c - ssl->hshakeHeadLen,
|
|
ssl->hshakeHeadLen);
|
|
ssl->fragHeaders[j].offset = fragOffset;
|
|
ssl->fragHeaders[j].fragLen = fragLen;
|
|
|
|
ssl->fragTotal += fragLen;
|
|
memcpy(ssl->fragMessage + fragOffset, c, fragLen);
|
|
if (ssl->fragTotal != hsLen) {
|
|
|
|
/* Don't have all the fragments yet */
|
|
return MATRIXSSL_SUCCESS;
|
|
}
|
|
c = ssl->fragMessage;
|
|
end = ssl->fragMessage + hsLen;
|
|
}
|
|
}
|
|
#endif /* USE_DTLS */
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
if (((uint32)(end - c) < hsLen) && !ssl->rec.partial) {
|
|
#else
|
|
if ((uint32)(end - c) < hsLen) {
|
|
#endif
|
|
/* Support for fragmented handshake messages - non-DTLS */
|
|
if (ssl->fragMessage == NULL) {
|
|
/* Initial indication there is a fragmented message */
|
|
ssl->fragTotal = hsLen + ssl->hshakeHeadLen;
|
|
ssl->fragMessage = psMalloc(ssl->hsPool, ssl->fragTotal);
|
|
if (ssl->fragMessage == NULL) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
psTraceInfo("Memory allocation error\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
ssl->fragIndex = (uint32)(end - c) + ssl->hshakeHeadLen;
|
|
memcpy(ssl->fragMessage, c - ssl->hshakeHeadLen,
|
|
ssl->fragIndex);
|
|
return MATRIXSSL_SUCCESS;
|
|
} else {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid handshake length\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
}
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
if (ssl->fragTotal > 0) {
|
|
/* Run the UpdateHash over the fragmented message */
|
|
dtlsHsHashFragMsg(ssl);
|
|
dtlsInitFrag(ssl);
|
|
} else {
|
|
/*
|
|
The DTLS case in which the message was not fragmented.
|
|
Not at all unusual to hit this
|
|
*/
|
|
sslUpdateHSHash(ssl, c - ssl->hshakeHeadLen,
|
|
hsLen + ssl->hshakeHeadLen);
|
|
}
|
|
|
|
} else {
|
|
#endif /* USE_DTLS */
|
|
SKIP_HSHEADER_PARSE:
|
|
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
if (ssl->rec.partial) {
|
|
/*
|
|
Length of partial certificate records are being managed
|
|
manually with ssl->rec.len. The first pass will need to
|
|
include the record header in the hash.
|
|
*/
|
|
if (ssl->rec.hsBytesHashed == 0) {
|
|
sslUpdateHSHash(ssl, c - ssl->hshakeHeadLen, ssl->rec.len);
|
|
} else {
|
|
sslUpdateHSHash(ssl, c, ssl->rec.len);
|
|
}
|
|
ssl->rec.hsBytesHashed += ssl->rec.len;
|
|
} else {
|
|
sslUpdateHSHash(ssl, c - ssl->hshakeHeadLen,
|
|
hsLen + ssl->hshakeHeadLen);
|
|
}
|
|
#else
|
|
sslUpdateHSHash(ssl, c - ssl->hshakeHeadLen,
|
|
hsLen + ssl->hshakeHeadLen);
|
|
|
|
#endif
|
|
#ifdef USE_DTLS
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
} else if (ssl->rec.majVer == SSL2_MAJ_VER) {
|
|
/*
|
|
Assume that the handshake len is the same as the incoming ssl record
|
|
length minus 1 byte (type), this is verified in SSL_HS_CLIENT_HELLO
|
|
*/
|
|
hsLen = len - 1;
|
|
sslUpdateHSHash(ssl, (unsigned char*)inbuf, len);
|
|
} else {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceIntInfo("Invalid record version: %d\n", ssl->rec.majVer);
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/*
|
|
Finished with header. Process each type of handshake message.
|
|
*/
|
|
switch (ssl->hsState) {
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifdef USE_SERVER_SIDE_SSL
|
|
case SSL_HS_CLIENT_HELLO:
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseClientHello(ssl, &c, end);
|
|
/* SSL_PROCESS_DATA is a valid code to indicate the end of a flight */
|
|
if (rc < 0 && rc != SSL_PROCESS_DATA) {
|
|
return rc;
|
|
}
|
|
break;
|
|
|
|
/******************************************************************************/
|
|
|
|
case SSL_HS_CLIENT_KEY_EXCHANGE:
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseClientKeyExchange(ssl, hsLen, &c, end);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
break;
|
|
#endif /* USE_SERVER_SIDE_SSL */
|
|
|
|
/******************************************************************************/
|
|
|
|
case SSL_HS_FINISHED:
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseFinished(ssl, hsLen, hsMsgHash, &c, end);
|
|
/* SSL_PROCESS_DATA is a valid code to indicate the end of a flight */
|
|
if (rc < 0 && rc != SSL_PROCESS_DATA) {
|
|
return rc;
|
|
}
|
|
break;
|
|
|
|
/******************************************************************************/
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
case SSL_HS_HELLO_REQUEST:
|
|
/* No body message and the only one in record flight */
|
|
psTraceHs(">>> Client parsing HELLO_REQUEST message\n");
|
|
if (end - c != 0) {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("Invalid hello request message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
#ifdef SSL_REHANDSHAKES_ENABLED
|
|
if (ssl->rehandshakeCount <= 0) {
|
|
ssl->err = SSL_ALERT_NO_RENEGOTIATION;
|
|
psTraceInfo("Server re-handshaking denied\n");
|
|
/* Reset the state to done */
|
|
ssl->hsState = SSL_HS_DONE;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
ssl->rehandshakeCount--;
|
|
#endif
|
|
/* Intentionally not changing state here to SERVER_HELLO. The
|
|
encodeResponse case this will fall into needs to distinguish
|
|
between calling the normal sslEncodeResponse or encodeClientHello.
|
|
The HELLO_REQUEST state is used to make that determination and the
|
|
writing of CLIENT_HELLO will properly move the state along itself */
|
|
ssl->decState = SSL_HS_HELLO_REQUEST;
|
|
rc = SSL_PROCESS_DATA;
|
|
break;
|
|
|
|
/******************************************************************************/
|
|
|
|
case SSL_HS_SERVER_HELLO:
|
|
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseServerHello(ssl, hsLen, &c, end);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
break;
|
|
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
|
|
case SSL_HS_CERTIFICATE:
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseCertificate(ssl, &c, end);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
break;
|
|
|
|
#endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
|
|
#endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
/******************************************************************************/
|
|
#ifdef USE_OCSP
|
|
case SSL_HS_CERTIFICATE_STATUS:
|
|
psTraceHs(">>> Client parsing CERTIFICATE_STATUS message\n");
|
|
rc = parseCertificateStatus(ssl, hsLen, &c, end);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
break;
|
|
#endif /* USE_OCSP */
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
#ifdef USE_STATELESS_SESSION_TICKETS
|
|
case SSL_HS_NEW_SESSION_TICKET:
|
|
|
|
psTraceHs(">>> Client parsing NEW_SESSION_TICKET message\n");
|
|
if (hsLen < 6) {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("Invalid NewSessionTicket message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
ssl->sid->sessionTicketLifetimeHint = *c << 24; c++;
|
|
ssl->sid->sessionTicketLifetimeHint |= *c << 16; c++;
|
|
ssl->sid->sessionTicketLifetimeHint |= *c << 8; c++;
|
|
ssl->sid->sessionTicketLifetimeHint |= *c; c++;
|
|
/* Reusing hsLen here */
|
|
hsLen = *c << 8; c++;
|
|
hsLen |= *c; c++;
|
|
|
|
if ((uint32)(end - c) < hsLen) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid NewSessionTicket message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
if (ssl->sid->sessionTicketLen == 0) {
|
|
/* First time receiving a session ticket */
|
|
ssl->sid->sessionTicketLen = hsLen;
|
|
/* This client has a dedicated SessionId pool to draw from. */
|
|
if ((ssl->sid->sessionTicket = psMalloc(ssl->sid->pool,
|
|
ssl->sid->sessionTicketLen)) != NULL) {
|
|
memcpy(ssl->sid->sessionTicket, c, ssl->sid->sessionTicketLen);
|
|
c += ssl->sid->sessionTicketLen;
|
|
} else {
|
|
/* Don't fail on alloc error. Just won't have the ticket for
|
|
next time */
|
|
c += ssl->sid->sessionTicketLen;
|
|
ssl->sid->sessionTicketLen = 0;
|
|
}
|
|
} else {
|
|
/* Updated (or duplicate) ticket */
|
|
psAssert(ssl->sid->sessionTicket); /* exists from previous hs */
|
|
if (hsLen == ssl->sid->sessionTicketLen &&
|
|
(memcmp(c, ssl->sid->sessionTicket, hsLen) == 0)) {
|
|
/* server not updating the ticket */
|
|
c += ssl->sid->sessionTicketLen;
|
|
} else {
|
|
ssl->sid->sessionTicketLen = hsLen;
|
|
psFree(ssl->sid->sessionTicket, ssl->sid->pool);
|
|
if ((ssl->sid->sessionTicket = psMalloc(ssl->sid->pool,
|
|
ssl->sid->sessionTicketLen)) != NULL) {
|
|
memcpy(ssl->sid->sessionTicket, c,
|
|
ssl->sid->sessionTicketLen);
|
|
c += ssl->sid->sessionTicketLen;
|
|
} else {
|
|
/* Don't fail on alloc error. Just won't have the ticket
|
|
for next time */
|
|
c += ssl->sid->sessionTicketLen;
|
|
ssl->sid->sessionTicketLen = 0;
|
|
}
|
|
}
|
|
}
|
|
ssl->sid->sessionTicketState = SESS_TICKET_STATE_INIT;
|
|
ssl->hsState = SSL_HS_FINISHED;
|
|
ssl->decState = SSL_HS_NEW_SESSION_TICKET;
|
|
break;
|
|
#endif /* USE_STATELESS_SESSION_TICKETS */
|
|
|
|
/******************************************************************************/
|
|
|
|
case SSL_HS_SERVER_HELLO_DONE:
|
|
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseServerHelloDone(ssl, hsLen, &c, end);
|
|
if (rc < 0 && rc != SSL_PROCESS_DATA) {
|
|
return rc;
|
|
}
|
|
break;
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
case SSL_HS_CERTIFICATE_REQUEST:
|
|
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseCertificateRequest(ssl, hsLen, &c, end);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
break;
|
|
#endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifndef USE_ONLY_PSK_CIPHER_SUITE
|
|
#if defined(USE_CLIENT_AUTH) && defined(USE_SERVER_SIDE_SSL)
|
|
case SSL_HS_CERTIFICATE_VERIFY:
|
|
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseCertificateVerify(ssl, hsMsgHash, &c, end);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
break;
|
|
#endif /* USE_SERVER_SIDE_SSL && USE_CLIENT_AUTH */
|
|
#endif /* !USE_ONLY_PSK_CIPHER_SUITE */
|
|
|
|
/******************************************************************************/
|
|
|
|
case SSL_HS_SERVER_KEY_EXCHANGE:
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
psAssert(rc == 0); /* checking to see if this is the correct default */
|
|
rc = parseServerKeyExchange(ssl, hsMsgHash, &c, end);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
#else /* USE_CLIENT_SIDE_SSL */
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
return MATRIXSSL_ERROR;
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
break;
|
|
|
|
/******************************************************************************/
|
|
|
|
#ifdef USE_DTLS
|
|
#ifdef USE_CLIENT_SIDE_SSL
|
|
case SSL_HS_HELLO_VERIFY_REQUEST:
|
|
psTraceHs(">>> Client parsing HELLO_VERIFY_REQUEST message\n");
|
|
/*
|
|
Format for message is two byte version specifier, 1 byte length, and
|
|
the cookie itself
|
|
*/
|
|
if ((end - c) < 3) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid HelloVerifyRequest message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
hvreqMajVer = *c; c++;
|
|
hvreqMinVer = *c; c++;
|
|
(void)hvreqMajVer; /* Silence a 'set but not used' warning. */
|
|
(void)hvreqMinVer;
|
|
ssl->cookieLen = *c; c++;
|
|
if (ssl->cookieLen > 0) {
|
|
if ((end -c) < ssl->cookieLen) {
|
|
ssl->err = SSL_ALERT_DECODE_ERROR;
|
|
psTraceInfo("Invalid HelloVerifyRequest message\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
/*
|
|
The handshake pool does exists at this point. For DTLS handshakes
|
|
the client created the pool during the ClientHello write in order
|
|
to store the initial message in case the Server asks for cookie
|
|
(which is exactly what is happening right here).
|
|
*/
|
|
if (ssl->haveCookie) {
|
|
/* retransmit. should match what we already have */
|
|
if (memcmpct(ssl->cookie, c, ssl->cookieLen) != 0) {
|
|
ssl->err = SSL_ALERT_ILLEGAL_PARAMETER;
|
|
psTraceInfo("Cookie has changed on retransmit\n");
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
c += ssl->cookieLen;
|
|
} else {
|
|
ssl->cookie = psMalloc(ssl->hsPool, ssl->cookieLen);
|
|
if (ssl->cookie == NULL) {
|
|
return SSL_MEM_ERROR;
|
|
}
|
|
memcpy(ssl->cookie, c, ssl->cookieLen);
|
|
c += ssl->cookieLen;
|
|
}
|
|
}
|
|
ssl->haveCookie++;
|
|
ssl->hsState = SSL_HS_SERVER_HELLO;
|
|
ssl->decState = SSL_HS_HELLO_VERIFY_REQUEST;
|
|
rc = SSL_PROCESS_DATA;
|
|
break;
|
|
#endif /* USE_CLIENT_SIDE_SSL */
|
|
#endif /* USE_DTLS */
|
|
|
|
/******************************************************************************/
|
|
|
|
default:
|
|
ssl->err = SSL_ALERT_UNEXPECTED_MESSAGE;
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
|
|
#ifdef USE_DTLS
|
|
if (ssl->flags & SSL_FLAGS_DTLS) {
|
|
ssl->lastMsn = msn; /* MSN of last message sucessfully parsed */
|
|
}
|
|
#endif /* USE_DTLS */
|
|
|
|
/*
|
|
if we've got more data in the record, the sender has packed
|
|
multiple handshake messages in one record. Parse the next one.
|
|
*/
|
|
if (c < end) {
|
|
goto parseHandshake;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
#if defined(USE_CLIENT_SIDE_SSL) || defined(USE_CLIENT_AUTH)
|
|
#ifdef USE_CERT_CHAIN_PARSING
|
|
static int32 parseSingleCert(ssl_t *ssl, unsigned char *c, unsigned char *end,
|
|
int32 certLen)
|
|
{
|
|
int32 parseLen;
|
|
psX509Cert_t *cert, *p;
|
|
|
|
/*
|
|
Extract the binary cert message into the cert structure
|
|
*/
|
|
if ((parseLen = psX509ParseCert(ssl->hsPool, c, certLen, &cert, 0)) < 0) {
|
|
psX509FreeCert(cert);
|
|
if (parseLen == PS_MEM_FAIL) {
|
|
ssl->err = SSL_ALERT_INTERNAL_ERROR;
|
|
} else {
|
|
ssl->err = SSL_ALERT_BAD_CERTIFICATE;
|
|
}
|
|
return MATRIXSSL_ERROR;
|
|
}
|
|
if (ssl->sec.cert == NULL) {
|
|
ssl->sec.cert = cert;
|
|
} else {
|
|
p = ssl->sec.cert;
|
|
while (p->next != NULL) {
|
|
p = p->next;
|
|
}
|
|
p->next = cert;
|
|
}
|
|
return parseLen;
|
|
}
|
|
#endif /* USE_CERT_CHAIN_PARSING */
|
|
#endif /* USE_CLIENT_SIDE_SSL || USE_CLIENT_AUTH */
|
|
|
|
/******************************************************************************/
|
|
|