Use SPTPS when ExperimentalProtocol is enabled.
[tinc] / src / sptps.c
index dc602e3..5088d65 100644 (file)
@@ -27,9 +27,6 @@
 #include "prf.h"
 #include "sptps.h"
 
 #include "prf.h"
 #include "sptps.h"
 
-char *logfilename;
-#include "utils.c"
-
 /*
    Nonce MUST be exchanged first (done)
    Signatures MUST be done over both nonces, to guarantee the signature is fresh
 /*
    Nonce MUST be exchanged first (done)
    Signatures MUST be done over both nonces, to guarantee the signature is fresh
@@ -45,7 +42,7 @@ char *logfilename;
 
    Maybe do add some alert messages to give helpful error messages? Not more than TLS sends.
 
 
    Maybe do add some alert messages to give helpful error messages? Not more than TLS sends.
 
-   Use counter mode instead of OFB.
+   Use counter mode instead of OFB. (done)
 
    Make sure ECC operations are fixed time (aka prevent side-channel attacks).
 */
 
    Make sure ECC operations are fixed time (aka prevent side-channel attacks).
 */
@@ -59,32 +56,31 @@ static bool error(sptps_t *s, int s_errno, const char *msg) {
 
 // Send a record (private version, accepts all record types, handles encryption and authentication).
 static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_t len) {
 
 // Send a record (private version, accepts all record types, handles encryption and authentication).
 static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_t len) {
-       char plaintext[len + 23];
-       char ciphertext[len + 19];
+       char buffer[len + 23UL];
 
        // Create header with sequence number, length and record type
        uint32_t seqno = htonl(s->outseqno++);
        uint16_t netlen = htons(len);
 
 
        // Create header with sequence number, length and record type
        uint32_t seqno = htonl(s->outseqno++);
        uint16_t netlen = htons(len);
 
-       memcpy(plaintext, &seqno, 4);
-       memcpy(plaintext + 4, &netlen, 2);
-       plaintext[6] = type;
+       memcpy(buffer, &seqno, 4);
+       memcpy(buffer + 4, &netlen, 2);
+       buffer[6] = type;
 
        // Add plaintext (TODO: avoid unnecessary copy)
 
        // Add plaintext (TODO: avoid unnecessary copy)
-       memcpy(plaintext + 7, data, len);
+       memcpy(buffer + 7, data, len);
 
        if(s->outstate) {
                // If first handshake has finished, encrypt and HMAC
 
        if(s->outstate) {
                // If first handshake has finished, encrypt and HMAC
-               if(!digest_create(&s->outdigest, plaintext, len + 7, plaintext + 7 + len))
+               if(!cipher_counter_xor(&s->outcipher, buffer + 4, len + 3UL, buffer + 4))
                        return false;
 
                        return false;
 
-               if(!cipher_encrypt(&s->outcipher, plaintext + 4, sizeof ciphertext, ciphertext, NULL, false))
+               if(!digest_create(&s->outdigest, buffer, len + 7UL, buffer + 7UL + len))
                        return false;
 
                        return false;
 
-               return s->send_data(s->handle, ciphertext, len + 19);
+               return s->send_data(s->handle, buffer + 4, len + 19UL);
        } else {
                // Otherwise send as plaintext
        } else {
                // Otherwise send as plaintext
-               return s->send_data(s->handle, plaintext + 4, len + 3);
+               return s->send_data(s->handle, buffer + 4, len + 3UL);
        }
 }
 
        }
 }
 
