Add AES-256-GCM support to SPTPS.
authorGuus Sliepen <guus@tinc-vpn.org>
Mon, 2 Aug 2021 21:53:13 +0000 (23:53 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Sun, 29 May 2022 14:23:47 +0000 (16:23 +0200)
This also adds a simple cipher suite negotiation, where peers announce the
ciphers they support, and their preferred cipher. Since we need to bump the
SPTPS version anyway, also prefer little endian over network byte order.

doc/SPTPS
src/invitation.c
src/protocol_auth.c
src/protocol_key.c
src/sptps.c
src/sptps.h
src/sptps_test.c

index 2d8fee5..2da2760 100644 (file)
--- a/doc/SPTPS
+++ b/doc/SPTPS
@@ -18,8 +18,8 @@ Stream record layer
 
 A record consists of these fields:
 
-- uint32_t seqno (network byte order)
-- uint16_t length (network byte order)
+- uint32_t seqno (little endian)
+- uint16_t length (little endian)
 - uint8_t type
 - opaque data[length]
 - opaque hmac[HMAC_SIZE] (HMAC over all preceding fields)
@@ -45,8 +45,8 @@ Datagram record layer
 
 A record consists of these fields:
 
-- uint16_t length (network byte order)
-- uint32_t seqno (network byte order)
+- uint16_t length (little endian)
+- uint32_t seqno (little endian)
 - uint8_t type
 - opaque data[length]
 - opaque hmac[HMAC_SIZE] (HMAC over all preceding fields)
@@ -75,7 +75,7 @@ SIG ->
 ...encrypt and HMAC using session keys from now on...
 
 App ->
-            <- App 
+            <- App
 ...
             ...
 
@@ -91,7 +91,7 @@ ACK ->
 ...encrypt and HMAC using new session keys from now on...
 
 App ->
-            <- App 
+            <- App
 ...
             ...
 ---------------------
@@ -102,7 +102,11 @@ connection.
 
 Key EXchange message:
 
-- uint8_t kex_version (always 0 in this version of SPTPS)
+- uint8_t kex_version (always 1 in this version of SPTPS)
+- uint8_t
+  - high 4 bits: public key algorithm
+  - low 4 bits: preferred cipher suite
+- uint16_t bitmask of cipher suites supported
 - opaque nonce[32] (random number)
 - opaque ecdh_key[ECDH_SIZE]
 
@@ -162,9 +166,34 @@ The expanded key is used as follows:
 Where initiator_cipher_key is the key used by session initiator to encrypt
 messages sent to the responder.
 
+Public key suites
+-----------------
+
+0: Ed25519 + SHA512
+1: Ed448 + SHAKE256?
+
+Symmetric cipher suites
+-----------------------
+
+Value in parentheses is the static priority used to break ties in cipher suite
+negotiation. We favor those algorithms that run faster without hardware
+acceleration.
+
+0: Chacha20-Poly1305 (1)
+1: AES256-GCM        (0)
+
+Cipher suite selection
+----------------------
+
+Public key suites are required to match on both sides. The symmetric suite is chosen as follows:
+
+1. AND the supported cipher suite bitmasks
+2. If both preferred cipher suites are possible, choose the one with the highest static priority.
+3. If only one is possible, choose that one.
+4. If none is possible, choose the suite from the resulting bitmask that has the highest static priority.
+
 TODO:
 -----
 
 - Document format of ECDH public key, ECDSA signature
-- Document how CTR mode is used
 - Refer to TLS RFCs where appropriate
index e068ae8..d83e137 100644 (file)
@@ -1397,7 +1397,16 @@ next:
        }
 
        // Start an SPTPS session
