Webserver+3d print
Diff: cyclone_ssl/tls_record.c
- Revision:
- 0:8918a71cdbe9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cyclone_ssl/tls_record.c Sat Feb 04 18:15:49 2017 +0000 @@ -0,0 +1,1151 @@ +/** + * @file tls_record.c + * @brief TLS record protocol + * + * @section License + * + * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. + * + * This file is part of CycloneSSL Open. + * + * This program is free software; 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 program is distributed in the hope that it will be useful, + * but 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @author Oryx Embedded SARL (www.oryx-embedded.com) + * @version 1.7.6 + **/ + +//Switch to the appropriate trace level +#define TRACE_LEVEL TLS_TRACE_LEVEL + +//Dependencies +#include <string.h> +#include "tls.h" +#include "tls_common.h" +#include "tls_record.h" +#include "tls_misc.h" +#include "ssl_common.h" +#include "cipher_mode_cbc.h" +#include "cipher_mode_ccm.h" +#include "cipher_mode_gcm.h" +#include "chacha20_poly1305.h" +#include "debug.h" + +//Check SSL library configuration +#if (TLS_SUPPORT == ENABLED) + + +/** + * @brief Write protocol data + * @param[in] context Pointer to the TLS context + * @param[in] data Pointer to the data buffer + * @param[in] length Number of data bytes to be written + * @param[in] contentType Higher level protocol + * @return Error code + **/ + +error_t tlsWriteProtocolData(TlsContext *context, + const void *data, size_t length, TlsContentType contentType) +{ + error_t error; + size_t n; + uint8_t *p; + + //Initialize status code + error = NO_ERROR; + + //Fragmentation process + while(!error) + { + if(context->txBufferLen == 0) + { + //Check the length of the data + if(length > context->txRecordMaxLen) + { + //Report an error + error = ERROR_MESSAGE_TOO_LONG; + } + else if(length > 0) + { + //The hash value is updated for each handshake message, + //except for HelloRequest messages + if(contentType == TLS_TYPE_HANDSHAKE) + tlsUpdateHandshakeHash(context, data, length); + + //Make room for the encryption overhead + memmove(context->txBuffer + context->txBufferSize - length, data, length); + + //Save record type + context->txBufferType = contentType; + //Set the length of the buffer + context->txBufferLen = length; + //Point to the beginning of the buffer + context->txBufferPos = 0; + } + else + { + //We are done + break; + } + } + else if(context->txBufferPos < context->txBufferLen) + { + //Number of bytes left to send + n = context->txBufferLen - context->txBufferPos; + //Point to the current fragment + p = context->txBuffer + context->txBufferSize - n; + //The record length must not exceed 16384 bytes + n = MIN(n, TLS_MAX_RECORD_LENGTH); + + //Send TLS record + error = tlsWriteRecord(context, p, n, context->txBufferType); + + //Check status code + if(!error) + { + //Advance data pointer + context->txBufferPos += n; + } + } + else + { + //Prepare to send new protocol data + context->txBufferLen = 0; + context->txBufferPos = 0; + + //We are done + break; + } + } + + //Return status code + return error; +} + + +/** + * @brief Read protocol data + * @param[in] context Pointer to the TLS context + * @param[out] data Pointer to the received data + * @param[out] length Number of data bytes that were received + * @param[out] contentType Higher level protocol + * @return Error code + **/ + +error_t tlsReadProtocolData(TlsContext *context, + void **data, size_t *length, TlsContentType *contentType) +{ + error_t error; + size_t n; + TlsContentType type; + TlsHandshake *message; + + //Initialize status code + error = NO_ERROR; + + //Fragment reassembly process + do + { + //Empty receive buffer? + if(context->rxBufferLen == 0) + { + //Read a TLS record + error = tlsReadRecord(context, context->rxBuffer, + context->rxBufferSize, &n, &type); + + //Check status code + if(!error) + { + //Save record type + context->rxBufferType = type; + //Number of bytes available for reading + context->rxBufferLen = n; + //Rewind to the beginning of the buffer + context->rxBufferPos = 0; + } + } + //Imcomplete message received? + else if(error == ERROR_MORE_DATA_REQUIRED) + { + //Make room at the end of the buffer + if(context->rxBufferPos > 0) + { + //Move unread data to the beginning of the buffer + memmove(context->rxBuffer, context->rxBuffer + + context->rxBufferPos, context->rxBufferLen); + + //Rewind to the beginning of the buffer + context->rxBufferPos = 0; + } + + //Read a TLS record + error = tlsReadRecord(context, context->rxBuffer + context->rxBufferLen, + context->rxBufferSize - context->rxBufferLen, &n, &type); + + //Check status code + if(!error) + { + //Fragmented records with mixed types cannot be interleaved + if(type != context->rxBufferType) + error = ERROR_UNEXPECTED_MESSAGE; + } + + //Check status code + if(!error) + { + //Number of bytes available for reading + context->rxBufferLen += n; + } + } + + //Check status code + if(!error) + { + //Handshake message received? + if(context->rxBufferType == TLS_TYPE_HANDSHAKE) + { + //A message may be fragmented across several records + if(context->rxBufferLen < sizeof(TlsHandshake)) + { + //Read an additional record + error = ERROR_MORE_DATA_REQUIRED; + } + else + { + //Point to the handshake message + message = (TlsHandshake *) (context->rxBuffer + context->rxBufferPos); + //Retrieve the length of the handshake message + n = sizeof(TlsHandshake) + LOAD24BE(message->length); + + //A message may be fragmented across several records + if(context->rxBufferLen < n) + { + //Read an additional record + error = ERROR_MORE_DATA_REQUIRED; + } + else + { + //Pass the handshake message to the higher layer + error = NO_ERROR; + } + } + } + //ChangeCipherSpec message received? + else if(context->rxBufferType == TLS_TYPE_CHANGE_CIPHER_SPEC) + { + //A message may be fragmented across several records + if(context->rxBufferLen < sizeof(TlsChangeCipherSpec)) + { + //Read an additional record + error = ERROR_MORE_DATA_REQUIRED; + } + else + { + //Length of the ChangeCipherSpec message + n = sizeof(TlsChangeCipherSpec); + //Pass the ChangeCipherSpec message to the higher layer + error = NO_ERROR; + } + } + //Alert message received? + else if(context->rxBufferType == TLS_TYPE_ALERT) + { + //A message may be fragmented across several records + if(context->rxBufferLen < sizeof(TlsAlert)) + { + //Read an additional record + error = ERROR_MORE_DATA_REQUIRED; + } + else + { + //Length of the Alert message + n = sizeof(TlsAlert); + //Pass the Alert message to the higher layer + error = NO_ERROR; + } + } + //Application data received? + else if(context->rxBufferType == TLS_TYPE_APPLICATION_DATA) + { + //Length of the application data + n = context->rxBufferLen; + //Pass the application data to the higher layer + error = NO_ERROR; + } + //Unknown content type? + else + { + //Report an error + error = ERROR_UNEXPECTED_MESSAGE; + } + } + + //Read as many records as necessary to reassemble the data + } while(error == ERROR_MORE_DATA_REQUIRED); + + //Successful processing? + if(!error) + { + //Pointer to the received data + *data = context->rxBuffer + context->rxBufferPos; + //Length, in byte, of the data + *length = n; + //Protocol type + *contentType = context->rxBufferType; + } + + //Return status code + return error; +} + + +/** + * @brief Send a TLS record + * @param[in] context Pointer to the TLS context + * @param[in] data Pointer to the record data + * @param[in] length Length of the record data + * @param[in] contentType Record type + * @return Error code + **/ + +error_t tlsWriteRecord(TlsContext *context, const uint8_t *data, + size_t length, TlsContentType contentType) +{ + error_t error; + size_t n; + TlsRecord *record; + + //Point to the TLS record + record = (TlsRecord *) context->txBuffer; + + //Initialize status code + error = NO_ERROR; + + //Send process + while(!error) + { + //Send as much data as possible + if(context->txRecordLen == 0) + { + //Format TLS record + record->type = contentType; + record->version = htons(context->version); + record->length = htons(length); + + //Copy record data + memmove(record->data, data, length); + + //Debug message + TRACE_DEBUG("Sending TLS record (%" PRIuSIZE " bytes)...\r\n", length); + TRACE_DEBUG_ARRAY(" ", record, length + sizeof(TlsRecord)); + + //Protect record payload? + if(context->changeCipherSpecSent) + { + //Encrypt TLS record + error = tlsEncryptRecord(context, record); + } + + //Check status code + if(!error) + { + //Actual length of the record data + context->txRecordLen = sizeof(TlsRecord) + ntohs(record->length); + //Point to the beginning of the record + context->txRecordPos = 0; + } + } + else if(context->txRecordPos < context->txRecordLen) + { + //Total number of bytes that have been written + n = 0; + + //Send more data + error = context->sendCallback(context->handle, + context->txBuffer + context->txRecordPos, + context->txRecordLen - context->txRecordPos, &n, 0); + + //Check status code + if(error == NO_ERROR || error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT) + { + //Advance data pointer + context->txRecordPos += n; + } + else + { + //The write operation has failed + error = ERROR_WRITE_FAILED; + } + } + else + { + //Prepare to send the next TLS record + context->txRecordLen = 0; + context->txRecordPos = 0; + + //We are done + break; + } + } + + //Return status code + return error; +} + + +/** + * @brief Receive a TLS record + * @param[in] context Pointer to the TLS context + * @param[out] data Buffer where to store the record data + * @param[in] size Maximum acceptable size for the incoming record + * @param[out] length Length of the record data + * @param[out] contentType Record type + * @return Error code + **/ + +error_t tlsReadRecord(TlsContext *context, uint8_t *data, + size_t size, size_t *length, TlsContentType *contentType) +{ + error_t error; + size_t n; + TlsRecord *record; + + //Point to the buffer where to store the incoming TLS record + record = (TlsRecord *) data; + + //Initialize status code + error = NO_ERROR; + + //Receive process + while(!error) + { + //Read as much data as possible + if(context->rxRecordPos < sizeof(TlsRecord)) + { + //Make sure that the buffer is large enough to hold the record header + if(size >= sizeof(TlsRecord)) + { + //Total number of bytes that have been received + n = 0; + + //Read TLS record header + error = context->receiveCallback(context->handle, + data + context->rxRecordPos, + sizeof(TlsRecord) - context->rxRecordPos, &n, 0); + + //Check status code + if(error == NO_ERROR || error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT) + { + //Advance data pointer + context->rxRecordPos += n; + + //TLS record header successfully received? + if(context->rxRecordPos >= sizeof(TlsRecord)) + { + //Debug message + TRACE_DEBUG("Record header received:\r\n"); + TRACE_DEBUG_ARRAY(" ", record, sizeof(record)); + + //Retrieve the length of the TLS record + context->rxRecordLen = sizeof(TlsRecord) + ntohs(record->length); + } + } + else + { + //The read operation has failed + error = ERROR_READ_FAILED; + } + } + else + { + //Report an error + error = ERROR_RECORD_OVERFLOW; + } + } + else if(context->rxRecordPos < context->rxRecordLen) + { + //Make sure that the buffer is large enough to hold the entire record + if(size >= context->rxRecordLen) + { + //Total number of bytes that have been received + n = 0; + + //Read TLS record contents + error = context->receiveCallback(context->handle, + data + context->rxRecordPos, + context->rxRecordLen - context->rxRecordPos, &n, 0); + + //Check status code + if(error == NO_ERROR || error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT) + { + //Advance data pointer + context->rxRecordPos += n; + } + else + { + //The read operation has failed + error = ERROR_READ_FAILED; + } + } + else + { + //Report an error + error = ERROR_RECORD_OVERFLOW; + } + } + else + { + //Check current state + if(context->state > TLS_STATE_SERVER_HELLO) + { + //Once the server has sent the ServerHello message, enforce + //incoming record versions + if(ntohs(record->version) != context->version) + error = ERROR_VERSION_NOT_SUPPORTED; + } + + //Check status code + if(!error) + { + //Record payload is protected? + if(context->changeCipherSpecReceived) + { + //Decrypt TLS record + error = tlsDecryptRecord(context, record); + } + } + + //Check status code + if(!error) + { + //Actual length of the record data + *length = ntohs(record->length); + //Record type + *contentType = (TlsContentType) record->type; + + //Debug message + TRACE_DEBUG("TLS record received (%" PRIuSIZE " bytes)...\r\n", *length); + TRACE_DEBUG_ARRAY(" ", record, *length + sizeof(TlsRecord)); + + //Discard record header + memmove(data, record->data, *length); + + //Prepare to receive the next TLS record + context->rxRecordLen = 0; + context->rxRecordPos = 0; + + //We are done + break; + } + } + } + + //Return status code + return error; +} + + +/** + * @brief Encrypt an outgoing TLS record + * @param[in] context Pointer to the TLS context + * @param[in,out] record TLS record to be encrypted + * @return Error code + **/ + +error_t tlsEncryptRecord(TlsContext *context, TlsRecord *record) +{ + error_t error; + size_t length; + + //Convert the length field to host byte order + length = ntohs(record->length); + + //Message authentication is required? + if(context->hashAlgo != NULL) + { +#if (TLS_MAX_VERSION >= SSL_VERSION_3_0 && TLS_MIN_VERSION <= SSL_VERSION_3_0) + //Check whether SSL 3.0 is currently used + if(context->version == SSL_VERSION_3_0) + { + //SSL 3.0 uses an older obsolete version of the HMAC construction + error = sslComputeMac(context, context->writeMacKey, + context->writeSeqNum, record, record->data, length, record->data + length); + //Any error to report? + if(error) + return error; + } + else +#endif +#if (TLS_MAX_VERSION >= TLS_VERSION_1_0 && TLS_MIN_VERSION <= TLS_VERSION_1_2) + //Check whether TLS 1.0, TLS 1.1 or TLS 1.2 is currently used + if(context->version >= TLS_VERSION_1_0) + { + //TLS uses a HMAC construction + hmacInit(&context->hmacContext, context->hashAlgo, + context->writeMacKey, context->macKeyLen); + + //Compute MAC over the sequence number and the record contents + hmacUpdate(&context->hmacContext, context->writeSeqNum, sizeof(TlsSequenceNumber)); + hmacUpdate(&context->hmacContext, record, length + sizeof(TlsRecord)); + + //Append the resulting MAC to the message + hmacFinal(&context->hmacContext, record->data + length); + } + else +#endif + //The negotiated TLS version is not valid... + { + //Report an error + return ERROR_INVALID_VERSION; + } + + //Debug message + TRACE_DEBUG("Write sequence number:\r\n"); + TRACE_DEBUG_ARRAY(" ", context->writeSeqNum, sizeof(TlsSequenceNumber)); + TRACE_DEBUG("Computed MAC:\r\n"); + TRACE_DEBUG_ARRAY(" ", record->data + length, context->hashAlgo->digestSize); + + //Adjust the length of the message + length += context->hashAlgo->digestSize; + //Fix length field + record->length = htons(length); + + //Increment sequence number + tlsIncSequenceNumber(context->writeSeqNum); + } + + //Encryption is required? + if(context->cipherMode != CIPHER_MODE_NULL) + { + //Debug message + TRACE_DEBUG("Record to be encrypted (%" PRIuSIZE " bytes):\r\n", length); + TRACE_DEBUG_ARRAY(" ", record, length + sizeof(TlsRecord)); + +#if (TLS_STREAM_CIPHER_SUPPORT == ENABLED) + //Stream cipher? + if(context->cipherMode == CIPHER_MODE_STREAM) + { + //Encrypt record contents + context->cipherAlgo->encryptStream(context->writeCipherContext, + record->data, record->data, length); + } + else +#endif +#if (TLS_CBC_CIPHER_SUPPORT == ENABLED) + //CBC block cipher? + if(context->cipherMode == CIPHER_MODE_CBC) + { + size_t i; + size_t paddingLength; + +#if (TLS_MAX_VERSION >= TLS_VERSION_1_1 && TLS_MIN_VERSION <= TLS_VERSION_1_2) + //TLS 1.1 and 1.2 use an explicit IV + if(context->version >= TLS_VERSION_1_1) + { + //Make room for the IV at the beginning of the data + memmove(record->data + context->recordIvLen, record->data, length); + + //The initialization vector should be chosen at random + error = context->prngAlgo->read(context->prngContext, + record->data, context->recordIvLen); + //Any error to report? + if(error) + return error; + + //Adjust the length of the message + length += context->recordIvLen; + } +#endif + //Get the actual amount of bytes in the last block + paddingLength = (length + 1) % context->cipherAlgo->blockSize; + + //Padding is added to force the length of the plaintext to be + //an integral multiple of the cipher's block length + if(paddingLength > 0) + paddingLength = context->cipherAlgo->blockSize - paddingLength; + + //Write padding bytes + for(i = 0; i <= paddingLength; i++) + record->data[length + i] = (uint8_t) paddingLength; + + //Compute the length of the resulting message + length += paddingLength + 1; + //Fix length field + record->length = htons(length); + + //Debug message + TRACE_DEBUG("Record with padding (%" PRIuSIZE " bytes):\r\n", length); + TRACE_DEBUG_ARRAY(" ", record, length + sizeof(TlsRecord)); + + //CBC encryption + error = cbcEncrypt(context->cipherAlgo, context->writeCipherContext, + context->writeIv, record->data, record->data, length); + //Any error to report? + if(error) + return error; + } + else +#endif +#if (TLS_CCM_CIPHER_SUPPORT == ENABLED || TLS_CCM_8_CIPHER_SUPPORT == ENABLED || \ + TLS_GCM_CIPHER_SUPPORT == ENABLED) + //CCM or GCM AEAD cipher? + if(context->cipherMode == CIPHER_MODE_CCM || + context->cipherMode == CIPHER_MODE_GCM) + { + uint8_t *data; + uint8_t *tag; + size_t nonceLength; + uint8_t nonce[12]; + uint8_t a[13]; + + //Determine the total length of the nonce + nonceLength = context->fixedIvLen + context->recordIvLen; + //The salt is the implicit part of the nonce and is not sent in the packet + memcpy(nonce, context->writeIv, context->fixedIvLen); + + //The explicit part of the nonce is chosen by the sender + error = context->prngAlgo->read(context->prngContext, + nonce + context->fixedIvLen, context->recordIvLen); + //Any error to report? + if(error) + return error; + + //Make room for the explicit nonce at the beginning of the record + memmove(record->data + context->recordIvLen, record->data, length); + //The explicit part of the nonce is carried in each TLS record + memcpy(record->data, nonce + context->fixedIvLen, context->recordIvLen); + + //Additional data to be authenticated + memcpy(a, context->writeSeqNum, sizeof(TlsSequenceNumber)); + memcpy(a + sizeof(TlsSequenceNumber), record, sizeof(TlsRecord)); + + //Point to the plaintext + data = record->data + context->recordIvLen; + //Point to the buffer where to store the authentication tag + tag = data + length; + +#if (TLS_CCM_CIPHER_SUPPORT == ENABLED || TLS_CCM_8_CIPHER_SUPPORT == ENABLED) + //CCM AEAD cipher? + if(context->cipherMode == CIPHER_MODE_CCM) + { + //Authenticated encryption using CCM + error = ccmEncrypt(context->cipherAlgo, context->writeCipherContext, + nonce, nonceLength, a, 13, data, data, length, tag, context->authTagLen); + } + else +#endif +#if (TLS_GCM_CIPHER_SUPPORT == ENABLED) + //GCM AEAD cipher? + if(context->cipherMode == CIPHER_MODE_GCM) + { + //Authenticated encryption using GCM + error = gcmEncrypt(context->writeGcmContext, nonce, nonceLength, + a, 13, data, data, length, tag, context->authTagLen); + } + else +#endif + //Invalid cipher mode? + { + //The specified cipher mode is not supported + error = ERROR_UNSUPPORTED_CIPHER_MODE; + } + + //Failed to encrypt data? + if(error) + return error; + + //Compute the length of the resulting message + length += context->recordIvLen + context->authTagLen; + //Fix length field + record->length = htons(length); + + //Increment sequence number + tlsIncSequenceNumber(context->writeSeqNum); + } + else +#endif +#if (TLS_CHACHA20_POLY1305_SUPPORT == ENABLED) + //ChaCha20Poly1305 AEAD cipher? + if(context->cipherMode == CIPHER_MODE_CHACHA20_POLY1305) + { + size_t i; + uint8_t *tag; + uint8_t nonce[12]; + uint8_t a[13]; + + //The 64-bit record sequence number is serialized as an 8-byte, + //big-endian value and padded on the left with four 0x00 bytes + memcpy(nonce + 4, context->writeSeqNum, 8); + memset(nonce, 0, 4); + + //The padded sequence number is XORed with the write IV to form + //the 96-bit nonce + for(i = 0; i < context->fixedIvLen; i++) + nonce[i] ^= context->writeIv[i]; + + //Additional data to be authenticated + memcpy(a, context->writeSeqNum, sizeof(TlsSequenceNumber)); + memcpy(a + sizeof(TlsSequenceNumber), record, sizeof(TlsRecord)); + + //Point to the buffer where to store the authentication tag + tag = record->data + length; + + //Authenticated encryption using ChaCha20Poly1305 + error = chacha20Poly1305Encrypt(context->writeEncKey, context->encKeyLen, + nonce, 12, a, 13, record->data, record->data, length, tag, context->authTagLen); + //Failed to encrypt data? + if(error) + return error; + + //Compute the length of the resulting message + length += context->authTagLen; + //Fix length field + record->length = htons(length); + + //Increment sequence number + tlsIncSequenceNumber(context->writeSeqNum); + } + else +#endif + //Invalid cipher mode? + { + //The specified cipher mode is not supported + return ERROR_UNSUPPORTED_CIPHER_MODE; + } + + //Debug message + TRACE_DEBUG("Encrypted record (%" PRIuSIZE " bytes):\r\n", length); + TRACE_DEBUG_ARRAY(" ", record, length + sizeof(TlsRecord)); + } + + //Successful encryption + return NO_ERROR; +} + + +/** + * @brief Decrypt an incoming TLS record + * @param[in] context Pointer to the TLS context + * @param[in,out] record TLS record to be decrypted + * @return Error code + **/ + +error_t tlsDecryptRecord(TlsContext *context, TlsRecord *record) +{ + error_t error; + size_t length; + + //Convert the length field to host byte order + length = ntohs(record->length); + + //Decrypt record if necessary + if(context->cipherMode != CIPHER_MODE_NULL) + { + //Debug message + TRACE_DEBUG("Record to be decrypted (%" PRIuSIZE " bytes):\r\n", length); + TRACE_DEBUG_ARRAY(" ", record, length + sizeof(TlsRecord)); + +#if (TLS_STREAM_CIPHER_SUPPORT == ENABLED) + //Stream cipher? + if(context->cipherMode == CIPHER_MODE_STREAM) + { + //Decrypt record contents + context->cipherAlgo->decryptStream(context->readCipherContext, + record->data, record->data, length); + } + else +#endif +#if (TLS_CBC_CIPHER_SUPPORT == ENABLED) + //CBC block cipher? + if(context->cipherMode == CIPHER_MODE_CBC) + { + size_t i; + size_t paddingLength; + + //The length of the data must be a multiple of the block size + if((length % context->cipherAlgo->blockSize) != 0) + return ERROR_DECODING_FAILED; + + //CBC decryption + error = cbcDecrypt(context->cipherAlgo, context->readCipherContext, + context->readIv, record->data, record->data, length); + //Any error to report? + if(error) + return error; + + //Debug message + TRACE_DEBUG("Record with padding (%" PRIuSIZE " bytes):\r\n", length); + TRACE_DEBUG_ARRAY(" ", record, length + sizeof(TlsRecord)); + +#if (TLS_MAX_VERSION >= TLS_VERSION_1_1 && TLS_MIN_VERSION <= TLS_VERSION_1_2) + //TLS 1.1 and 1.2 use an explicit IV + if(context->version >= TLS_VERSION_1_1) + { + //Make sure the message length is acceptable + if(length < context->recordIvLen) + return ERROR_DECODING_FAILED; + + //Adjust the length of the message + length -= context->recordIvLen; + //Discard the first cipher block (corresponding to the explicit IV) + memmove(record->data, record->data + context->recordIvLen, length); + } +#endif + //Make sure the message length is acceptable + if(length < context->cipherAlgo->blockSize) + return ERROR_DECODING_FAILED; + + //Compute the length of the padding string + paddingLength = record->data[length - 1]; + //Erroneous padding length? + if(paddingLength >= length) + return ERROR_BAD_RECORD_MAC; + + //The receiver must check the padding + for(i = 0; i <= paddingLength; i++) + { + //Each byte in the padding data must be filled + //with the padding length value + if(record->data[length - 1 - i] != paddingLength) + return ERROR_BAD_RECORD_MAC; + } + + //Remove padding bytes + length -= paddingLength + 1; + //Fix the length field of the TLS record + record->length = htons(length); + } + else +#endif +#if (TLS_CCM_CIPHER_SUPPORT == ENABLED || TLS_CCM_8_CIPHER_SUPPORT == ENABLED || \ + TLS_GCM_CIPHER_SUPPORT == ENABLED) + //CCM or GCM AEAD cipher? + if(context->cipherMode == CIPHER_MODE_CCM || + context->cipherMode == CIPHER_MODE_GCM) + { + uint8_t *ciphertext; + uint8_t *tag; + size_t nonceLength; + uint8_t nonce[12]; + uint8_t a[13]; + + //Make sure the message length is acceptable + if(length < (context->recordIvLen + context->authTagLen)) + return ERROR_DECODING_FAILED; + + //Determine the total length of the nonce + nonceLength = context->fixedIvLen + context->recordIvLen; + //The salt is the implicit part of the nonce and is not sent in the packet + memcpy(nonce, context->readIv, context->fixedIvLen); + //The explicit part of the nonce is chosen by the sender + memcpy(nonce + context->fixedIvLen, record->data, context->recordIvLen); + + //Calculate the length of the ciphertext + length -= context->recordIvLen + context->authTagLen; + //Fix the length field of the TLS record + record->length = htons(length); + + //Additional data to be authenticated + memcpy(a, context->readSeqNum, sizeof(TlsSequenceNumber)); + memcpy(a + sizeof(TlsSequenceNumber), record, sizeof(TlsRecord)); + + //Point to the ciphertext + ciphertext = record->data + context->recordIvLen; + //Point to the authentication tag + tag = ciphertext + length; + +#if (TLS_CCM_CIPHER_SUPPORT == ENABLED || TLS_CCM_8_CIPHER_SUPPORT == ENABLED) + //CCM AEAD cipher? + if(context->cipherMode == CIPHER_MODE_CCM) + { + //Authenticated decryption using CCM + error = ccmDecrypt(context->cipherAlgo, context->readCipherContext, + nonce, nonceLength, a, 13, ciphertext, ciphertext, length, tag, context->authTagLen); + } + else +#endif +#if (TLS_GCM_CIPHER_SUPPORT == ENABLED) + //GCM AEAD cipher? + if(context->cipherMode == CIPHER_MODE_GCM) + { + //Authenticated decryption using GCM + error = gcmDecrypt(context->readGcmContext, nonce, nonceLength, + a, 13, ciphertext, ciphertext, length, tag, context->authTagLen); + } + else +#endif + //Invalid cipher mode? + { + //The specified cipher mode is not supported + error = ERROR_UNSUPPORTED_CIPHER_MODE; + } + + //Wrong authentication tag? + if(error) + return ERROR_BAD_RECORD_MAC; + + //Discard the explicit part of the nonce + memmove(record->data, record->data + context->recordIvLen, length); + + //Increment sequence number + tlsIncSequenceNumber(context->readSeqNum); + } + else +#endif +#if (TLS_CHACHA20_POLY1305_SUPPORT == ENABLED) + //ChaCha20Poly1305 AEAD cipher? + if(context->cipherMode == CIPHER_MODE_CHACHA20_POLY1305) + { + size_t i; + uint8_t *tag; + uint8_t nonce[12]; + uint8_t a[13]; + + //Make sure the message length is acceptable + if(length < context->authTagLen) + return ERROR_DECODING_FAILED; + + //The 64-bit record sequence number is serialized as an 8-byte, + //big-endian value and padded on the left with four 0x00 bytes + memcpy(nonce + 4, context->readSeqNum, 8); + memset(nonce, 0, 4); + + //The padded sequence number is XORed with the read IV to form + //the 96-bit nonce + for(i = 0; i < context->fixedIvLen; i++) + nonce[i] ^= context->readIv[i]; + + //Calculate the length of the ciphertext + length -= context->authTagLen; + //Fix the length field of the TLS record + record->length = htons(length); + + //Additional data to be authenticated + memcpy(a, context->readSeqNum, sizeof(TlsSequenceNumber)); + memcpy(a + sizeof(TlsSequenceNumber), record, sizeof(TlsRecord)); + + //Point to the authentication tag + tag = record->data + length; + + //Authenticated decryption using ChaCha20Poly1305 + error = chacha20Poly1305Decrypt(context->readEncKey, context->encKeyLen, + nonce, 12, a, 13, record->data, record->data, length, tag, context->authTagLen); + //Wrong authentication tag? + if(error) + return ERROR_BAD_RECORD_MAC; + + //Increment sequence number + tlsIncSequenceNumber(context->readSeqNum); + } + else +#endif + //Invalid cipher mode? + { + //The specified cipher mode is not supported + return ERROR_UNSUPPORTED_CIPHER_MODE; + } + + //Debug message + TRACE_DEBUG("Decrypted record (%" PRIuSIZE " bytes):\r\n", length); + TRACE_DEBUG_ARRAY(" ", record, length + sizeof(TlsRecord)); + } + + //Check message authentication code if necessary + if(context->hashAlgo != NULL) + { + //Make sure the message length is acceptable + if(length < context->hashAlgo->digestSize) + return ERROR_DECODING_FAILED; + + //Adjust the length of the message + length -= context->hashAlgo->digestSize; + //Fix the length field of the TLS record + record->length = htons(length); + +#if (TLS_MAX_VERSION >= SSL_VERSION_3_0 && TLS_MIN_VERSION <= SSL_VERSION_3_0) + //Check whether SSL 3.0 is currently used + if(context->version == SSL_VERSION_3_0) + { + //SSL 3.0 uses an older obsolete version of the HMAC construction + error = sslComputeMac(context, context->readMacKey, context->readSeqNum, + record, record->data, length, context->hmacContext.digest); + //Any error to report? + if(error) + return error; + } + else +#endif +#if (TLS_MAX_VERSION >= TLS_VERSION_1_0 && TLS_MIN_VERSION <= TLS_VERSION_1_2) + //Check whether TLS 1.0, TLS 1.1 or TLS 1.2 is currently used + if(context->version >= TLS_VERSION_1_0) + { + //TLS uses a HMAC construction + hmacInit(&context->hmacContext, context->hashAlgo, + context->readMacKey, context->macKeyLen); + + //Compute MAC over the sequence number and the record contents + hmacUpdate(&context->hmacContext, context->readSeqNum, sizeof(TlsSequenceNumber)); + hmacUpdate(&context->hmacContext, record, sizeof(TlsRecord)); + hmacUpdate(&context->hmacContext, record->data, length); + hmacFinal(&context->hmacContext, NULL); + } + else +#endif + //The negotiated TLS version is not valid... + { + //Report an error + return ERROR_INVALID_VERSION; + } + + //Debug message + TRACE_DEBUG("Read sequence number:\r\n"); + TRACE_DEBUG_ARRAY(" ", context->readSeqNum, sizeof(TlsSequenceNumber)); + TRACE_DEBUG("Computed MAC:\r\n"); + TRACE_DEBUG_ARRAY(" ", context->hmacContext.digest, context->hashAlgo->digestSize); + + //Check the message authentication code + if(memcmp(record->data + length, context->hmacContext.digest, context->hashAlgo->digestSize)) + return ERROR_BAD_RECORD_MAC; + + //Increment sequence number + tlsIncSequenceNumber(context->readSeqNum); + } + + //Successful decryption + return NO_ERROR; +} + + +/** + * @brief Increment sequence number + * @param[in] seqNum Sequence number to increment + **/ + +void tlsIncSequenceNumber(TlsSequenceNumber seqNum) +{ + int_t i; + + //Sequence numbers are stored MSB first + for(i = 7; i >= 0; i--) + { + //Increment the current byte + seqNum[i]++; + //Propagate the carry if necessary + if(seqNum[i] != 0) + break; + } +} + +#endif +