@@ -149,8 +145,8 @@ static bool generate_key_material(sptps_t *s, const char *shared, size_t len) {
        // Initialise cipher and digest structures if necessary
        if(!s->outstate) {
                bool result
        // Initialise cipher and digest structures if necessary
        if(!s->outstate) {
                bool result
-                       =  cipher_open_by_name(&s->incipher, "aes-256-ofb")
-                       && cipher_open_by_name(&s->outcipher, "aes-256-ofb")
+                       =  cipher_open_by_name(&s->incipher, "aes-256-ecb")
+                       && cipher_open_by_name(&s->outcipher, "aes-256-ecb")
                        && digest_open_by_name(&s->indigest, "sha256", 16)
                        && digest_open_by_name(&s->outdigest, "sha256", 16);
                if(!result)
                        && digest_open_by_name(&s->indigest, "sha256", 16)
                        && digest_open_by_name(&s->outdigest, "sha256", 16);
                if(!result)
@@ -191,10 +187,27 @@ static bool send_ack(sptps_t *s) {
 // Receive an ACKnowledgement record.
 static bool receive_ack(sptps_t *s, const char *data, uint16_t len) {
        if(len)
 // Receive an ACKnowledgement record.
 static bool receive_ack(sptps_t *s, const char *data, uint16_t len) {
        if(len)
-               return false;
+               return error(s, EIO, "Invalid ACK record length");
+
+       if(s->initiator) {
+               bool result
+                       = cipher_set_counter_key(&s->incipher, s->key)
+                       && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->incipher), digest_keylength(&s->indigest));
+               if(!result)
+                       return false;
+       } else {
+               bool result
+                       = cipher_set_counter_key(&s->incipher, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest))
+                       && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest) + cipher_keylength(&s->incipher), digest_keylength(&s->indigest));
+               if(!result)
+                       return false;
+       }
 
 
-       // TODO: set cipher/digest keys
-       return error(s, ENOSYS, "receive_ack() not completely implemented yet");
+       free(s->key);
+       s->key = NULL;
+       s->instate = true;
+
+       return true;
 }
 
 // Receive a Key EXchange record, respond by sending a SIG record.
 }
 
 // Receive a Key EXchange record, respond by sending a SIG record.
@@ -244,31 +257,32 @@ static bool receive_sig(sptps_t *s, const char *data, uint16_t len) {
        if(!generate_key_material(s, shared, sizeof shared))
                return false;
 
        if(!generate_key_material(s, shared, sizeof shared))
                return false;
 
-       // Send cipher change record if necessary
-       //if(s->outstate && !send_ack(s))
-       //      return false;
+       free(s->mykex);
+       free(s->hiskex);
+
+       s->mykex = NULL;
+       s->hiskex = NULL;
+
+       // Send cipher change record
+       if(!send_ack(s))
+               return false;
 
        // TODO: only set new keys after ACK has been set/received
        if(s->initiator) {
                bool result
 
        // TODO: only set new keys after ACK has been set/received
        if(s->initiator) {
                bool result
-                       =  cipher_set_key(&s->incipher, s->key, false)
-                       && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->incipher), digest_keylength(&s->indigest))
-                       && cipher_set_key(&s->outcipher, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest), true)
+                       = cipher_set_counter_key(&s->outcipher, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest))
                        && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest) + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest));
                if(!result)
                        return false;
        } else {
                bool result
                        && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest) + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest));
                if(!result)
                        return false;
        } else {
                bool result
-                       =  cipher_set_key(&s->outcipher, s->key, true)
-                       && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest))
-                       && cipher_set_key(&s->incipher, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest), false)
-                       && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest) + cipher_keylength(&s->incipher), digest_keylength(&s->indigest));
+                       =  cipher_set_counter_key(&s->outcipher, s->key)
+                       && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest));
                if(!result)
                        return false;
        }
 
        s->outstate = true;
                if(!result)
                        return false;
        }
 
        s->outstate = true;
-       s->instate = true;
 
        return true;
 }
 
        return true;
 }
@@ -302,12 +316,13 @@ static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) {
                        if(!receive_sig(s, data, len))
                                return false;
                        // s->state = SPTPS_ACK;
                        if(!receive_sig(s, data, len))
                                return false;
                        // s->state = SPTPS_ACK;
-                       s->state = SPTPS_SECONDARY_KEX;
+                       s->state = SPTPS_ACK;
                        return true;
                case SPTPS_ACK:
                        // We expect a handshake message to indicate transition to the new keys.
                        if(!receive_ack(s, data, len))
                                return false;
                        return true;
                case SPTPS_ACK:
                        // We expect a handshake message to indicate transition to the new keys.
                        if(!receive_ack(s, data, len))
                                return false;
+                       s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0);
                        s->state = SPTPS_SECONDARY_KEX;
                        return true;
                // TODO: split ACK into a VERify and ACK?
                        s->state = SPTPS_SECONDARY_KEX;
                        return true;
                // TODO: split ACK into a VERify and ACK?
