tinc/debian/patches/0001-Add-AES-256-GCM-support-to-SPTPS.patch

880 lines
26 KiB
Diff
Raw Permalink Normal View History

From cc521f3d5f3a0c758163c871e75f5e533e86771b Mon Sep 17 00:00:00 2001
From: Guus Sliepen <guus@tinc-vpn.org>
Date: Mon, 2 Aug 2021 23:53:13 +0200
Subject: [PATCH 01/10] Add AES-256-GCM support to SPTPS.
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 | 45 +++++--
src/invitation.c | 11 +-
src/protocol_auth.c | 25 +++-
src/protocol_key.c | 30 ++++-
src/sptps.c | 310 +++++++++++++++++++++++++++++++++-----------
src/sptps.h | 44 ++++++-
src/sptps_test.c | 13 +-
7 files changed, 382 insertions(+), 96 deletions(-)
diff --git a/doc/SPTPS b/doc/SPTPS
index 2d8fee5b..2da27604 100644
--- 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
diff --git a/src/invitation.c b/src/invitation.c
index cff9e727..6c49af48 100644
--- a/src/invitation.c
+++ b/src/invitation.c
@@ -1399,7 +1399,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;
diff --git a/src/protocol_auth.c b/src/protocol_auth.c
index 22254575..050b266c 100644
--- a/src/protocol_auth.c
+++ b/src/protocol_auth.c
@@ -412,7 +412,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 */
@@ -507,7 +517,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);
}
diff --git a/src/protocol_key.c b/src/protocol_key.c
index 740d2fb4..da53c16c 100644
--- a/src/protocol_key.c
+++ b/src/protocol_key.c
@@ -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;
diff --git a/src/sptps.c b/src/sptps.c
index a0483c34..33c41424 100644
--- a/src/sptps.c
+++ b/src/sptps.c
@@ -28,6 +28,10 @@
#include "sptps.h"
#include "random.h"
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#endif
+
unsigned int sptps_replaywin = 16;
/*
@@ -90,25 +94,159 @@ static void warning(sptps_t *s, const char *format, ...) {
va_end(ap);
}
+static bool cipher_init(uint8_t suite, void **ctx, const uint8_t *key, bool key_half) {
+ switch(suite) {
+ case SPTPS_CHACHA_POLY1305:
+ *ctx = chacha_poly1305_init();
+ return ctx && chacha_poly1305_set_key(*ctx, key + (key_half ? CHACHA_POLY1305_KEYLEN : 0));
+
+ 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_half ? 32 : 0), key);
+#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).
@@ -117,11 +255,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;
@@ -129,11 +267,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);
}
}
@@ -161,7 +302,7 @@ static bool send_kex(sptps_t *s) {
return false;
}
- s->mykex = realloc(s->mykex, 1 + 32 + keylen);
+ s->mykex = realloc(s->mykex, 4 + 32 + keylen);
if(!s->mykex) {
return error(s, errno, strerror(errno));
@@ -169,16 +310,18 @@ static bool send_kex(sptps_t *s) {
// Set version byte to zero.
s->mykex[0] = SPTPS_VERSION;
+ s->mykex[1] = s->preferred_suite;
+ memcpy(s->mykex + 2, &s->cipher_suites, 2);
// Create a random nonce.
- randomize(s->mykex + 1, 32);
+ randomize(s->mykex + 4, 32);
// Create a new ECDH public key.
- if(!(s->ecdh = ecdh_generate_public(s->mykex + 1 + 32))) {
+ if(!(s->ecdh = ecdh_generate_public(s->mykex + 4 + 32))) {
return error(s, EINVAL, "Failed to generate ECDH public key");
}
- return send_record_priv(s, SPTPS_HANDSHAKE, s->mykex, 1 + 32 + keylen);
+ return send_record_priv(s, SPTPS_HANDSHAKE, s->mykex, 4 + 32 + keylen);
}
// Send a SIGnature record, containing an Ed25519 signature over both KEX records.
@@ -192,9 +335,9 @@ static bool send_sig(sptps_t *s) {
uint8_t *sig = alloca(siglen);
msg[0] = s->initiator;
- memcpy(msg + 1, s->mykex, 1 + 32 + keylen);
- memcpy(msg + 1 + 33 + keylen, s->hiskex, 1 + 32 + keylen);
- memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen);
+ memcpy(msg + 1, s->mykex, 4 + 32 + keylen);
+ memcpy(msg + 1 + (4 + 32 + keylen), s->hiskex, 4 + 32 + keylen);
+ memcpy(msg + 1 + 2 * (4 + 32 + keylen), s->label, s->labellen);
// Sign the result.
if(!ecdsa_sign(s->mykey, msg, msglen, sig)) {
@@ -207,16 +350,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
size_t keylen = 2 * CHACHA_POLY1305_KEYLEN;
@@ -261,14 +394,8 @@ static bool receive_ack(sptps_t *s, const uint8_t *data, uint16_t len) {
return error(s, EIO, "Invalid ACK record length");
}
- if(s->initiator) {
- if(!chacha_poly1305_set_key(s->incipher, s->key)) {
- return error(s, EINVAL, "Failed to set counter");
- }
- } else {
- if(!chacha_poly1305_set_key(s->incipher, s->key + CHACHA_POLY1305_KEYLEN)) {
- 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(s->key);
@@ -278,14 +405,51 @@ 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 != 1 + 32 + ECDH_SIZE) {
+ if(len != 4 + 32 + ECDH_SIZE) {
return error(s, EIO, "Invalid KEX record length");
}
- // Ignore version number for now.
+ if(data[0] != SPTPS_VERSION) {
+ return error(s, EIO, "Incompatible SPTPS version");
+ }
+
+ 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) {
@@ -322,9 +486,9 @@ static bool receive_sig(sptps_t *s, const uint8_t *data, uint16_t len) {
uint8_t *msg = alloca(msglen);
msg[0] = !s->initiator;
- memcpy(msg + 1, s->hiskex, 1 + 32 + keylen);
- memcpy(msg + 1 + 33 + keylen, s->mykex, 1 + 32 + keylen);
- memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen);
+ memcpy(msg + 1, s->hiskex, 4 + 32 + keylen);
+ memcpy(msg + 1 + (4 + 32 + keylen), s->mykex, 4 + 32 + keylen);
+ memcpy(msg + 1 + 2 * (4 + 32 + keylen), s->label, s->labellen);
// Verify signature.
if(!ecdsa_verify(s->hiskey, msg, msglen, data)) {
@@ -334,7 +498,7 @@ static bool receive_sig(sptps_t *s, const uint8_t *data, uint16_t len) {
// Compute shared secret.
uint8_t shared[ECDH_SHARED_SIZE];
- if(!ecdh_compute_shared(s->ecdh, s->hiskex + 1 + 32, shared)) {
+ if(!ecdh_compute_shared(s->ecdh, s->hiskex + 4 + 32, shared)) {
return error(s, EINVAL, "Failed to compute ECDH shared secret");
}
@@ -360,15 +524,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
- if(s->initiator) {
- if(!chacha_poly1305_set_key(s->outcipher, s->key + CHACHA_POLY1305_KEYLEN)) {
- return error(s, EINVAL, "Failed to set key");
- }
- } else {
- if(!chacha_poly1305_set_key(s->outcipher, s->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;
@@ -518,15 +675,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.
@@ -537,7 +692,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;
@@ -563,7 +717,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");
}
@@ -635,10 +789,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, strerror(errno));
@@ -651,7 +804,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;
@@ -662,7 +815,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;
}
@@ -672,13 +825,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];
@@ -704,16 +857,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);
@@ -725,13 +880,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, strerror(errno));
}
- if(!datagram) {
+ memcpy(s->label, params->label, s->labellen);
+
+ if(!s->datagram) {
s->inbuf = malloc(7);
if(!s->inbuf) {
@@ -741,11 +899,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;
@@ -755,8 +911,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(s->mykex);
diff --git a/src/sptps.h b/src/sptps.h
index 96edc366..b9ec11fd 100644
--- a/src/sptps.h
+++ b/src/sptps.h
@@ -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
@@ -34,7 +34,10 @@
#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);
@@ -47,9 +50,40 @@ typedef enum sptps_state_t {
SPTPS_ACK = 4, // Waiting for an ACKnowledgement record
} sptps_state_t;
+// 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;
@@ -57,7 +91,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;
@@ -65,7 +99,7 @@ typedef struct sptps {
uint8_t *late;
bool outstate;
- chacha_poly1305_ctx_t *outcipher;
+ void *outcipher;
uint32_t outseqno;
ecdsa_t *mykey;
@@ -87,7 +121,7 @@ extern unsigned int sptps_replaywin;
extern void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap);
extern void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap);
extern void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap);
-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);
diff --git a/src/sptps_test.c b/src/sptps_test.c
index 249f2e4f..e77ab9c7 100644
--- a/src/sptps_test.c
+++ b/src/sptps_test.c
@@ -562,7 +562,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)) {
free(mykey);
free(hiskey);
return 1;
--
2.36.0