Remove use of strcpy() and sprintf().
[tinc] / src / sptps.c
index 0989b14..712d50e 100644 (file)
@@ -1,6 +1,6 @@
 /*
     sptps.c -- Simple Peer-to-Peer Security
-    Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>,
+    Copyright (C) 2011-2015 Guus Sliepen <guus@tinc-vpn.org>,
                   2010      Brandon L. Black <blblack@gmail.com>
 
     This program is free software; you can redistribute it and/or modify
@@ -39,7 +39,7 @@ unsigned int sptps_replaywin = 16;
 
    Sign all handshake messages up to ECDHE kex with long-term public keys. (done)
 
-   HMACed KEX finished message to prevent downgrade attacks and prove you have the right key material (done by virtue of ECDSA over the whole ECDHE exchange?)
+   HMACed KEX finished message to prevent downgrade attacks and prove you have the right key material (done by virtue of Ed25519 over the whole ECDHE exchange?)
 
    Explicit close message needs to be added.
 
@@ -81,7 +81,7 @@ static void warning(sptps_t *s, const char *format, ...) {
 }
 
 // Send a record (datagram version, accepts all record types, handles encryption and authentication).
-static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const char *data, uint16_t len) {
+static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const void *data, uint16_t len) {
        char buffer[len + 21UL];
 
        // Create header with sequence number, length and record type
@@ -102,7 +102,7 @@ static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const char *data
        }
 }
 // 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) {
+static bool send_record_priv(sptps_t *s, uint8_t type, const void *data, uint16_t len) {
        if(s->datagram)
                return send_record_priv_datagram(s, type, data, len);
 
@@ -127,7 +127,7 @@ static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_
 }
 
 // Send an application record.
-bool sptps_send_record(sptps_t *s, uint8_t type, const char *data, uint16_t len) {
+bool sptps_send_record(sptps_t *s, uint8_t type, const void *data, uint16_t len) {
        // Sanity checks: application cannot send data before handshake is finished,
        // and only record types 0..127 are allowed.
        if(!s->outstate)
@@ -163,7 +163,7 @@ static bool send_kex(sptps_t *s) {
        return send_record_priv(s, SPTPS_HANDSHAKE, s->mykex, 1 + 32 + keylen);
 }
 
-// Send a SIGnature record, containing an ECDSA signature over both KEX records.
+// Send a SIGnature record, containing an Ed25519 signature over both KEX records.
 static bool send_sig(sptps_t *s) {
        size_t keylen = ECDH_SIZE;
        size_t siglen = ecdsa_size(s->mykey);
@@ -204,7 +204,7 @@ static bool generate_key_material(sptps_t *s, const char *shared, size_t len) {
 
        // Create the HMAC seed, which is "key expansion" + session label + server nonce + client nonce
        char seed[s->labellen + 64 + 13];
-       strcpy(seed, "key expansion");
+       memcpy(seed, "key expansion", 13);
        if(s->initiator) {
                memcpy(seed + 13, s->mykex + 1, 32);
                memcpy(seed + 45, s->hiskex + 1, 32);
@@ -370,14 +370,74 @@ static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) {
        }
 }
 
+static bool sptps_check_seqno(sptps_t *s, uint32_t seqno, bool update_state) {
+       // Replay protection using a sliding window of configurable size.
+       // s->inseqno is expected sequence number
+       // seqno is received sequence number
+       // s->late[] is a circular buffer, a 1 bit means a packet has not been received yet
+       // The circular buffer contains bits for sequence numbers from s->inseqno - s->replaywin * 8 to (but excluding) s->inseqno.
+       if(s->replaywin) {
+               if(seqno != s->inseqno) {
+                       if(seqno >= s->inseqno + s->replaywin * 8) {
+                               // Prevent packets that jump far ahead of the queue from causing many others to be dropped.
+                               bool farfuture = s->farfuture < s->replaywin >> 2;
+                               if (update_state)
+                                       s->farfuture++;
+                               if(farfuture)
+                                       return update_state ? error(s, EIO, "Packet is %d seqs in the future, dropped (%u)\n", seqno - s->inseqno, s->farfuture) : false;
+
+                               // Unless we have seen lots of them, in which case we consider the others lost.
+                               if(update_state)
+                                       warning(s, "Lost %d packets\n", seqno - s->inseqno);
+                               if (update_state) {
+                                       // Mark all packets in the replay window as being late.
+                                       memset(s->late, 255, s->replaywin);
+                               }
+                       } else if (seqno < s->inseqno) {
+                               // If the sequence number is farther in the past than the bitmap goes, or if the packet was already received, drop it.
+                               if((s->inseqno >= s->replaywin * 8 && seqno < s->inseqno - s->replaywin * 8) || !(s->late[(seqno / 8) % s->replaywin] & (1 << seqno % 8)))
+                                       return update_state ? error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno) : false;
+                       } else if (update_state) {
+                               // We missed some packets. Mark them in the bitmap as being late.
+                               for(int i = s->inseqno; i < seqno; i++)
+                                       s->late[(i / 8) % s->replaywin] |= 1 << i % 8;
+                       }
+               }
+
+               if (update_state) {
+                       // Mark the current packet as not being late.
+                       s->late[(seqno / 8) % s->replaywin] &= ~(1 << seqno % 8);
+                       s->farfuture = 0;
+               }
+       }
+
+       if (update_state) {
+               if(seqno >= s->inseqno)
+                       s->inseqno = seqno + 1;
+
+               if(!s->inseqno)
+                       s->received = 0;
+               else
+                       s->received++;
+       }
+
+       return true;
+}
+
 // Check datagram for valid HMAC
-bool sptps_verify_datagram(sptps_t *s, const char *data, size_t len) {
+bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len) {
        if(!s->instate || len < 21)
                return error(s, EIO, "Received short packet");
 
-       // TODO: just decrypt without updating the replay window
+       uint32_t seqno;
+       memcpy(&seqno, data, 4);
+       seqno = ntohl(seqno);
+       if (!sptps_check_seqno(s, seqno, false))
+               return false;
 
-       return true;
+       char buffer[len];
+       size_t outlen;
+       return chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen);
 }
 
 // Receive incoming data, datagram version.
@@ -388,6 +448,7 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len
        uint32_t seqno;
        memcpy(&seqno, data, 4);
        seqno = ntohl(seqno);
+       data += 4; len -= 4;
 
        if(!s->instate) {
                if(seqno != s->inseqno)
@@ -395,76 +456,40 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len
 
                s->inseqno = seqno + 1;
 
-               uint8_t type = data[4];
+               uint8_t type = *(data++); len--;
 
                if(type != SPTPS_HANDSHAKE)
                        return error(s, EIO, "Application record received before handshake finished");
 
-               return receive_handshake(s, data + 5, len - 5);
+               return receive_handshake(s, data, len);
        }
 
        // Decrypt
 
        char buffer[len];
-
        size_t outlen;
-
-       if(!chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen))
+       if(!chacha_poly1305_decrypt(s->incipher, seqno, data, len, buffer, &outlen))
                return error(s, EIO, "Failed to decrypt and verify packet");
 
-       // Replay protection using a sliding window of configurable size.
-       // s->inseqno is expected sequence number
-       // seqno is received sequence number
-       // s->late[] is a circular buffer, a 1 bit means a packet has not been received yet
-       // The circular buffer contains bits for sequence numbers from s->inseqno - s->replaywin * 8 to (but excluding) s->inseqno.
-       if(s->replaywin) {
-               if(seqno != s->inseqno) {
-                       if(seqno >= s->inseqno + s->replaywin * 8) {
-                               // Prevent packets that jump far ahead of the queue from causing many others to be dropped.
-                               if(s->farfuture++ < s->replaywin >> 2)
-                                       return error(s, EIO, "Packet is %d seqs in the future, dropped (%u)\n", seqno - s->inseqno, s->farfuture);
-
-                               // Unless we have seen lots of them, in which case we consider the others lost.
-                               warning(s, "Lost %d packets\n", seqno - s->inseqno);
-                               // Mark all packets in the replay window as being late.
-                               memset(s->late, 255, s->replaywin);
-                       } else if (seqno < s->inseqno) {
-                               // If the sequence number is farther in the past than the bitmap goes, or if the packet was already received, drop it.
-                               if((s->inseqno >= s->replaywin * 8 && seqno < s->inseqno - s->replaywin * 8) || !(s->late[(seqno / 8) % s->replaywin] & (1 << seqno % 8)))
-                                       return error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno);
-                       } else {
-                               // We missed some packets. Mark them in the bitmap as being late.
-                               for(int i = s->inseqno; i < seqno; i++)
-                                       s->late[(i / 8) % s->replaywin] |= 1 << i % 8;
-                       }
-               }
-
-               // Mark the current packet as not being late.
-               s->late[(seqno / 8) % s->replaywin] &= ~(1 << seqno % 8);
-               s->farfuture = 0;
-       }
-
-       if(seqno >= s->inseqno)
-               s->inseqno = seqno + 1;
-
-       if(!s->inseqno)
-               s->received = 0;
-       else
-               s->received++;
+       if(!sptps_check_seqno(s, seqno, true))
+               return false;
 
        // Append a NULL byte for safety.
-       buffer[len - 20] = 0;
+       buffer[outlen] = 0;
+
+       data = buffer;
+       len = outlen;
 
-       uint8_t type = buffer[0];
+       uint8_t type = *(data++); len--;
 
        if(type < SPTPS_HANDSHAKE) {
                if(!s->instate)
                        return error(s, EIO, "Application record received before handshake finished");
-               if(!s->receive_record(s->handle, type, buffer + 1, len - 21))
-                       abort();
+               if(!s->receive_record(s->handle, type, data, len))
+                       return false;
        } else if(type == SPTPS_HANDSHAKE) {
-               if(!receive_handshake(s, buffer + 1, len - 21))
-                       abort();
+               if(!receive_handshake(s, data, len))
+                       return false;
        } else {
                return error(s, EIO, "Invalid record type %d", type);
        }
@@ -473,94 +498,96 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len
 }
 
 // Receive incoming data. Check if it contains a complete record, if so, handle it.
-bool sptps_receive_data(sptps_t *s, const char *data, size_t len) {
+size_t sptps_receive_data(sptps_t *s, const void *data, size_t len) {
+       size_t total_read = 0;
+
        if(!s->state)
                return error(s, EIO, "Invalid session state zero");
 
        if(s->datagram)
-               return sptps_receive_data_datagram(s, data, len);
+               return sptps_receive_data_datagram(s, data, len) ? len : false;
 
-       while(len) {
-               // First read the 2 length bytes.
-               if(s->buflen < 2) {
-                       size_t toread = 2 - s->buflen;
-                       if(toread > len)
-                               toread = len;
-
-                       memcpy(s->inbuf + s->buflen, data, toread);
+       // First read the 2 length bytes.
+       if(s->buflen < 2) {
+               size_t toread = 2 - s->buflen;
+               if(toread > len)
+                       toread = len;
 
-                       s->buflen += toread;
-                       len -= toread;
-                       data += toread;
+               memcpy(s->inbuf + s->buflen, data, toread);
 
-                       // Exit early if we don't have the full length.
-                       if(s->buflen < 2)
-                               return true;
+               total_read += toread;
+               s->buflen += toread;
+               len -= toread;
+               data += toread;
 
-                       // Get the length bytes
+               // Exit early if we don't have the full length.
+               if(s->buflen < 2)
+                       return total_read;
 
-                       memcpy(&s->reclen, s->inbuf, 2);
-                       s->reclen = ntohs(s->reclen);
+               // Get the length bytes
 
-                       // If we have the length bytes, ensure our buffer can hold the whole request.
-                       s->inbuf = realloc(s->inbuf, s->reclen + 19UL);
-                       if(!s->inbuf)
-                               return error(s, errno, strerror(errno));
+               memcpy(&s->reclen, s->inbuf, 2);
+               s->reclen = ntohs(s->reclen);
 
-                       // Exit early if we have no more data to process.
-                       if(!len)
-                               return true;
-               }
+               // If we have the length bytes, ensure our buffer can hold the whole request.
+               s->inbuf = realloc(s->inbuf, s->reclen + 19UL);
+               if(!s->inbuf)
+                       return error(s, errno, strerror(errno));
 
-               // Read up to the end of the record.
-               size_t toread = s->reclen + (s->instate ? 19UL : 3UL) - s->buflen;
-               if(toread > len)
-                       toread = len;
+               // Exit early if we have no more data to process.
+               if(!len)
+                       return total_read;
+       }
 
-               memcpy(s->inbuf + s->buflen, data, toread);
-               s->buflen += toread;
-               len -= toread;
-               data += toread;
+       // Read up to the end of the record.
+       size_t toread = s->reclen + (s->instate ? 19UL : 3UL) - s->buflen;
+       if(toread > len)
+               toread = len;
 
-               // If we don't have a whole record, exit.
-               if(s->buflen < s->reclen + (s->instate ? 19UL : 3UL))
-                       return true;
+       memcpy(s->inbuf + s->buflen, data, toread);
+       total_read += toread;
+       s->buflen += toread;
+       len -= toread;
+       data += toread;
 
-               // Update sequence number.
+       // If we don't have a whole record, exit.
+       if(s->buflen < s->reclen + (s->instate ? 19UL : 3UL))
+               return total_read;
 
-               uint32_t seqno = s->inseqno++;
+       // Update sequence number.
 
-               // Check HMAC and decrypt.
-               if(s->instate) {
-                       if(!chacha_poly1305_decrypt(s->incipher, seqno, s->inbuf + 2UL, s->reclen + 17UL, s->inbuf + 2UL, NULL))
-                               return error(s, EINVAL, "Failed to decrypt and verify record");
-               }
+       uint32_t seqno = s->inseqno++;
 
-               // Append a NULL byte for safety.
-               s->inbuf[s->reclen + 3UL] = 0;
+       // Check HMAC and decrypt.
+       if(s->instate) {
+               if(!chacha_poly1305_decrypt(s->incipher, seqno, s->inbuf + 2UL, s->reclen + 17UL, s->inbuf + 2UL, NULL))
+                       return error(s, EINVAL, "Failed to decrypt and verify record");
+       }
 
-               uint8_t type = s->inbuf[2];
+       // Append a NULL byte for safety.
+       s->inbuf[s->reclen + 3UL] = 0;
 
-               if(type < SPTPS_HANDSHAKE) {
-                       if(!s->instate)
-                               return error(s, EIO, "Application record received before handshake finished");
-                       if(!s->receive_record(s->handle, type, s->inbuf + 3, s->reclen))
-                               return false;
-               } else if(type == SPTPS_HANDSHAKE) {
-                       if(!receive_handshake(s, s->inbuf + 3, s->reclen))
-                               return false;
-               } else {
-                       return error(s, EIO, "Invalid record type %d", type);
-               }
+       uint8_t type = s->inbuf[2];
 
-               s->buflen = 0;
+       if(type < SPTPS_HANDSHAKE) {
+               if(!s->instate)
+                       return error(s, EIO, "Application record received before handshake finished");
+               if(!s->receive_record(s->handle, type, s->inbuf + 3, s->reclen))
+                       return false;
+       } else if(type == SPTPS_HANDSHAKE) {
+               if(!receive_handshake(s, s->inbuf + 3, s->reclen))
+                       return false;
+       } else {
+               return error(s, EIO, "Invalid record type %d", type);
        }
 
-       return true;
+       s->buflen = 0;
+
+       return total_read;
 }
 
 // Start a SPTPS session.
-bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) {
+bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const void *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) {
        // Initialise struct sptps
        memset(s, 0, sizeof *s);