/*
sptps.c -- Simple Peer-to-Peer Security
- Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>,
- 2010 Brandon L. Black <blblack@gmail.com>
+ Copyright (C) 2014 Guus Sliepen <guus@meshlink.io>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "system.h"
-#include "cipher.h"
+#include "chacha-poly1305/chacha-poly1305.h"
#include "crypto.h"
#include "ecdh.h"
#include "ecdsa.h"
}
// 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
- uint32_t seqno = htonl(s->outseqno++);
+ uint32_t seqno = s->outseqno++;
+ uint32_t netseqno = ntohl(seqno);
- memcpy(buffer, &seqno, 4);
+ memcpy(buffer, &netseqno, 4);
buffer[4] = type;
+ memcpy(buffer + 5, data, len);
if(s->outstate) {
// If first handshake has finished, encrypt and HMAC
- if(!cipher_set_counter(s->outcipher, &seqno, sizeof seqno))
- return error(s, EINVAL, "Failed to set counter");
-
- if(!cipher_gcm_encrypt_start(s->outcipher, buffer + 4, 1, buffer + 4, NULL))
- return error(s, EINVAL, "Error encrypting record");
-
- if(!cipher_gcm_encrypt_finish(s->outcipher, data, len, buffer + 5, NULL))
- return error(s, EINVAL, "Error encrypting record");
-
+ chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 4, len + 1, buffer + 4, NULL);
return s->send_data(s->handle, type, buffer, len + 21UL);
} else {
// Otherwise send as plaintext
- memcpy(buffer + 5, data, len);
return s->send_data(s->handle, type, buffer, len + 5UL);
}
}
// 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);
char buffer[len + 19UL];
// Create header with sequence number, length and record type
- uint32_t seqno = htonl(s->outseqno++);
+ uint32_t seqno = s->outseqno++;
uint16_t netlen = htons(len);
memcpy(buffer, &netlen, 2);
buffer[2] = type;
+ memcpy(buffer + 3, data, len);
if(s->outstate) {
// If first handshake has finished, encrypt and HMAC
- if(!cipher_set_counter(s->outcipher, &seqno, 4))
- return error(s, EINVAL, "Failed to set counter");
-
- if(!cipher_gcm_encrypt_start(s->outcipher, buffer, 3, buffer, NULL))
- return error(s, EINVAL, "Error encrypting record");
-
- if(!cipher_gcm_encrypt_finish(s->outcipher, data, len, buffer + 3, NULL))
- return error(s, EINVAL, "Error encrypting record");
-
+ chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 2, len + 1, buffer + 2, NULL);
return s->send_data(s->handle, type, buffer, len + 19UL);
} else {
// Otherwise send as plaintext
- memcpy(buffer + 3, data, len);
return s->send_data(s->handle, type, buffer, len + 3UL);
}
}
// 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)
static bool generate_key_material(sptps_t *s, const char *shared, size_t len) {
// Initialise cipher and digest structures if necessary
if(!s->outstate) {
- s->incipher = cipher_open_by_name("aes-256-gcm");
- s->outcipher = cipher_open_by_name("aes-256-gcm");
+ 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 = cipher_keylength(s->incipher) + cipher_keylength(s->outcipher);
+ size_t keylen = 2 * CHACHA_POLY1305_KEYLEN;
s->key = realloc(s->key, keylen);
if(!s->key)
return error(s, EIO, "Invalid ACK record length");
if(s->initiator) {
- if(!cipher_set_counter_key(s->incipher, s->key))
+ if(!chacha_poly1305_set_key(s->incipher, s->key))
return error(s, EINVAL, "Failed to set counter");
} else {
- if(!cipher_set_counter_key(s->incipher, s->key + cipher_keylength(s->outcipher)))
+ if(!chacha_poly1305_set_key(s->incipher, s->key + CHACHA_POLY1305_KEYLEN))
return error(s, EINVAL, "Failed to set counter");
}
// TODO: only set new keys after ACK has been set/received
if(s->initiator) {
- if(!cipher_set_counter_key(s->outcipher, s->key + cipher_keylength(s->incipher)))
- return error(s, EINVAL, "Failed to set counter");
+ if(!chacha_poly1305_set_key(s->outcipher, s->key + CHACHA_POLY1305_KEYLEN))
+ return error(s, EINVAL, "Failed to set key");
} else {
- if(!cipher_set_counter_key(s->outcipher, s->key))
- return error(s, EINVAL, "Failed to set counter");
+ if(!chacha_poly1305_set_key(s->outcipher, s->key))
+ return error(s, EINVAL, "Failed to set key");
}
return true;
}
// Check datagram for valid HMAC
-bool sptps_verify_datagram(sptps_t *s, const char *data, size_t len) {
- if(!s->instate || len < 21)
- return error(s, EIO, "Received short packet");
+bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len) {
+ if (!s->instate)
+ return error(s, EIO, "SPTPS state not ready to verify this datagram");
- // TODO: just decrypt without updating the replay window
+ if(len < 21)
+ return error(s, EIO, "Received short packet in sptps_verify_datagram");
- return true;
+ uint32_t seqno;
+ memcpy(&seqno, data, 4);
+ seqno = ntohl(seqno);
+ // TODO: check whether seqno makes sense, to avoid CPU intensive decrypt
+
+ char buffer[len];
+ size_t outlen;
+ return chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen);
}
// Receive incoming data, datagram version.
-static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len) {
+static bool sptps_receive_data_datagram(sptps_t *s, const void *vdata, size_t len) {
+ const char *data = vdata;
+
if(len < (s->instate ? 21 : 5))
- return error(s, EIO, "Received short packet");
+ return error(s, EIO, "Received short packet in sptps_receive_data_datagram");
uint32_t seqno;
memcpy(&seqno, data, 4);
char buffer[len];
- if(!cipher_set_counter(s->incipher, data, sizeof seqno))
- return error(s, EINVAL, "Failed to set counter");
size_t outlen;
- if(!cipher_gcm_decrypt(s->incipher, data + 4, len - 4, buffer, &outlen))
+ if(!chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen))
return error(s, EIO, "Failed to decrypt and verify packet");
// Replay protection using a sliding window of configurable size.
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.
+ // TODO: Prevent packets that jump far ahead of the queue from causing many others to be dropped.
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);
// Mark the current packet as not being late.
s->late[(seqno / 8) % s->replaywin] &= ~(1 << seqno % 8);
- s->farfuture = 0;
}
if(seqno >= s->inseqno)
}
// 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) {
+bool sptps_receive_data(sptps_t *s, const void *data, size_t len) {
if(!s->state)
return error(s, EIO, "Invalid session state zero");
if(s->buflen < 2)
return true;
- // Update sequence number.
-
- uint32_t seqno = htonl(s->inseqno++);
-
- // Decrypt the length bytes
-
- if(s->instate) {
- if(!cipher_set_counter(s->incipher, &seqno, 4))
- return error(s, EINVAL, "Failed to set counter");
-
- if(!cipher_gcm_decrypt_start(s->incipher, s->inbuf, 2, &s->reclen, NULL))
- return error(s, EINVAL, "Failed to decrypt record");
- } else {
- memcpy(&s->reclen, s->inbuf, 2);
- }
+ // 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.
if(s->buflen < s->reclen + (s->instate ? 19UL : 3UL))
return true;
+ // Update sequence number.
+
+ uint32_t seqno = s->inseqno++;
+
// Check HMAC and decrypt.
if(s->instate) {
- if(!cipher_gcm_decrypt_finish(s->incipher, s->inbuf + 2UL, s->reclen + 17UL, s->inbuf + 2UL, NULL))
+ 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");
}
// 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) {
+ if(!s || !mykey || !hiskey || !label || !labellen || !send_data || !receive_record)
+ return error(s, EINVAL, "Invalid argument to sptps_start()");
+
// Initialise struct sptps
memset(s, 0, sizeof *s);
// Stop a SPTPS session.
bool sptps_stop(sptps_t *s) {
// Clean up any resources.
- cipher_close(s->incipher);
- cipher_close(s->outcipher);
- digest_close(s->indigest);
- digest_close(s->outdigest);
+ chacha_poly1305_exit(s->incipher);
+ chacha_poly1305_exit(s->outcipher);
ecdh_free(s->ecdh);
free(s->inbuf);
free(s->mykex);