A simple library to support serving https.

Dependents:   oldheating gps motorhome heating

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