@@ -325,26 +340,29 @@ bool receive_data(sptps_t *s, const char *data, size_t len) {
                        if(toread > len)
                                toread = len;
 
                        if(toread > len)
                                toread = len;
 
-                       if(s->instate) {
-                               if(!cipher_decrypt(&s->incipher, data, toread, s->inbuf + s->buflen, NULL, false))
-                                       return false;
-                       } else {
-                               memcpy(s->inbuf + s->buflen, data, toread);
-                       }
+                       memcpy(s->inbuf + s->buflen, data, toread);
 
                        s->buflen += toread;
                        len -= toread;
                        data += toread;
 
                        s->buflen += toread;
                        len -= toread;
                        data += toread;
-
+               
                        // Exit early if we don't have the full length.
                        if(s->buflen < 6)
                                return true;
 
                        // Exit early if we don't have the full length.
                        if(s->buflen < 6)
                                return true;
 
+                       // Decrypt the length bytes
+
+                       if(s->instate) {
+                               if(!cipher_counter_xor(&s->incipher, s->inbuf + 4, 2, &s->reclen))
+                                       return false;
+                       } else {
+                               memcpy(&s->reclen, s->inbuf + 4, 2);
+                       }
+
+                       s->reclen = ntohs(s->reclen);
+
                        // If we have the length bytes, ensure our buffer can hold the whole request.
                        // If we have the length bytes, ensure our buffer can hold the whole request.
-                       uint16_t reclen;
-                       memcpy(&reclen, s->inbuf + 4, 2);
-                       reclen = htons(reclen);
-                       s->inbuf = realloc(s->inbuf, reclen + 23UL);
+                       s->inbuf = realloc(s->inbuf, s->reclen + 23UL);
                        if(!s->inbuf)
                                return error(s, errno, strerror(errno));
 
                        if(!s->inbuf)
                                return error(s, errno, strerror(errno));
 
@@ -358,41 +376,40 @@ bool receive_data(sptps_t *s, const char *data, size_t len) {
                }
 
                // Read up to the end of the record.
                }
 
                // Read up to the end of the record.
-               uint16_t reclen;
-               memcpy(&reclen, s->inbuf + 4, 2);
-               reclen = htons(reclen);
-               size_t toread = reclen + (s->instate ? 23UL : 7UL) - s->buflen;
+               size_t toread = s->reclen + (s->instate ? 23UL : 7UL) - s->buflen;
                if(toread > len)
                        toread = len;
 
                if(toread > len)
                        toread = len;
 
-               if(s->instate) {
-                       if(!cipher_decrypt(&s->incipher, data, toread, s->inbuf + s->buflen, NULL, false))
-                               return false;
-               } else {
-                       memcpy(s->inbuf + s->buflen, data, toread);
-               }
-
+               memcpy(s->inbuf + s->buflen, data, toread);
                s->buflen += toread;
                len -= toread;
                data += toread;
 
                // If we don't have a whole record, exit.
                s->buflen += toread;
                len -= toread;
                data += toread;
 
                // If we don't have a whole record, exit.
-               if(s->buflen < reclen + (s->instate ? 23UL : 7UL))
+               if(s->buflen < s->reclen + (s->instate ? 23UL : 7UL))
                        return true;
 
                        return true;
 
-               // Check HMAC.
-               if(s->instate)
-                       if(!digest_verify(&s->indigest, s->inbuf, reclen + 7UL, s->inbuf + reclen + 7UL))
-                               error(s, EIO, "Invalid HMAC");
+               // Check HMAC and decrypt.
+               if(s->instate) {
+                       if(!digest_verify(&s->indigest, s->inbuf, s->reclen + 7UL, s->inbuf + s->reclen + 7UL))
+                               return error(s, EIO, "Invalid HMAC");
+
+                       if(!cipher_counter_xor(&s->incipher, s->inbuf + 6UL, s->reclen + 1UL, s->inbuf + 6UL))
+                               return false;
+               }
+
+               // Append a NULL byte for safety.
+               s->inbuf[s->reclen + 7UL] = 0;
 
                uint8_t type = s->inbuf[6];
 
 
                uint8_t type = s->inbuf[6];
 
-               // Handle record.
                if(type < SPTPS_HANDSHAKE) {
                if(type < SPTPS_HANDSHAKE) {
-                       if(!s->receive_record(s->handle, type, s->inbuf + 7, reclen))
+                       if(!s->instate)
+                               return error(s, EIO, "Application record received before handshake finished");
+                       if(!s->receive_record(s->handle, type, s->inbuf + 7, s->reclen))
                                return false;
                } else if(type == SPTPS_HANDSHAKE) {
                                return false;
                } else if(type == SPTPS_HANDSHAKE) {
-                       if(!receive_handshake(s, s->inbuf + 7, reclen))
+                       if(!receive_handshake(s, s->inbuf + 7, s->reclen))
                                return false;
                } else {
                        return error(s, EIO, "Invalid record type");
                                return false;
                } else {
                        return error(s, EIO, "Invalid record type");