-       if(!sptps_start(&sptps, NULL, true, false, key, hiskey, "tinc invitation", 15, invitation_send, invitation_receive)) {
+       sptps_params_t params = {
+               .initiator = true,
+               .mykey = key,
+               .hiskey = hiskey,
+               .label = "tinc invitation",
+               .send_data = invitation_send,
+               .receive_record = invitation_receive,
+       };
+
+       if(!sptps_start(&sptps, &params)) {
                ecdsa_free(hiskey);
                ecdsa_free(key);
                return 1;
index 263a131..6a7a919 100644 (file)
@@ -359,7 +359,17 @@ bool id_h(connection_t *c, const char *request) {
 
                c->protocol_minor = 2;
 
-               return sptps_start(&c->sptps, c, false, false, invitation_key, c->ecdsa, "tinc invitation", 15, send_meta_sptps, receive_invitation_sptps);
+               sptps_params_t params = {
+                       .handle = c,
+                       .initiator = false,
+                       .mykey = invitation_key,
+                       .hiskey = c->ecdsa,
+                       .label = "tinc invitation",
+                       .send_data = send_meta_sptps,
+                       .receive_record = receive_invitation_sptps,
+               };
+
+               return sptps_start(&c->sptps, &params);
        }
 
        /* Check if identity is a valid name */
@@ -454,7 +464,18 @@ bool id_h(connection_t *c, const char *request) {
                        snprintf(label, labellen, "tinc TCP key expansion %s %s", c->name, myself->name);
                }
 
-               return sptps_start(&c->sptps, c, c->outgoing, false, myself->connection->ecdsa, c->ecdsa, label, labellen, send_meta_sptps, receive_meta_sptps);
+               sptps_params_t params = {
+                       .handle = c,
+                       .initiator = c->outgoing,
+                       .mykey = myself->connection->ecdsa,
+                       .hiskey = c->ecdsa,
+                       .label = label,
+                       .labellen = sizeof(label),
+                       .send_data = send_meta_sptps,
+                       .receive_record = receive_meta_sptps,
+               };
+
+               return sptps_start(&c->sptps, &params);
        } else {
                return send_metakey(c);
        }
index 0890755..71f3d8e 100644 (file)
@@ -128,7 +128,20 @@ bool send_req_key(node_t *to) {
                to->status.waitingforkey = true;
                to->last_req_key = now.tv_sec;
                to->incompression = myself->incompression;
-               return sptps_start(&to->sptps, to, true, true, myself->connection->ecdsa, to->ecdsa, label, labellen, send_initial_sptps_data, receive_sptps_record);
+
+               sptps_params_t params = {
+                       .handle = to,
+                       .initiator = true,
+                       .datagram = true,
+                       .mykey = myself->connection->ecdsa,
+                       .hiskey = to->ecdsa,
+                       .label = label,
+                       .labellen = sizeof(label),
+                       .send_data = send_initial_sptps_data,
+                       .receive_record = receive_sptps_record,
+               };
+
+               return sptps_start(&to->sptps, &params);
        }
 
        return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name);
@@ -249,7 +262,20 @@ static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, no
                from->status.validkey = false;
                from->status.waitingforkey = true;
                from->last_req_key = now.tv_sec;
-               sptps_start(&from->sptps, from, false, true, myself->connection->ecdsa, from->ecdsa, label, labellen, send_sptps_data_myself, receive_sptps_record);
+
+               sptps_params_t params = {
+                       .handle = from,
+                       .initiator = false,
+                       .datagram = true,
+                       .mykey = myself->connection->ecdsa,
+                       .hiskey = from->ecdsa,
+                       .label = label,
+                       .labellen = sizeof(label),
+                       .send_data = send_sptps_data_myself,
+                       .receive_record = receive_sptps_record,
+               };
+
+               sptps_start(&from->sptps, &params);
                sptps_receive_data(&from->sptps, buf, len);
                send_mtu_info(myself, from, MTU);
                return true;
