A simple library to support serving https.
Dependents: oldheating gps motorhome heating
Diff: tls/tls-response.c
- Revision:
- 10:e269fd7b9500
- Child:
- 13:0a80b49a5e78
diff -r f354b4859b0b -r e269fd7b9500 tls/tls-response.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tls/tls-response.c Tue Sep 24 18:11:02 2019 +0000 @@ -0,0 +1,322 @@ +#include "tls-defs.h" +#include "tls-connection.h" +#include "tls-session.h" +#include "tls-log.h" +#include "tls-prf.h" +#include "ser-cer.h" +#include "pri-key.h" +#include "log.h" +#include "aes128.h" +#include "random.h" +#include "sha1.h" +#include "tls-mac.h" +#include "http.h" + +void addSize(uint8_t** ppCurrent, int size) +{ + uint8_t* p = *ppCurrent; + *p++ = size >> 8; + *p++ = size & 0xFF; + *ppCurrent = p; +} +void backfillSize(uint8_t* pCurrent, uint8_t* pStart) +{ + int size = pCurrent - pStart - 2; + *(pStart + 0) = size >> 8; + *(pStart + 1) = size & 0xFF; +} +static void sendServerHello(struct TlsConnection* pConnection, struct TlsSession* pSession, int* pWindowSize, uint8_t* pWindow, uint32_t positionOfWindowInStream) +{ + //Set up a multiple handshakes content - hello, certificate and done + LogTime(" sending server hello\r\n"); + uint8_t* p = pWindow; + *p++ = TLS_CONTENT_TYPE_Handshake; //Content is handshakes + *p++ = 0x03; *p++ = 0x03; //Legacy TLS version + uint8_t* pHandshakesLength = p; + p += 2; //Handshakes Length (2 bytes) + uint8_t* handshakesPayloadStart = p; //Record the start of the handshake payload + + //Server hello handshake + *p++ = TLS_HANDSHAKE_ServerHello; //Handshake type server hello + *p++ = 0x00; + uint8_t* pHandshakeHelloLength = p; + p += 2; //Size of this handshake + *p++ = 0x03; *p++ = 0x03; //TLS version 1.2 + for (int i = 0; i < 32; i++) + { + uint8_t r = RandomGetByte(); + pConnection->serverRandom[i] = r; + *p++ = r; //32 bit random number + } + *p++ = 0x04; //SessionId length 4 + *p++ = pConnection->sessionId >> 24; //Session id + *p++ = pConnection->sessionId >> 16; //Session id + *p++ = pConnection->sessionId >> 8; //Session id + *p++ = pConnection->sessionId >> 0; //Session id + *p++ = 0x00; *p++ = 0x2f; //Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f) + *p++ = 0x00; //Compression method none + *p++ = 0x00; *p++ = 0x05; //Extensions length (2 bytes) 5 bytes + *p++ = 0xff; *p++ = 0x01; //Extension Renegotiation Info + *p++ = 0x00; *p++ = 0x01; //1 bytes of "Renegotiation Info" extension data follows + *p++ = 0x00; //length is zero, because this is a new connection + backfillSize(p, pHandshakeHelloLength); + + //Certificate handshake + *p++ = TLS_HANDSHAKE_Certificate; *p++ = 0x00; //Handshake type certificate + addSize(&p, SerCerSize + 6); *p++ = 0x00; //Size of this handshake + addSize(&p, SerCerSize + 3); *p++ = 0x00; //Size of all certificates + addSize(&p, SerCerSize ); //Size of first certificate + for (int i = 0; i < SerCerSize; i++) *p++ = SerCerData[i]; //Certificate + + //Hello done handshake + *p++ = TLS_HANDSHAKE_ServerHelloDone; *p++ = 0x00; //Handshake type server hello done + *p++ = 0; *p++ = 0; //Size of this handshake + backfillSize(p, pHandshakesLength); + + //Finalise the handshake content + int handshakeLength = p - handshakesPayloadStart; + Sha256Add(&pConnection->handshakeSha, handshakesPayloadStart, handshakeLength); //Add the handshake hash + *pWindowSize = p - pWindow; +} +static void sendServerChange(struct TlsConnection* pConnection, struct TlsSession* pSession, int* pWindowSize, uint8_t* pWindow, uint32_t positionOfWindowInStream) +{ + LogTime(" sending server change\r\n"); + uint8_t* p = pWindow; + *p++ = TLS_CONTENT_TYPE_CHANGE_CIPHER; //Content is change cipher + *p++ = 0x03; *p++ = 0x03; //Legacy TLS version + *p++ = 0x00; *p++ = 0x01; //Change cipher Length (2 bytes) + *p++ = 0x01; //Change cipher message 1 + + //Record that all incoming messages are now encrypted + pConnection->serverEncrypted = true; + pConnection->serverSequence = 0; + + LogTime(" sending server handshake finished\r\n"); + *p++ = TLS_CONTENT_TYPE_Handshake; //Content is handshakes + *p++ = 0x03; *p++ = 0x03; //Legacy TLS version + uint8_t* pHandshakesLength = p; + p += 2; //Handshakes Length (2 bytes) + + //Hash over all handshake payloads exchanged so far + uint8_t hash[32]; + Sha256Finish(&pConnection->handshakeSha, hash); + + //Make verify data + uint8_t verify[12]; + TlsPrfServerFinished(pSession->masterSecret, hash, verify); //Hash over all handshakes + + //Make the 'finished' handshake + uint8_t payload[16]; + payload[0] = TLS_HANDSHAKE_Finished; + payload[1] = 0x00; + payload[2] = 0x00; + payload[3] = 0x0c; //Length 12 + for (int i = 0; i < 12; i++) payload[i + 4] = verify[i]; + int payloadLength = 16; + + uint8_t mac[SHA1_HASH_SIZE]; + TlsMacSha1(TLS_KEY_SIZE_MAC, + pConnection->serverMacKey, + pConnection->serverSequence, + TLS_CONTENT_TYPE_Handshake, + 0x03, + 0x03, + payloadLength, + payload, + mac); + + + //plaintext + uint8_t message[48]; + for (int i = 0; i < 16; i++) message[i ] = payload[i]; //payload + for (int i = 0; i < 20; i++) message[i + 16] = mac[i]; //mac + for (int i = 0; i < 12; i++) message[i + 36] = 0x0b; //padding + + uint8_t iv[16]; + for (int i = 0; i < 16; i++) iv[i] = RandomGetByte(); + + //Encrypt + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, pConnection->serverWriteKey, iv); + AES_CBC_encrypt_buffer(&ctx, message, 48); + + for (int i = 0; i < 16; i++) *p++ = iv[i]; + for (int i = 0; i < 48; i++) *p++ = message[i]; + + //Finalise + backfillSize(p, pHandshakesLength); + pConnection->serverSequence++; + *pWindowSize = p - pWindow; + pConnection->serverPositionInStreamOffset = positionOfWindowInStream + *pWindowSize; +} +static void sendFatal(char description, int* pWindowSize, uint8_t* pWindow, uint32_t positionOfWindowInStream) +{ + LogTime(" sending fatal alert: "); + TlsLogAlertDescription(description); + Log("\r\n"); + uint8_t* p = pWindow; + *p++ = TLS_CONTENT_TYPE_ALERT; //Content is alert + *p++ = 0x03; *p++ = 0x03; //Legacy TLS version + addSize(&p, 2); //Alert Length (2 bytes) + + *p++ = 2; //Fatal (level = 2) + *p++ = description; //Description + + *pWindowSize = p - pWindow; +} + +static bool sendContent(struct TlsConnection* pConnection, int* pWindowSize, uint8_t* pWindow, uint32_t positionOfWindowInStream) +{ +/* +content: + contentType * 1 + version * 2 + length * 2 + iv * AES_BLOCKLEN (16) + message: + payload * payloadLength + mac * SHA1_HASH_SIZE (20) + padding * 0 to AES_BLOCKLEN - 1 (0 to 15) + paddingLength * 1 +*/ +#define CONTENT_MAX_OVERHEAD (5 + AES_BLOCKLEN + SHA1_HASH_SIZE + AES_BLOCKLEN - 1 + 1) + + //Start + LogTime(" adding application content\r\n"); + LogF("- available window size %d\r\n", *pWindowSize); + LogF("- position of window in stream %d\r\n", positionOfWindowInStream); + uint8_t* p = pWindow; + *p++ = TLS_CONTENT_TYPE_Application; + *p++ = 0x03; *p++ = 0x03; + + //Prepare a place to backfill the size + uint8_t* pBackfillSize = p; + *p++ = 0; *p++ = 0; + + //Add the IV + uint8_t* pIv = p; + for (int i = 0; i < AES_BLOCKLEN; i++) *p++ = RandomGetByte(); + + //Add the plain payload + uint8_t* pPayload = p; + int payloadSize = *pWindowSize - CONTENT_MAX_OVERHEAD; + LogF("- available payload size %d\r\n", payloadSize); + uint32_t positionOfPayloadInStream = positionOfWindowInStream - pConnection->serverPositionInStreamOffset; + LogF("- position of payload in stream %d\r\n", positionOfPayloadInStream); + bool finished = HttpAdd(pConnection->id, &payloadSize, (char*)pPayload, positionOfPayloadInStream); //Return whatever HTTP would be + LogF("- resulting payload size %d\r\n", payloadSize); + p += payloadSize; + + //Add the MAC + TlsMacSha1(TLS_KEY_SIZE_MAC, + pConnection->serverMacKey, + pConnection->serverSequence, + TLS_CONTENT_TYPE_Application, + 0x03, + 0x03, + payloadSize, + pPayload, + p); + p += SHA1_HASH_SIZE; + + //Add the padding + int paddingSize = AES_BLOCKLEN - 1 - (payloadSize + SHA1_HASH_SIZE + 1 - 1) % AES_BLOCKLEN; + LogF("- padding size %d\r\n", paddingSize); + for (int i = 0; i < paddingSize; i++) *p++ = paddingSize; + + //Add the padding size + *p++ = paddingSize; + + //Backfill the size + backfillSize(p, pBackfillSize); + + //Calculate the resulting window size + *pWindowSize = p - pWindow; + LogF("- resulting window size %d\r\n", *pWindowSize); + + //Log the plain content + Log("- plain content\r\n"); LogBytesAsHex(pWindow, *pWindowSize); Log("\r\n"); + + //Encrypt payload + mac + padding + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, pConnection->serverWriteKey, pIv); + AES_CBC_encrypt_buffer(&ctx, pPayload, p - pPayload); + + //Finalise + pConnection->serverSequence++; + pConnection->serverPositionInStreamOffset += *pWindowSize - payloadSize; + + return finished; +} +bool TlsResponse(int connectionId, bool clientFinished, int* pWindowSize, uint8_t* pWindow, uint32_t positionOfWindowInStream) +{ + struct TlsConnection* pConnection = TlsConnectionOrNull(connectionId); + if (!pConnection) + { + *pWindowSize = 0; + return false; + } + + if (!pConnection->sessionId) + { + *pWindowSize = 0; + return false; + } + + struct TlsSession* pSession = TlsSessionOrNull(pConnection->sessionId); + if (!pSession) + { + LogTimeF("TlsPoll - invalid session %u\r\n", pConnection->sessionId); + *pWindowSize = 0; + return false; + } + + switch (pConnection->toDo) + { + case DO_WAIT_CLIENT_HELLO: + case DO_WAIT_CLIENT_CHANGE: + case DO_WAIT_DECRYPT_MASTER_SECRET: + *pWindowSize = 0; + if (clientFinished) return true; //The client hasn't made a request and never will so finish + else return false; //The client hasn't made a request yet but it could. + + case DO_SEND_SERVER_HELLO: + sendServerHello(pConnection, pSession, pWindowSize, pWindow, positionOfWindowInStream); + pConnection->toDo = DO_WAIT_CLIENT_CHANGE; + return false; //Not finished + + case DO_SEND_SERVER_CHANGE: + sendServerChange(pConnection, pSession, pWindowSize, pWindow, positionOfWindowInStream); + pConnection->toDo = DO_APPLICATION; + return false; + + case DO_APPLICATION: + { + int status = HttpPoll(connectionId, clientFinished); + bool finished; + switch (status) + { + case HTTP_WAIT: finished = false; *pWindowSize = 0; break; + case HTTP_FINISHED: finished = true; *pWindowSize = 0; break; + case HTTP_HAVE_SOMETHING_TO_SEND: finished = sendContent(pConnection, pWindowSize, pWindow, positionOfWindowInStream); break; + } + if (finished) pConnection->toDo = DO_WAIT_CLIENT_HELLO; + return finished; + } + case DO_SEND_ALERT_ILLEGAL_PARAMETER: + sendFatal(TLS_ALERT_ILLEGAL_PARAMETER, pWindowSize, pWindow, positionOfWindowInStream); + pConnection->toDo = DO_WAIT_CLIENT_HELLO; + return true; //Finished + + case DO_SEND_ALERT_INTERNAL_ERROR: + sendFatal(TLS_ALERT_INTERNAL_ERROR, pWindowSize, pWindow, positionOfWindowInStream); + pConnection->toDo = DO_WAIT_CLIENT_HELLO; + return true; //Finished + + default: + LogTimeF("TlsPoll - unspecified TLS state %d\r\n", pConnection->toDo); + sendFatal(TLS_ALERT_INTERNAL_ERROR, pWindowSize, pWindow, positionOfWindowInStream); //Internal error + pConnection->toDo = DO_WAIT_CLIENT_HELLO; + return true; //Finished + } +} \ No newline at end of file