diff options
author | Andreas Baumann <mail@andreasbaumann.cc> | 2015-01-03 13:58:15 +0100 |
---|---|---|
committer | Andreas Baumann <mail@andreasbaumann.cc> | 2015-01-03 13:58:15 +0100 |
commit | 4aca87515a5083ae0e31ce3177189fd43b6d05ac (patch) | |
tree | 7b1d9a31393ca090757dc6f0d3859b4fcd93f271 /release/src/router/matrixssl/src/sslEncode.c | |
parent | 008d0be72b2f160382c6e880765e96b64a050c65 (diff) | |
download | tomato-4aca87515a5083ae0e31ce3177189fd43b6d05ac.tar.gz tomato-4aca87515a5083ae0e31ce3177189fd43b6d05ac.tar.bz2 |
patch to Vanilla Tomato 1.28
Diffstat (limited to 'release/src/router/matrixssl/src/sslEncode.c')
-rw-r--r-- | release/src/router/matrixssl/src/sslEncode.c | 1255 |
1 files changed, 1255 insertions, 0 deletions
diff --git a/release/src/router/matrixssl/src/sslEncode.c b/release/src/router/matrixssl/src/sslEncode.c new file mode 100644 index 00000000..605a3a49 --- /dev/null +++ b/release/src/router/matrixssl/src/sslEncode.c @@ -0,0 +1,1255 @@ +/* + * sslEncode.c + * Release $Name: MATRIXSSL_1_8_8_OPEN $ + * + * Secure Sockets Layer message encoding + */ +/* + * Copyright (c) PeerSec Networks, 2002-2009. 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 PeerSec Networks + * at http://www.peersec.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 "matrixInternal.h" + +/******************************************************************************/ + +static int32 writeCertificate(ssl_t *ssl, sslBuf_t *out, int32 notEmpty); +static int32 writeChangeCipherSpec(ssl_t *ssl, sslBuf_t *out); +static int32 writeFinished(ssl_t *ssl, sslBuf_t *out); +static int32 writeAlert(ssl_t *ssl, unsigned char level, + unsigned char description, sslBuf_t *out); +static int32 writeRecordHeader(ssl_t *ssl, int32 type, int32 hsType, int32 *messageSize, + char *padLen, unsigned char **encryptStart, + unsigned char **end, unsigned char **c); + +static int32 encryptRecord(ssl_t *ssl, int32 type, int32 messageSize, int32 padLen, + unsigned char *encryptStart, sslBuf_t *out, + unsigned char **c); + +#ifdef USE_CLIENT_SIDE_SSL +static int32 writeClientKeyExchange(ssl_t *ssl, sslBuf_t *out); +#endif /* USE_CLIENT_SIDE_SSL */ + +#ifdef USE_SERVER_SIDE_SSL +static int32 writeServerHello(ssl_t *ssl, sslBuf_t *out); +static int32 writeServerHelloDone(ssl_t *ssl, sslBuf_t *out); +#endif /* USE_SERVER_SIDE_SSL */ + + +static int32 secureWriteAdditions(ssl_t *ssl, int32 numRecs); + +/******************************************************************************/ +/* + Encode the incoming data into the out buffer for sending to remote peer. + + FUTURE SECURITY - If sending the first application data record, we could + prepend it with a blank SSL record to avoid a timing attack. We're fine + for now, because this is an impractical attack and the solution is + incompatible with some SSL implementations (including some versions of IE). + http://www.openssl.org/~bodo/tls-cbc.txt +*/ +int32 matrixSslEncode(ssl_t *ssl, unsigned char *in, int32 inlen, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, rc; +/* + If we've had a protocol error, don't allow further use of the session + Also, don't allow a application data record to be encoded unless the + handshake is complete. +*/ + if (ssl->flags & SSL_FLAGS_ERROR || ssl->hsState != SSL_HS_DONE || + ssl->flags & SSL_FLAGS_CLOSED) { + return SSL_ERROR; + } + c = out->end; + end = out->buf + out->size; + messageSize = ssl->recordHeadLen + inlen; + +/* + Validate size constraint +*/ + if (messageSize > SSL_MAX_BUF_SIZE) { + return SSL_ERROR; + } + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_APPLICATION_DATA, 0, + &messageSize, &padLen, &encryptStart, &end, &c)) < 0) { + return rc; + } + + memcpy(c, in, inlen); + c += inlen; + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_APPLICATION_DATA, + messageSize, padLen, encryptStart, out, &c)) < 0) { + return rc; + } + out->end = c; + + return (int32)(out->end - out->start); +} + +/******************************************************************************/ +/* + We indicate to the caller through return codes in sslDecode when we need + to write internal data to the remote host. The caller will call this + function to generate a message appropriate to our state. +*/ +int32 sslEncodeResponse(ssl_t *ssl, sslBuf_t *out) +{ + int32 messageSize; + int32 rc = SSL_ERROR; +#ifdef USE_SERVER_SIDE_SSL + int32 totalCertLen, i; + sslLocalCert_t *cert; +#endif /* USE_SERVER_SIDE_SSL */ +#ifdef USE_CLIENT_SIDE_SSL + int32 ckeSize; +#endif /* USE_CLIENT_SIDE_SSL */ + +/* + We may be trying to encode an alert response if there is an error marked + on the connection. +*/ + if (ssl->err != SSL_ALERT_NONE) { + rc = writeAlert(ssl, SSL_ALERT_LEVEL_FATAL, ssl->err, out); + if (rc == SSL_ERROR) { + ssl->flags |= SSL_FLAGS_ERROR; + } + return rc; + } + + +/* + We encode a set of response messages based on our current state + We have to pre-verify the size of the outgoing buffer against + all the messages to make the routine transactional. If the first + write succeeds and the second fails because of size, we cannot + rollback the state of the cipher and MAC. +*/ + switch (ssl->hsState) { +/* + If we're waiting for the ClientKeyExchange message, then we need to + send the messages that would prompt that result on the client +*/ +#ifdef USE_SERVER_SIDE_SSL + case SSL_HS_CLIENT_KEY_EXCHANGE: + +/* + This is the entry point for a server encoding the first flight + of a non-DH, non-client-auth handshake. +*/ + totalCertLen = 0; + cert = &ssl->keys->cert; + for (i = 0; cert != NULL; i++) { + totalCertLen += cert->certLen; + cert = cert->next; + } + messageSize = + 3 * ssl->recordHeadLen + + 3 * ssl->hshakeHeadLen + + 38 + SSL_MAX_SESSION_ID_SIZE + /* server hello */ + 3 + (i * 3) + totalCertLen; /* certificate */ + + + messageSize += secureWriteAdditions(ssl, 3); + + if ((out->buf + out->size) - out->end < messageSize) { + return SSL_FULL; + } +/* + Message size complete. Begin the flight write +*/ + rc = writeServerHello(ssl, out); + + if (rc == SSL_SUCCESS) { + rc = writeCertificate(ssl, out, 1); + } + + if (rc == SSL_SUCCESS) { + rc = writeServerHelloDone(ssl, out); + } + break; + +#endif /* USE_SERVER_SIDE_SSL */ + +/* + If we're not waiting for any message from client, then we need to + send our finished message +*/ + case SSL_HS_DONE: + messageSize = 2 * ssl->recordHeadLen + + ssl->hshakeHeadLen + + 1 + /* change cipher spec */ + SSL_MD5_HASH_SIZE + SSL_SHA1_HASH_SIZE; /* finished */ +/* + Account for possible overhead in CCS message with secureWriteAdditions + then always account for the encrypted FINISHED message +*/ + messageSize += secureWriteAdditions(ssl, 1); + messageSize += ssl->enMacSize + /* handshake msg hash */ + (ssl->cipher->blockSize - 1); /* max padding */ + + if ((out->buf + out->size) - out->end < messageSize) { + return SSL_FULL; + } + rc = writeChangeCipherSpec(ssl, out); + if (rc == SSL_SUCCESS) { + rc = writeFinished(ssl, out); + } + break; +/* + If we're expecting a Finished message, as a server we're doing + session resumption. As a client, we're completing a normal + handshake +*/ + case SSL_HS_FINISHED: +#ifdef USE_SERVER_SIDE_SSL + if (ssl->flags & SSL_FLAGS_SERVER) { + messageSize = + 3 * ssl->recordHeadLen + + 2 * ssl->hshakeHeadLen + + 38 + SSL_MAX_SESSION_ID_SIZE + /* server hello */ + 1 + /* change cipher spec */ + SSL_MD5_HASH_SIZE + SSL_SHA1_HASH_SIZE; /* finished */ +/* + Account for possible overhead with secureWriteAdditions + then always account for the encrypted FINISHED message +*/ + messageSize += secureWriteAdditions(ssl, 2); + messageSize += ssl->enMacSize + /* handshake msg hash */ + (ssl->cipher->blockSize - 1); /* max padding */ + if ((out->buf + out->size) - out->end < messageSize) { + return SSL_FULL; + } + rc = writeServerHello(ssl, out); + if (rc == SSL_SUCCESS) { + rc = writeChangeCipherSpec(ssl, out); + } + if (rc == SSL_SUCCESS) { + rc = writeFinished(ssl, out); + } + } +#endif /* USE_SERVER_SIDE_SSL */ +#ifdef USE_CLIENT_SIDE_SSL +/* + Encode entry point for client side final flight encodes. + First task here is to find out size of ClientKeyExchange message +*/ + if (!(ssl->flags & SSL_FLAGS_SERVER)) { + ckeSize = 0; +/* + Normal RSA auth cipher suite case +*/ + if (ssl->sec.cert == NULL) { + ssl->flags |= SSL_FLAGS_ERROR; + return SSL_ERROR; + } + ckeSize = ssl->sec.cert->publicKey.size; + + messageSize = 0; +/* + Client authentication requires the client to send a CERTIFICATE + and CERTIFICATE_VERIFY message. Account for the length. It + is possible the client didn't have a match for the requested cert. + Send an empty certificate message in that case (or alert for SSLv3) +*/ + if (ssl->flags & SSL_FLAGS_CLIENT_AUTH) { + if (ssl->sec.certMatch > 0) { +/* + Account for the certificate and certificateVerify messages +*/ + messageSize += 6 + (2 * ssl->recordHeadLen) + + (2 * ssl->hshakeHeadLen) + ssl->keys->cert.certLen + + 2 + ssl->keys->cert.privKey->size; + } else { +/* + SSLv3 sends a no_certificate warning alert for no match +*/ + if (ssl->majVer == SSL3_MAJ_VER + && ssl->minVer == SSL3_MIN_VER) { + messageSize += 2 + ssl->recordHeadLen; + } else { +/* + TLS just sends an empty certificate message +*/ + messageSize += 3 + ssl->recordHeadLen + + ssl->hshakeHeadLen; + } + } + } +/* + Account for the header and message size for all records. The + finished message will always be encrypted, so account for one + largest possible MAC size and block size. Minus one + for padding. The finished message is not accounted for in the + writeSecureAddition calls below since it is accounted for here. +*/ + messageSize += + 3 * ssl->recordHeadLen + + 2 * ssl->hshakeHeadLen + /* change cipher has no hsHead */ + ckeSize + /* client key exchange */ + 1 + /* change cipher spec */ + SSL_MD5_HASH_SIZE + SSL_SHA1_HASH_SIZE + /* SSLv3 finished */ + SSL_MAX_MAC_SIZE + SSL_MAX_BLOCK_SIZE - 1; + if (ssl->flags & SSL_FLAGS_CLIENT_AUTH) { +/* + Secure write for ClientKeyExchange, ChangeCipherSpec, + Certificate, and CertificateVerify. Don't account for + Certificate and/or CertificateVerify message if no auth cert. + This will also cover the NO_CERTIFICATE alert sent in + replacement of the NULL certificate message in SSLv3. +*/ + if (ssl->sec.certMatch > 0) { + messageSize += secureWriteAdditions(ssl, 4); + } else { + messageSize += secureWriteAdditions(ssl, 3); + } + } else { + messageSize += secureWriteAdditions(ssl, 2); + } + + +/* + The actual buffer size test to hold this flight +*/ + if ((out->buf + out->size) - out->end < messageSize) { + return SSL_FULL; + } + rc = SSL_SUCCESS; + + if (ssl->flags & SSL_FLAGS_CLIENT_AUTH) { + if (ssl->sec.certMatch == 0 && ssl->majVer == SSL3_MAJ_VER + && ssl->minVer == SSL3_MIN_VER) { + rc = writeAlert(ssl, SSL_ALERT_LEVEL_WARNING, + SSL_ALERT_NO_CERTIFICATE, out); + } else { + rc = writeCertificate(ssl, out, ssl->sec.certMatch); + } + } + + if (rc == SSL_SUCCESS) { + rc = writeClientKeyExchange(ssl, out); + } + if (rc == SSL_SUCCESS) { + rc = writeChangeCipherSpec(ssl, out); + } + if (rc == SSL_SUCCESS) { + rc = writeFinished(ssl, out); + } + } +#endif /* USE_CLIENT_SIDE_SSL */ + break; + } + if (rc == SSL_ERROR) { + ssl->flags |= SSL_FLAGS_ERROR; + } + return rc; +} + +/******************************************************************************/ +/* + Message size must account for any additional length a secure-write + would add to the message. It would be too late to check length in + the writeRecordHeader call since some of the handshake hashing could + have already taken place and we can't rewind those hashes. +*/ +static int32 secureWriteAdditions(ssl_t *ssl, int32 numRecs) +{ + int32 add = 0; +/* + There is a slim chance for a false FULL message due to the fact that + the maximum padding is being calculated rather than the actual number. + Caller must simply grow buffer and try again. +*/ + if (ssl->flags & SSL_FLAGS_WRITE_SECURE) { + add += (numRecs * ssl->enMacSize) + /* handshake msg hash */ + (numRecs * (ssl->enBlockSize - 1)); /* max padding */ + } + return add; +} + +/******************************************************************************/ +/* + Write out a closure alert message (the only user initiated alert) + The user would call this when about to initate a socket close +*/ +int32 matrixSslEncodeClosureAlert(ssl_t *ssl, sslBuf_t *out) +{ +/* + If we've had a protocol error, don't allow further use of the session +*/ + if (ssl->flags & SSL_FLAGS_ERROR) { + return SSL_ERROR; + } + return writeAlert(ssl, SSL_ALERT_LEVEL_WARNING, SSL_ALERT_CLOSE_NOTIFY, + out); +} + +/******************************************************************************/ +/* + Generic record header construction for alerts, handshake messages, and + change cipher spec. Determines message length for encryption and + writes out to buffer up to the real message data. +*/ +static int32 writeRecordHeader(ssl_t *ssl, int32 type, int32 hsType, + int32 *messageSize, char *padLen, unsigned char **encryptStart, + unsigned char **end, unsigned char **c) +{ + int32 messageData, msn; + + messageData = *messageSize - ssl->recordHeadLen; + if (type == SSL_RECORD_TYPE_HANDSHAKE) { + messageData -= ssl->hshakeHeadLen; + } + + +/* + If this session is already in a secure-write state, determine padding +*/ + *padLen = 0; + if (ssl->flags & SSL_FLAGS_WRITE_SECURE) { + *messageSize += ssl->enMacSize; + *padLen = sslPadLenPwr2(*messageSize - ssl->recordHeadLen, + ssl->enBlockSize); + *messageSize += *padLen; + } + + if (*end - *c < *messageSize) { +/* + Callers other than sslEncodeResponse do not necessarily check for + FULL before calling. We do it here for them. +*/ + return SSL_FULL; + } + + + *c += psWriteRecordInfo(ssl, type, *messageSize - ssl->recordHeadLen, *c); + +/* + All data written after this point is to be encrypted (if secure-write) +*/ + *encryptStart = *c; + msn = 0; + + +/* + Handshake records have another header layer to write here +*/ + if (type == SSL_RECORD_TYPE_HANDSHAKE) { + *c += psWriteHandshakeHeader(ssl, hsType, messageData, msn, 0, + messageData, *c); + } + return 0; +} + +/******************************************************************************/ +/* + Encrypt the message using the current cipher. This call is used in + conjunction with the writeRecordHeader function above to finish writing + an SSL record. Updates handshake hash if necessary, generates message + MAC, writes the padding, and does the encrytion. +*/ +static int32 encryptRecord(ssl_t *ssl, int32 type, int32 messageSize, + int32 padLen, unsigned char *encryptStart, + sslBuf_t *out, unsigned char **c) +{ + if (type == SSL_RECORD_TYPE_HANDSHAKE) { + sslUpdateHSHash(ssl, encryptStart, (int32)(*c - encryptStart)); + } + *c += ssl->generateMac(ssl, type, encryptStart, + (int32)(*c - encryptStart), *c); + + *c += sslWritePad(*c, padLen); + + if (ssl->encrypt(&ssl->sec.encryptCtx, encryptStart, encryptStart, + (int32)(*c - encryptStart)) < 0 || *c - out->end != messageSize) { + matrixStrDebugMsg("Error encrypting message for write\n", NULL); + return SSL_ERROR; + } + + return 0; +} + +#ifdef USE_SERVER_SIDE_SSL +/******************************************************************************/ +/* + Write out the ServerHello message +*/ +static int32 writeServerHello(ssl_t *ssl, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, rc; + time_t t; + + c = out->end; + end = out->buf + out->size; +/* + Calculate the size of the message up front, and verify we have room + We assume there will be a sessionId in the message, and make adjustments + below if there is no sessionId. +*/ + messageSize = + ssl->recordHeadLen + + ssl->hshakeHeadLen + + 38 + SSL_MAX_SESSION_ID_SIZE; + +/* + First 4 bytes of the serverRandom are the unix time to prevent replay + attacks, the rest are random +*/ + t = time(0); + ssl->sec.serverRandom[0] = (unsigned char)((t & 0xFF000000) >> 24); + ssl->sec.serverRandom[1] = (unsigned char)((t & 0xFF0000) >> 16); + ssl->sec.serverRandom[2] = (unsigned char)((t & 0xFF00) >> 8); + ssl->sec.serverRandom[3] = (unsigned char)(t & 0xFF); + if (sslGetEntropy(ssl->sec.serverRandom + 4, SSL_HS_RANDOM_SIZE - 4) < 0) { + matrixStrDebugMsg("Error gathering serverRandom entropy\n", NULL); + return SSL_ERROR; + } +/* + We register session here because at this point the serverRandom value is + populated. If we are able to register the session, the sessionID and + sessionIdLen fields will be non-NULL, otherwise the session couldn't + be registered. +*/ + if (!(ssl->flags & SSL_FLAGS_RESUMED)) { + matrixRegisterSession(ssl); + } + messageSize -= (SSL_MAX_SESSION_ID_SIZE - ssl->sessionIdLen); + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_HANDSHAKE, + SSL_HS_SERVER_HELLO, &messageSize, &padLen, &encryptStart, + &end, &c)) < 0) { + return rc; + } +/* + First two fields in the ServerHello message are the major and minor + SSL protocol versions we agree to talk with +*/ + *c = ssl->majVer; c++; + *c = ssl->minVer; c++; +/* + The next 32 bytes are the server's random value, to be combined with + the client random and premaster for key generation later +*/ + memcpy(c, ssl->sec.serverRandom, SSL_HS_RANDOM_SIZE); + c += SSL_HS_RANDOM_SIZE; +/* + The next data is a single byte containing the session ID length, + and up to 32 bytes containing the session id. + First register the session, which will give us a session id and length + if not all session slots in the table are used +*/ + *c = ssl->sessionIdLen; c++; + if (ssl->sessionIdLen > 0) { + memcpy(c, ssl->sessionId, ssl->sessionIdLen); + c += ssl->sessionIdLen; + } +/* + Two byte cipher suite we've chosen based on the list sent by the client + and what we support. + One byte compression method (always zero) +*/ + *c = (ssl->cipher->id & 0xFF00) >> 8; c++; + *c = ssl->cipher->id & 0xFF; c++; + *c = 0; c++; + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_HANDSHAKE, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } +/* + If we're resuming a session, we now have the clientRandom, master and + serverRandom, so we can derive keys which we'll be using shortly. +*/ + if (ssl->flags & SSL_FLAGS_RESUMED) { + sslDeriveKeys(ssl); + } +/* + Verify that we've calculated the messageSize correctly, really this + should never fail; it's more of an implementation verification +*/ + if (c - out->end != messageSize) { + return SSL_ERROR; + } + out->end = c; + return SSL_SUCCESS; +} + +/******************************************************************************/ +/* + ServerHelloDone message is a blank handshake message +*/ +static int32 writeServerHelloDone(ssl_t *ssl, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, rc; + + c = out->end; + end = out->buf + out->size; + messageSize = + ssl->recordHeadLen + + ssl->hshakeHeadLen; + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_HANDSHAKE, + SSL_HS_SERVER_HELLO_DONE, &messageSize, &padLen, + &encryptStart, &end, &c)) < 0) { + return rc; + } + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_HANDSHAKE, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } + + if (c - out->end != messageSize) { + matrixStrDebugMsg("Error generating hello done for write\n", NULL); + return SSL_ERROR; + } + out->end = c; + return SSL_SUCCESS; +} + + +/******************************************************************************/ +/* + Server initiated rehandshake public API call. +*/ +int32 matrixSslEncodeHelloRequest(ssl_t *ssl, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, rc; + + if (ssl->flags & SSL_FLAGS_ERROR || ssl->flags & SSL_FLAGS_CLOSED) { + return SSL_ERROR; + } + if (!(ssl->flags & SSL_FLAGS_SERVER) || (ssl->hsState != SSL_HS_DONE)) { + return SSL_ERROR; + } + + c = out->end; + end = out->buf + out->size; + messageSize = + ssl->recordHeadLen + + ssl->hshakeHeadLen; + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_HANDSHAKE, + SSL_HS_HELLO_REQUEST, &messageSize, &padLen, + &encryptStart, &end, &c)) < 0) { + return rc; + } + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_HANDSHAKE, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } + + if (c - out->end != messageSize) { + matrixStrDebugMsg("Error generating hello request for write\n", NULL); + return SSL_ERROR; + } + out->end = c; + + return SSL_SUCCESS; +} +#else /* USE_SERVER_SIDE_SSL */ +int32 matrixSslEncodeHelloRequest(ssl_t *ssl, sslBuf_t *out) +{ + matrixStrDebugMsg("Library not built with USE_SERVER_SIDE_SSL\n", NULL); + return -1; +} +#endif /* USE_SERVER_SIDE_SSL */ + +/******************************************************************************/ +/* + Write a Certificate message. + The encoding of the message is as follows: + 3 byte length of certificate data (network byte order) + If there is no certificate, + 3 bytes of 0 + If there is one certificate, + 3 byte length of certificate + 3 + 3 byte length of certificate + certificate data + For more than one certificate: + 3 byte length of all certificate data + 3 byte length of first certificate + first certificate data + 3 byte length of second certificate + second certificate data + Certificate data is the base64 section of an X.509 certificate file + in PEM format decoded to binary. No additional interpretation is required. +*/ +static int32 writeCertificate(ssl_t *ssl, sslBuf_t *out, int32 notEmpty) +{ + sslLocalCert_t *cert; + unsigned char *c, *end, *encryptStart; + char padLen; + int32 totalCertLen, certLen, lsize, messageSize, i, rc; + + + c = out->end; + end = out->buf + out->size; + +/* + Determine total length of certs +*/ + totalCertLen = i = 0; + if (notEmpty) { + cert = &ssl->keys->cert; + for (; cert != NULL; i++) { + totalCertLen += cert->certLen; + cert = cert->next; + } + } +/* + Account for the 3 bytes of certChain len for each cert and get messageSize +*/ + lsize = 3 + (i * 3); + messageSize = + ssl->recordHeadLen + + ssl->hshakeHeadLen + + lsize + totalCertLen; + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_HANDSHAKE, + SSL_HS_CERTIFICATE, &messageSize, &padLen, &encryptStart, + &end, &c)) < 0) { + return rc; + } + +/* + Write out the certs +*/ + *c = ((totalCertLen + (lsize - 3)) & 0xFF0000) >> 16; c++; + *c = ((totalCertLen + (lsize - 3)) & 0xFF00) >> 8; c++; + *c = ((totalCertLen + (lsize - 3)) & 0xFF); c++; + + if (notEmpty) { + cert = &ssl->keys->cert; + while (cert) { + certLen = cert->certLen; + if (certLen > 0) { + *c = (certLen & 0xFF0000) >> 16; c++; + *c = (certLen & 0xFF00) >> 8; c++; + *c = (certLen & 0xFF); c++; + memcpy(c, cert->certBin, certLen); + c += certLen; + } + cert = cert->next; + } + } + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_HANDSHAKE, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } + + if (c - out->end != messageSize) { + matrixStrDebugMsg("Error parsing certificate for write\n", NULL); + return SSL_ERROR; + } + out->end = c; + return SSL_SUCCESS; +} + +/******************************************************************************/ +/* + Write the ChangeCipherSpec message. It has its own message type + and contains just one byte of value one. It is not a handshake + message, so it isn't included in the handshake hash. +*/ +static int32 writeChangeCipherSpec(ssl_t *ssl, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, rc; + + c = out->end; + end = out->buf + out->size; + messageSize = ssl->recordHeadLen + 1; + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_CHANGE_CIPHER_SPEC, 0, + &messageSize, &padLen, &encryptStart, &end, &c)) < 0) { + return rc; + } + *c = 1; c++; + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_CHANGE_CIPHER_SPEC, + messageSize, padLen, encryptStart, out, &c)) < 0) { + return rc; + } + + if (c - out->end != messageSize) { + matrixStrDebugMsg("Error generating change cipher for write\n", NULL); + return SSL_ERROR; + } + out->end = c; +/* + After the peer parses the ChangeCipherSpec message, it will expect + the next message to be encrypted, so activate encryption on outgoing + data now +*/ + sslActivateWriteCipher(ssl); + + + return SSL_SUCCESS; +} + +/******************************************************************************/ +/* + Write the Finished message + The message contains the 36 bytes, the 16 byte MD5 and 20 byte SHA1 hash + of all the handshake messages so far (excluding this one!) +*/ +static int32 writeFinished(ssl_t *ssl, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, verifyLen, rc; + + c = out->end; + end = out->buf + out->size; + verifyLen = SSL_MD5_HASH_SIZE + SSL_SHA1_HASH_SIZE; + messageSize = ssl->recordHeadLen + ssl->hshakeHeadLen + verifyLen; + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_HANDSHAKE, SSL_HS_FINISHED, + &messageSize, &padLen, &encryptStart, &end, &c)) < 0) { + return rc; + } +/* + Output the hash of messages we've been collecting so far into the buffer +*/ + c += sslSnapshotHSHash(ssl, c, ssl->flags & SSL_FLAGS_SERVER); + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_HANDSHAKE, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } + + if (c - out->end != messageSize) { + matrixStrDebugMsg("Error generating finished for write\n", NULL); + return SSL_ERROR; + } + out->end = c; + + +#ifdef USE_CLIENT_SIDE_SSL +/* + Free handshake pool, of which the cert is the primary member. + There is also an attempt to free the handshake pool during + the reciept of the finished message. Both cases are attempted + to keep the lifespan of this pool as short as possible. This + is the default case for the client side. +*/ + if (ssl->sec.cert) { + matrixX509FreeCert(ssl->sec.cert); + ssl->sec.cert = NULL; + } +#endif /* USE_CLIENT_SIDE */ + + + return SSL_SUCCESS; +} + +/******************************************************************************/ +/* + Write an Alert message + The message contains two bytes: AlertLevel and AlertDescription +*/ +static int32 writeAlert(ssl_t *ssl, unsigned char level, + unsigned char description, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, rc; + + c = out->end; + end = out->buf + out->size; + messageSize = 2 + ssl->recordHeadLen; + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_ALERT, 0, &messageSize, + &padLen, &encryptStart, &end, &c)) < 0) { + return rc; + } + *c = level; c++; + *c = description; c++; + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_ALERT, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } + + out->end = c; + return SSL_SUCCESS; +} + +#ifdef USE_CLIENT_SIDE_SSL +/******************************************************************************/ +/* + Write out the ClientHello message to a buffer +*/ +int32 matrixSslEncodeClientHello(ssl_t *ssl, sslBuf_t *out, + unsigned short cipherSpec) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, rc, cipherLen, cookieLen; + time_t t; + + if (ssl->flags & SSL_FLAGS_ERROR || ssl->flags & SSL_FLAGS_CLOSED) { + return SSL_ERROR; + } + if (ssl->flags & SSL_FLAGS_SERVER || (ssl->hsState != SSL_HS_SERVER_HELLO && + ssl->hsState != SSL_HS_DONE && + ssl->hsState != SSL_HS_HELLO_REQUEST )) { + return SSL_ERROR; + } + + sslInitHSHash(ssl); + + cookieLen = 0; +/* + If session resumption is being done on a rehandshake, make sure we are + sending the same cipher spec as the currently negotiated one. If no + resumption, clear the RESUMED flag in case the caller forgot to clear + it with matrixSslSetSessionOption. +*/ + if (ssl->sessionIdLen > 0) { + cipherSpec = ssl->cipher->id; + } else { + ssl->flags &= ~SSL_FLAGS_RESUMED; + } + +/* + If a cipher is specified it is two bytes length and two bytes data. +*/ + if (cipherSpec == 0) { + cipherLen = sslGetCipherSpecListLen(); + } else { + if (sslGetCipherSpec(cipherSpec) == NULL) { + matrixIntDebugMsg("Cipher suite not supported: %d\n", cipherSpec); + return SSL_ERROR; + } + cipherLen = 4; + } + + c = out->end; + end = out->buf + out->size; +/* + Calculate the size of the message up front, and write header +*/ + messageSize = ssl->recordHeadLen + ssl->hshakeHeadLen + + 5 + SSL_HS_RANDOM_SIZE + ssl->sessionIdLen + cipherLen + cookieLen; + + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_HANDSHAKE, + SSL_HS_CLIENT_HELLO, &messageSize, &padLen, &encryptStart, + &end, &c)) < 0) { + return rc; + } + +/* + First 4 bytes of the serverRandom are the unix time to prevent replay + attacks, the rest are random +*/ + + t = time(0); + ssl->sec.clientRandom[0] = (unsigned char)((t & 0xFF000000) >> 24); + ssl->sec.clientRandom[1] = (unsigned char)((t & 0xFF0000) >> 16); + ssl->sec.clientRandom[2] = (unsigned char)((t & 0xFF00) >> 8); + ssl->sec.clientRandom[3] = (unsigned char)(t & 0xFF); + if (sslGetEntropy(ssl->sec.clientRandom + 4, SSL_HS_RANDOM_SIZE - 4) < 0) { + matrixStrDebugMsg("Error gathering clientRandom entropy\n", NULL); + return SSL_ERROR; + } +/* + First two fields in the ClientHello message are the maximum major + and minor SSL protocol versions we support +*/ + *c = ssl->majVer; c++; + *c = ssl->minVer; c++; +/* + The next 32 bytes are the server's random value, to be combined with + the client random and premaster for key generation later +*/ + memcpy(c, ssl->sec.clientRandom, SSL_HS_RANDOM_SIZE); + c += SSL_HS_RANDOM_SIZE; +/* + The next data is a single byte containing the session ID length, + and up to 32 bytes containing the session id. + If we are asking to resume a session, then the sessionId would have + been set at session creation time. +*/ + *c = ssl->sessionIdLen; c++; + if (ssl->sessionIdLen > 0) { + memcpy(c, ssl->sessionId, ssl->sessionIdLen); + c += ssl->sessionIdLen; + } +/* + Write out the length and ciphers we support + Client can request a single specific cipher in the cipherSpec param +*/ + if (cipherSpec == 0) { + if ((rc = sslGetCipherSpecList(c, (int32)(end - c))) < 0) { + return SSL_FULL; + } + c += rc; + } else { + if ((int32)(end - c) < 4) { + return SSL_FULL; + } + *c = 0; c++; + *c = 2; c++; + *c = (cipherSpec & 0xFF00) >> 8; c++; + *c = cipherSpec & 0xFF; c++; + } +/* + Followed by two bytes (len and compression method (always zero)) +*/ + *c = 1; c++; + *c = 0; c++; + + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_HANDSHAKE, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } +/* + Verify that we've calculated the messageSize correctly, really this + should never fail; it's more of an implementation verification +*/ + if (c - out->end != messageSize) { + return SSL_ERROR; + } + out->end = c; + +/* + Could be a rehandshake so clean up old context if necessary. + Always explicitly set state to beginning. +*/ + if (ssl->hsState == SSL_HS_DONE) { + sslResetContext(ssl); + } + +/* + Could be a rehandshake on a previous connection that used client auth. + Reset our local client auth state as the server is always the one + responsible for initiating it. +*/ + ssl->flags &= ~SSL_FLAGS_CLIENT_AUTH; + ssl->hsState = SSL_HS_SERVER_HELLO; + return SSL_SUCCESS; +} + +/******************************************************************************/ +/* + Write a ClientKeyExchange message. +*/ +static int32 writeClientKeyExchange(ssl_t *ssl, sslBuf_t *out) +{ + unsigned char *c, *end, *encryptStart; + char padLen; + int32 messageSize, keyLen, explicitLen, rc; + + c = out->end; + end = out->buf + out->size; + messageSize = keyLen = 0; + + + +/* + Determine messageSize for the record header +*/ + /* Standard RSA auth suites */ + keyLen = ssl->sec.cert->publicKey.size; + + messageSize += ssl->recordHeadLen + ssl->hshakeHeadLen + keyLen; + explicitLen = 0; + + if ((rc = writeRecordHeader(ssl, SSL_RECORD_TYPE_HANDSHAKE, + SSL_HS_CLIENT_KEY_EXCHANGE, &messageSize, &padLen, + &encryptStart, &end, &c)) < 0) { + return rc; + } + +/* + ClientKeyExchange message contains the encrypted premaster secret. + The base premaster is the original SSL protocol version we asked for + followed by 46 bytes of random data. + These 48 bytes are padded to the current RSA key length and encrypted + with the RSA key. +*/ + if (explicitLen == 1) { + +/* + Add the two bytes of key length +*/ + if (keyLen > 0) { + *c = (keyLen & 0xFF00) >> 8; c++; + *c = (keyLen & 0xFF); c++; + } + } + + +/* + Standard RSA suite +*/ + ssl->sec.premasterSize = SSL_HS_RSA_PREMASTER_SIZE; + ssl->sec.premaster = psMalloc(ssl->hsPool, + SSL_HS_RSA_PREMASTER_SIZE); + + ssl->sec.premaster[0] = ssl->reqMajVer; + ssl->sec.premaster[1] = ssl->reqMinVer; + if (sslGetEntropy(ssl->sec.premaster + 2, + SSL_HS_RSA_PREMASTER_SIZE - 2) < 0) { + matrixStrDebugMsg("Error gathering premaster entropy\n", NULL); + return SSL_ERROR; + } + + sslActivatePublicCipher(ssl); + + if (ssl->encryptPub(ssl->hsPool, &(ssl->sec.cert->publicKey), + ssl->sec.premaster, ssl->sec.premasterSize, c, + (int32)(end - c)) != keyLen) { + matrixStrDebugMsg("Error encrypting premaster\n", NULL); + return SSL_FULL; + } + + c += keyLen; + + if ((rc = encryptRecord(ssl, SSL_RECORD_TYPE_HANDSHAKE, messageSize, + padLen, encryptStart, out, &c)) < 0) { + return rc; + } + + if (c - out->end != messageSize) { + matrixStrDebugMsg("Invalid ClientKeyExchange length\n", NULL); + return SSL_ERROR; + } + +/* + Now that we've got the premaster secret, derive the various symmetric + keys using it and the client and server random values +*/ + sslDeriveKeys(ssl); + + out->end = c; + return SSL_SUCCESS; +} + + +#else /* USE_CLIENT_SIDE_SSL */ +/******************************************************************************/ +/* + Stub out this function rather than ifdef it out in the public header +*/ +int32 matrixSslEncodeClientHello(ssl_t *ssl, sslBuf_t *out, + unsigned short cipherSpec) +{ + matrixStrDebugMsg("Library not built with USE_CLIENT_SIDE_SSL\n", NULL); + return -1; +} +#endif /* USE_CLIENT_SIDE_SSL */ + + +/******************************************************************************/ +/* + Write out a SSLv3 record header. + Assumes 'c' points to a buffer of at least SSL3_HEADER_LEN bytes + 1 byte type (SSL_RECORD_TYPE_*) + 1 byte major version + 1 byte minor version + 2 bytes length (network byte order) + Returns the number of bytes written +*/ +int32 psWriteRecordInfo(ssl_t *ssl, unsigned char type, int32 len, unsigned char *c) +{ + *c = type; c++; + *c = ssl->majVer; c++; + *c = ssl->minVer; c++; + *c = (len & 0xFF00) >> 8; c++; + *c = (len & 0xFF); + + return ssl->recordHeadLen; +} + +/******************************************************************************/ +/* + Write out an ssl handshake message header. + Assumes 'c' points to a buffer of at least ssl->hshakeHeadLen bytes + 1 byte type (SSL_HS_*) + 3 bytes length (network byte order) + Returns the number of bytes written +*/ +int32 psWriteHandshakeHeader(ssl_t *ssl, unsigned char type, int32 len, + int32 seq, int32 fragOffset, int32 fragLen, + unsigned char *c) +{ + *c = type; c++; + *c = (len & 0xFF0000) >> 16; c++; + *c = (len & 0xFF00) >> 8; c++; + *c = (len & 0xFF); + + return ssl->hshakeHeadLen; +} + +/******************************************************************************/ +/* + Write pad bytes and pad length per the TLS spec. Most block cipher + padding fills each byte with the number of padding bytes, but SSL/TLS + pretends one of these bytes is a pad length, and the remaining bytes are + filled with that length. The end result is that the padding is identical + to standard padding except the values are one less. For SSLv3 we are not + required to have any specific pad values, but they don't hurt. + + PadLen Result + 0 + 1 00 + 2 01 01 + 3 02 02 02 + 4 03 03 03 03 + 5 04 04 04 04 04 + 6 05 05 05 05 05 05 + 7 06 06 06 06 06 06 06 + 8 07 07 07 07 07 07 07 07 + + We calculate the length of padding required for a record using + sslPadLenPwr2() +*/ +int32 sslWritePad(unsigned char *p, unsigned char padLen) +{ + unsigned char c = padLen; + + while (c-- > 0) { + *p++ = padLen - 1; + } + return padLen; +} + +/******************************************************************************/ + + + |