index 35c68bc..7be8cd8 100644 (file)
 #include "random.h"
 #include "xalloc.h"
 
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#endif
+
 unsigned int sptps_replaywin = 16;
 
 /*
@@ -108,25 +112,160 @@ static void free_sptps_key(sptps_key_t *key) {
        xzfree(key, sizeof(sptps_key_t));
 }
 
+static bool cipher_init(uint8_t suite, void **ctx, const sptps_key_t *keys, bool key_half) {
+        const uint8_t *key = key_half ? keys->key1 : keys->key0;
+
+       switch(suite) {
+       case SPTPS_CHACHA_POLY1305:
+               *ctx = chacha_poly1305_init();
+               return ctx && chacha_poly1305_set_key(*ctx, key);
+
+       case SPTPS_AES256_GCM:
+#ifdef HAVE_OPENSSL
+               *ctx = EVP_CIPHER_CTX_new();
+
+               if(!ctx) {
+                       return false;
+               }
+
+               return EVP_EncryptInit_ex(*ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)
+                      && EVP_CIPHER_CTX_ctrl(*ctx, EVP_CTRL_AEAD_SET_IVLEN, 4, NULL)
+                      && EVP_EncryptInit_ex(*ctx, NULL, NULL, key, key + 32);
+#endif
+
+       default:
+               return false;
+       }
+}
+
+static void cipher_exit(uint8_t suite, void *ctx) {
+       switch(suite) {
+       case SPTPS_CHACHA_POLY1305:
+               chacha_poly1305_exit(ctx);
+               break;
+
+       case SPTPS_AES256_GCM:
+#ifdef HAVE_OPENSSL
+               EVP_CIPHER_CTX_free(ctx);
+               break;
+#endif
+
+       default:
+               break;
+       }
+}
+
+static bool cipher_encrypt(uint8_t suite, void *ctx, uint32_t seqno, const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen) {
+       switch(suite) {
+       case SPTPS_CHACHA_POLY1305:
+               chacha_poly1305_encrypt(ctx, seqno, in, inlen, out, outlen);
+               return true;
+
+       case SPTPS_AES256_GCM:
+#ifdef HAVE_OPENSSL
+               {
+                       if(!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, (uint8_t *)&seqno)) {
+                               return false;
+                       }
+
+                       int outlen1 = 0, outlen2 = 0;
+
+                       if(!EVP_EncryptUpdate(ctx, out, &outlen1, in, (int)inlen)) {
+                               return false;
+                       }
+
+                       if(!EVP_EncryptFinal_ex(ctx, out + outlen1, &outlen2)) {
+                               return false;
+                       }
+
+                       outlen1 += outlen2;
+
+                       if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, out + outlen1)) {
+                               return false;
+                       }
+
+                       outlen1 += 16;
+
+                       if(outlen) {
+                               *outlen = outlen1;
+                       }
+
+                       return true;
+               }
+
+#endif
+
+       default:
+               return false;
+       }
+}
+
+static bool cipher_decrypt(uint8_t suite, void *ctx, uint32_t seqno, const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen) {
+       switch(suite) {
+       case SPTPS_CHACHA_POLY1305:
+               return chacha_poly1305_decrypt(ctx, seqno, in, inlen, out, outlen);
+
+       case SPTPS_AES256_GCM:
+#ifdef HAVE_OPENSSL
+               {
+                       if(inlen < 16) {
+                               return false;
+                       }
+
+                       inlen -= 16;
+
+                       if(!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, (uint8_t *)&seqno)) {
+                               return false;
+                       }
+
+                       int outlen1 = 0, outlen2 = 0;
+
+                       if(!EVP_DecryptUpdate(ctx, out, &outlen1, in, (int)inlen)) {
+                               return false;
+                       }
+
+                       if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void *)(in + inlen))) {
+                               return false;
+                       }
+
+                       if(!EVP_DecryptFinal_ex(ctx, out + outlen1, &outlen2)) {
+                               return false;
+                       }
+
+                       if(outlen) {
+                               *outlen = outlen1 + outlen2;
+                       }
+
+                       return true;
+               }
+
+#endif
+
+       default:
+               return false;
+       }
+}
+
 // 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 void *data, uint16_t len) {
-       uint8_t *buffer = alloca(len + 21UL);
-
+       uint8_t *buffer = alloca(len + SPTPS_DATAGRAM_OVERHEAD);
        // Create header with sequence number, length and record type
        uint32_t seqno = s->outseqno++;
-       uint32_t netseqno = ntohl(seqno);
 
-       memcpy(buffer, &netseqno, 4);
+       memcpy(buffer, &seqno, 4);
        buffer[4] = type;
        memcpy(buffer + 5, data, len);
 
        if(s->outstate) {
                // If first handshake has finished, encrypt and HMAC
-               chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 4, len + 1, buffer + 4, NULL);
-               return s->send_data(s->handle, type, buffer, len + 21UL);
+               if(!cipher_encrypt(s->cipher_suite, s->outcipher, seqno, buffer + 4, len + 1, buffer + 4, NULL)) {
+                       return error(s, EINVAL, "Failed to encrypt message");
+               }
+
+               return s->send_data(s->handle, type, buffer, len + SPTPS_DATAGRAM_OVERHEAD);
        } else {
                // Otherwise send as plaintext
-               return s->send_data(s->handle, type, buffer, len + 5UL);
+               return s->send_data(s->handle, type, buffer, len + SPTPS_DATAGRAM_HEADER);
        }
 }
 // Send a record (private version, accepts all record types, handles encryption and authentication).
@@ -135,11 +274,11 @@ static bool send_record_priv(sptps_t *s, uint8_t type, const void *data, uint16_
                return send_record_priv_datagram(s, type, data, len);
        }
 
-       uint8_t *buffer = alloca(len + 19UL);
+       uint8_t *buffer = alloca(len + SPTPS_OVERHEAD);
 
        // Create header with sequence number, length and record type
        uint32_t seqno = s->outseqno++;
-       uint16_t netlen = htons(len);
+       uint16_t netlen = len;
 
        memcpy(buffer, &netlen, 2);
        buffer[2] = type;
@@ -147,11 +286,14 @@ static bool send_record_priv(sptps_t *s, uint8_t type, const void *data, uint16_
 
        if(s->outstate) {
                // If first handshake has finished, encrypt and HMAC
-               chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 2, len + 1, buffer + 2, NULL);
-               return s->send_data(s->handle, type, buffer, len + 19UL);
+               if(!cipher_encrypt(s->cipher_suite, s->outcipher, seqno, buffer + 2, len + 1, buffer + 2, NULL)) {
+                       return error(s, EINVAL, "Failed to encrypt message");
+               }
+
+               return s->send_data(s->handle, type, buffer, len + SPTPS_OVERHEAD);
        } else {
                // Otherwise send as plaintext
-               return s->send_data(s->handle, type, buffer, len + 3UL);
+               return s->send_data(s->handle, type, buffer, len + SPTPS_HEADER);
        }
 }
 
@@ -181,6 +323,8 @@ static bool send_kex(sptps_t *s) {
 
        // Set version byte to zero.
        s->mykex->version = SPTPS_VERSION;
+       s->mykex->preferred_suite = s->preferred_suite;
+       s->mykex->cipher_suites = s->cipher_suites;
 
        // Create a random nonce.
        randomize(s->mykex->nonce, ECDH_SIZE);
@@ -225,16 +369,6 @@ static bool send_sig(sptps_t *s) {
 
 // Generate key material from the shared secret created from the ECDHE key exchange.
 static bool generate_key_material(sptps_t *s, const uint8_t *shared, size_t len) {
-       // Initialise cipher and digest structures if necessary
-       if(!s->outstate) {
-               s->incipher = chacha_poly1305_init();
-               s->outcipher = chacha_poly1305_init();
-
-               if(!s->incipher || !s->outcipher) {
-                       return error(s, EINVAL, "Failed to open cipher");
-               }
-       }
-
        // Allocate memory for key material
        s->key = new_sptps_key();
 
@@ -276,10 +410,8 @@ static bool receive_ack(sptps_t *s, const uint8_t *data, uint16_t len) {
                return error(s, EIO, "Invalid ACK record length");
        }
 
-       uint8_t *key = s->initiator ? s->key->key0 : s->key->key1;
-
-       if(!chacha_poly1305_set_key(s->incipher, key)) {
-               return error(s, EINVAL, "Failed to set counter");
+       if(!cipher_init(s->cipher_suite, &s->incipher, s->key, s->initiator)) {
+               return error(s, EINVAL, "Failed to initialize cipher");
        }
 
        free_sptps_key(s->key);
@@ -289,9 +421,35 @@ static bool receive_ack(sptps_t *s, const uint8_t *data, uint16_t len) {
        return true;
 }
 
+static uint8_t select_cipher_suite(uint16_t mask, uint8_t pref1, uint8_t pref2) {
+       // Check if there is a viable preference, if so select the lowest one
+       uint8_t selection = 255;
+
+       if(mask & (1U << pref1)) {
+               selection = pref1;
+       }
+
+       if(pref2 < selection && (mask & (1U << pref2))) {
+               selection = pref2;
+       }
+
+       // Otherwise, select the lowest cipher suite both sides support
+       if(selection == 255) {
+               selection = 0;
+
+               while(!(mask & 1U)) {
+                       selection++;
+                       mask >>= 1;
+               }
+       }
+
+       return selection;
+}
+
 // Receive a Key EXchange record, respond by sending a SIG record.
 static bool receive_kex(sptps_t *s, const uint8_t *data, uint16_t len) {
        // Verify length of the HELLO record
+
        if(len != sizeof(sptps_kex_t)) {
                return error(s, EIO, "Invalid KEX record length");
        }
@@ -300,6 +458,16 @@ static bool receive_kex(sptps_t *s, const uint8_t *data, uint16_t len) {
                return error(s, EINVAL, "Received incorrect version %d", *data);
        }
 
+       uint16_t suites;
+       memcpy(&suites, data + 2, 2);
+       suites &= s->cipher_suites;
+
+       if(!suites) {
+               return error(s, EIO, "No matching cipher suites");
+       }
+
+       s->cipher_suite = select_cipher_suite(suites, s->preferred_suite, data[1] & 0xf);
+
        // Make a copy of the KEX message, send_sig() and receive_sig() need it
        if(s->hiskex) {
                return error(s, EINVAL, "Received a second KEX message before first has been processed");
@@ -365,11 +533,8 @@ static bool receive_sig(sptps_t *s, const uint8_t *data, uint16_t len) {
                return false;
        }
 
-       // TODO: only set new keys after ACK has been set/received
-       uint8_t *key = s->initiator ? s->key->key1 : s->key->key0;
-
-       if(!chacha_poly1305_set_key(s->outcipher, key)) {
-               return error(s, EINVAL, "Failed to set key");
+       if(!cipher_init(s->cipher_suite, &s->outcipher, s->key, !s->initiator)) {
+               return error(s, EINVAL, "Failed to initialize cipher");
        }
 
        return true;
@@ -519,15 +684,13 @@ bool sptps_verify_datagram(sptps_t *s, const void *vdata, size_t len) {
        const uint8_t *data = vdata;
        uint32_t seqno;
        memcpy(&seqno, data, 4);
-       seqno = ntohl(seqno);
 
        if(!sptps_check_seqno(s, seqno, false)) {
                return false;
        }
 
        uint8_t *buffer = alloca(len);
-       size_t outlen;
-       return chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen);
+       return cipher_decrypt(s->cipher_suite, s->incipher, seqno, data + 4, len - 4, buffer, NULL);
 }
 
 // Receive incoming data, datagram version.
@@ -538,7 +701,6 @@ static bool sptps_receive_data_datagram(sptps_t *s, const uint8_t *data, size_t
 
        uint32_t seqno;
        memcpy(&seqno, data, 4);
-       seqno = ntohl(seqno);
        data += 4;
        len -= 4;
 
@@ -564,7 +726,7 @@ static bool sptps_receive_data_datagram(sptps_t *s, const uint8_t *data, size_t
        uint8_t *buffer = alloca(len);
        size_t outlen;
 
-       if(!chacha_poly1305_decrypt(s->incipher, seqno, data, len, buffer, &outlen)) {
+       if(!cipher_decrypt(s->cipher_suite, s->incipher, seqno, data, len, buffer, &outlen)) {
                return error(s, EIO, "Failed to decrypt and verify packet");
        }
 
@@ -636,10 +798,9 @@ size_t sptps_receive_data(sptps_t *s, const void *vdata, size_t len) {
                // Get the length bytes
 
                memcpy(&s->reclen, s->inbuf, 2);
-               s->reclen = ntohs(s->reclen);
 
                // If we have the length bytes, ensure our buffer can hold the whole request.
-               s->inbuf = realloc(s->inbuf, s->reclen + 19UL);
+               s->inbuf = realloc(s->inbuf, s->reclen + SPTPS_OVERHEAD);
 
                if(!s->inbuf) {
                        return error(s, errno, "%s", strerror(errno));
@@ -652,7 +813,7 @@ size_t sptps_receive_data(sptps_t *s, const void *vdata, size_t len) {
        }
 
        // Read up to the end of the record.
-       size_t toread = s->reclen + (s->instate ? 19UL : 3UL) - s->buflen;
+       size_t toread = s->reclen + (s->instate ? SPTPS_OVERHEAD : SPTPS_HEADER) - s->buflen;
 
        if(toread > len) {
                toread = len;
@@ -663,7 +824,7 @@ size_t sptps_receive_data(sptps_t *s, const void *vdata, size_t len) {
        s->buflen += toread;
 
        // If we don't have a whole record, exit.
-       if(s->buflen < s->reclen + (s->instate ? 19UL : 3UL)) {
+       if(s->buflen < s->reclen + (s->instate ? SPTPS_OVERHEAD : SPTPS_HEADER)) {
                return total_read;
        }
 
@@ -673,13 +834,13 @@ size_t sptps_receive_data(sptps_t *s, const void *vdata, size_t len) {
 
        // Check HMAC and decrypt.
        if(s->instate) {
-               if(!chacha_poly1305_decrypt(s->incipher, seqno, s->inbuf + 2UL, s->reclen + 17UL, s->inbuf + 2UL, NULL)) {
+               if(!cipher_decrypt(s->cipher_suite, s->incipher, seqno, s->inbuf + 2UL, s->reclen + 17UL, s->inbuf + 2UL, NULL)) {
                        return error(s, EINVAL, "Failed to decrypt and verify record");
                }
        }
 
        // Append a NULL byte for safety.
-       s->inbuf[s->reclen + 3UL] = 0;
+       s->inbuf[s->reclen + SPTPS_HEADER] = 0;
 
        uint8_t type = s->inbuf[2];
 
@@ -705,16 +866,18 @@ size_t sptps_receive_data(sptps_t *s, const void *vdata, size_t len) {
 }
 
 // Start a SPTPS session.
-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) {
+bool sptps_start(sptps_t *s, const sptps_params_t *params) {
        // Initialise struct sptps
        memset(s, 0, sizeof(*s));
 
-       s->handle = handle;
-       s->initiator = initiator;
-       s->datagram = datagram;
-       s->mykey = mykey;
-       s->hiskey = hiskey;
+       s->handle = params->handle;
+       s->initiator = params->initiator;
+       s->datagram = params->datagram;
+       s->mykey = params->mykey;
+       s->hiskey = params->hiskey;
        s->replaywin = sptps_replaywin;
+       s->cipher_suites = params->cipher_suites ? params->cipher_suites & SPTPS_ALL_CIPHER_SUITES : SPTPS_ALL_CIPHER_SUITES;
+       s->preferred_suite = params->preferred_suite;
 
        if(s->replaywin) {
                s->late = malloc(s->replaywin);
@@ -726,13 +889,16 @@ bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_
                memset(s->late, 0, s->replaywin);
        }
 
-       s->label = malloc(labellen);
+       s->labellen = params->labellen ? params->labellen : strlen(params->label);
+       s->label = malloc(s->labellen);
 
        if(!s->label) {
                return error(s, errno, "%s", strerror(errno));
        }
 
-       if(!datagram) {
+       memcpy(s->label, params->label, s->labellen);
+
+       if(!s->datagram) {
                s->inbuf = malloc(7);
 
                if(!s->inbuf) {
@@ -742,11 +908,9 @@ bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_
                s->buflen = 0;
        }
 
-       memcpy(s->label, label, labellen);
-       s->labellen = labellen;
 
-       s->send_data = send_data;
-       s->receive_record = receive_record;
+       s->send_data = params->send_data;
+       s->receive_record = params->receive_record;
 
        // Do first KEX immediately
        s->state = SPTPS_KEX;
@@ -756,8 +920,8 @@ bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_
 // Stop a SPTPS session.
 bool sptps_stop(sptps_t *s) {
        // Clean up any resources.
-       chacha_poly1305_exit(s->incipher);
-       chacha_poly1305_exit(s->outcipher);
+       cipher_exit(s->cipher_suite, s->incipher);
+       cipher_exit(s->cipher_suite, s->outcipher);
        ecdh_free(s->ecdh);
        free(s->inbuf);
        free_sptps_kex(s->mykex);
index 633eb81..e21804a 100644 (file)
@@ -26,7 +26,7 @@
 #include "ecdh.h"
 #include "ecdsa.h"
 
-#define SPTPS_VERSION 0
+#define SPTPS_VERSION 1
 
 // Record types
 #define SPTPS_HANDSHAKE 128   // Key exchange and authentication
 #define SPTPS_CLOSE 130       // Application closed the connection
 
 // Overhead for datagrams
-#define SPTPS_DATAGRAM_OVERHEAD 21
+static const size_t SPTPS_OVERHEAD = 19;
+static const size_t SPTPS_HEADER = 3;
+static const size_t SPTPS_DATAGRAM_OVERHEAD = 21;
+static const size_t SPTPS_DATAGRAM_HEADER = 5;
 
 typedef bool (*send_data_t)(void *handle, uint8_t type, const void *data, size_t len);
 typedef bool (*receive_record_t)(void *handle, uint8_t type, const void *data, uint16_t len);
@@ -49,13 +52,15 @@ typedef enum sptps_state_t {
 
 PACKED(struct sptps_kex_t {
        uint8_t version;
+       uint8_t preferred_suite;
+       uint16_t cipher_suites;
        uint8_t nonce[ECDH_SIZE];
        uint8_t pubkey[ECDH_SIZE];
 });
 
 typedef struct sptps_kex_t sptps_kex_t;
 
-STATIC_ASSERT(sizeof(sptps_kex_t) == 65, "sptps_kex_t has invalid size");
+STATIC_ASSERT(sizeof(sptps_kex_t) == 68, "sptps_kex_t has invalid size");
 
 typedef union sptps_key_t {
        struct {
@@ -67,9 +72,40 @@ typedef union sptps_key_t {
 
 STATIC_ASSERT(sizeof(sptps_key_t) == 128, "sptps_key_t has invalid size");
 
+// Public key suites
+enum {
+       SPTPS_ED25519 = 0,
+};
+
+// Cipher suites
+enum {
+       SPTPS_CHACHA_POLY1305 = 0,
+       SPTPS_AES256_GCM = 1,
+       SPTPS_ALL_CIPHER_SUITES = 0x3,
+};
+
+typedef struct sptps_params {
+       void *handle;
+       bool initiator;
+       bool datagram;
+       uint8_t preferred_suite;
+       uint16_t cipher_suites;
+       ecdsa_t *mykey;
+       ecdsa_t *hiskey;
+       const void *label;
+       size_t labellen;
+       send_data_t send_data;
+       receive_record_t receive_record;
+} sptps_params_t;
+
 typedef struct sptps {
        bool initiator;
        bool datagram;
+       uint8_t preferred_suite;
+       uint16_t cipher_suites;
+
+       uint8_t pk_suite;
+       uint8_t cipher_suite;
        sptps_state_t state;
 
        uint8_t *inbuf;
@@ -77,7 +113,7 @@ typedef struct sptps {
        uint16_t reclen;
 
        bool instate;
-       chacha_poly1305_ctx_t *incipher;
+       void *incipher;
        uint32_t inseqno;
        uint32_t received;
        unsigned int replaywin;
@@ -85,7 +121,7 @@ typedef struct sptps {
        uint8_t *late;
 
        bool outstate;
-       chacha_poly1305_ctx_t *outcipher;
+       void *outcipher;
        uint32_t outseqno;
 
        ecdsa_t *mykey;
@@ -107,7 +143,7 @@ extern unsigned int sptps_replaywin;
 extern void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap) ATTR_FORMAT(printf, 3, 0);
 extern void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap) ATTR_FORMAT(printf, 3, 0);
 extern void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap) ATTR_FORMAT(printf, 3, 0);
-extern 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);
+extern bool sptps_start(sptps_t *s, const struct sptps_params *params);
 extern bool sptps_stop(sptps_t *s);
 extern bool sptps_send_record(sptps_t *s, uint8_t type, const void *data, uint16_t len);
 extern size_t sptps_receive_data(sptps_t *s, const void *data, size_t len);
index 38e8b50..73f4ec0 100644 (file)
@@ -583,7 +583,18 @@ static int run_test(int argc, char *argv[]) {
 
        sptps_t s;
 
-       if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record)) {
+       sptps_params_t params = {
+               .handle = &sock,
+               .initiator = initiator,
+               .datagram = datagram,
+               .mykey = mykey,
+               .hiskey = hiskey,
+               .label = "sptps_test",
+               .send_data = send_data,
+               .receive_record = receive_record,
+       };
+
+       if(!sptps_start(&s, &params)) {
                ecdsa_free(mykey);
                ecdsa_free(hiskey);
                return 1;