#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
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).
*/
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 char *data, uint16_t len) {
+ char buffer[len + 23UL];
+
+ // Create header with sequence number, length and record type
+ uint32_t seqno = htonl(s->outseqno++);
+ uint16_t netlen = htons(len);
+
+ memcpy(buffer, &netlen, 2);
+ memcpy(buffer + 2, &seqno, 4);
+ buffer[6] = type;
+
+ // Add plaintext (TODO: avoid unnecessary copy)
+ memcpy(buffer + 7, data, len);
+
+ if(s->outstate) {
+ // If first handshake has finished, encrypt and HMAC
+ cipher_set_counter(&s->outcipher, &seqno, sizeof seqno);
+ if(!cipher_counter_xor(&s->outcipher, buffer + 6, len + 1UL, buffer + 6))
+ return false;
+
+ if(!digest_create(&s->outdigest, buffer, len + 7UL, buffer + 7UL + len))
+ return false;
+
+ return s->send_data(s->handle, buffer + 2, len + 21UL);
+ } else {
+ // Otherwise send as plaintext
+ return s->send_data(s->handle, buffer + 2, 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) {
- char plaintext[len + 23];
- char ciphertext[len + 19];
+ if(s->datagram)
+ return send_record_priv_datagram(s, type, data, len);
+
+ char buffer[len + 23UL];
// 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)
- memcpy(plaintext + 7, data, len);
+ memcpy(buffer + 7, data, len);
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;
- 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 s->send_data(s->handle, ciphertext, len + 19);
+ return s->send_data(s->handle, buffer + 4, len + 19UL);
} 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);
}
}
// Send an application record.
-bool 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 char *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)
size_t keylen = ECDH_SIZE;
// Make room for our KEX message, which we will keep around since send_sig() needs it.
+ if(s->mykex)
+ abort();
s->mykex = realloc(s->mykex, 1 + 32 + keylen);
if(!s->mykex)
return error(s, errno, strerror(errno));
size_t keylen = ECDH_SIZE;
size_t siglen = ecdsa_size(&s->mykey);
- // Concatenate both KEX messages, plus tag indicating if it is from the connection originator
- char msg[(1 + 32 + keylen) * 2 + 1];
+ // Concatenate both KEX messages, plus tag indicating if it is from the connection originator, plus label
+ char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen];
char sig[siglen];
msg[0] = s->initiator;
memcpy(msg + 1, s->mykex, 1 + 32 + keylen);
- memcpy(msg + 2 + 32 + keylen, s->hiskex, 1 + 32 + keylen);
+ memcpy(msg + 1 + 33 + keylen, s->hiskex, 1 + 32 + keylen);
+ memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen);
// Sign the result.
if(!ecdsa_sign(&s->mykey, msg, sizeof msg, sig))
// 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)
// 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");
- // TODO: set cipher/digest keys
- return error(s, ENOSYS, "receive_ack() not completely implemented yet");
+ 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;
+ }
+
+ free(s->key);
+ s->key = NULL;
+ s->instate = true;
+
+ return true;
}
// Receive a Key EXchange record, respond by sending a SIG record.
// Ignore version number for now.
// Make a copy of the KEX message, send_sig() and receive_sig() need it
+ if(s->hiskex)
+ abort();
s->hiskex = realloc(s->hiskex, len);
if(!s->hiskex)
return error(s, errno, strerror(errno));
return error(s, EIO, "Invalid KEX record length");
// Concatenate both KEX messages, plus tag indicating if it is from the connection originator
- char msg[(1 + 32 + keylen) * 2 + 1];
+ char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen];
msg[0] = !s->initiator;
memcpy(msg + 1, s->hiskex, 1 + 32 + keylen);
- memcpy(msg + 2 + 32 + keylen, s->mykex, 1 + 32 + keylen);
+ memcpy(msg + 1 + 33 + keylen, s->mykex, 1 + 32 + keylen);
+ memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen);
// Verify signature.
if(!ecdsa_verify(&s->hiskey, msg, sizeof msg, data))
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(s->outstate && !send_ack(s))
+ return false;
// 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
- = 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;
- s->instate = true;
-
return true;
}
// Force another Key EXchange (for testing purposes).
-bool force_kex(sptps_t *s) {
+bool sptps_force_kex(sptps_t *s) {
if(!s->outstate || s->state != SPTPS_SECONDARY_KEX)
return error(s, EINVAL, "Cannot force KEX in current state");
// If we already sent our secondary public ECDH key, we expect the peer to send his.
if(!receive_sig(s, data, len))
return false;
- // s->state = SPTPS_ACK;
- s->state = SPTPS_SECONDARY_KEX;
+ if(s->outstate)
+ s->state = SPTPS_ACK;
+ else {
+ s->outstate = true;
+ if(!receive_ack(s, NULL, 0))
+ return false;
+ s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0);
+ s->state = SPTPS_SECONDARY_KEX;
+ }
+
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?
}
}
+// Receive incoming data, datagram version.
+static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len) {
+ if(len < (s->instate ? 21 : 5))
+ return error(s, EIO, "Received short packet");
+
+ uint32_t seqno;
+ memcpy(&seqno, data, 4);
+ seqno = ntohl(seqno);
+
+ if(!s->instate) {
+ if(seqno != s->inseqno) {
+ fprintf(stderr, "Received invalid packet seqno: %d != %d\n", seqno, s->inseqno);
+ return error(s, EIO, "Invalid packet seqno");
+ }
+
+ s->inseqno = seqno + 1;
+
+ uint8_t type = data[4];
+
+ if(type != SPTPS_HANDSHAKE)
+ return error(s, EIO, "Application record received before handshake finished");
+
+ return receive_handshake(s, data + 5, len - 5);
+ }
+
+ if(seqno < s->inseqno) {
+ fprintf(stderr, "Received late or replayed packet: %d < %d\n", seqno, s->inseqno);
+ return true;
+ }
+
+ if(seqno > s->inseqno)
+ fprintf(stderr, "Missed %d packets\n", seqno - s->inseqno);
+
+ s->inseqno = seqno + 1;
+
+ uint16_t netlen = htons(len - 21);
+
+ char buffer[len + 23];
+
+ memcpy(buffer, &netlen, 2);
+ memcpy(buffer + 2, data, len);
+
+ memcpy(&seqno, buffer + 2, 4);
+
+ // Check HMAC and decrypt.
+ if(!digest_verify(&s->indigest, buffer, len - 14, buffer + len - 14))
+ return error(s, EIO, "Invalid HMAC");
+
+ cipher_set_counter(&s->incipher, &seqno, sizeof seqno);
+ if(!cipher_counter_xor(&s->incipher, buffer + 6, len - 4, buffer + 6))
+ return false;
+
+ // Append a NULL byte for safety.
+ buffer[len - 14] = 0;
+
+ uint8_t type = buffer[6];
+
+ 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 + 7, len - 21))
+ return false;
+ } else {
+ return error(s, EIO, "Invalid record type");
+ }
+
+ return true;
+}
// Receive incoming data. Check if it contains a complete record, if so, handle it.
-bool receive_data(sptps_t *s, const char *data, size_t len) {
+bool sptps_receive_data(sptps_t *s, const char *data, size_t len) {
+ if(s->datagram)
+ return sptps_receive_data_datagram(s, data, len);
+
while(len) {
// First read the 2 length bytes.
if(s->buflen < 6) {
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;
-
+
// 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.
- 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));
}
// 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(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.
- if(s->buflen < reclen + (s->instate ? 23UL : 7UL))
+ if(s->buflen < s->reclen + (s->instate ? 23UL : 7UL))
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];
- // Handle record.
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) {
- 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");
}
// Start a SPTPS session.
-bool start_sptps(sptps_t *s, void *handle, bool initiator, 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 char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) {
// Initialise struct sptps
memset(s, 0, sizeof *s);
s->handle = handle;
s->initiator = initiator;
+ s->datagram = datagram;
s->mykey = mykey;
s->hiskey = hiskey;
if(!s->label)
return error(s, errno, strerror(errno));
- s->inbuf = malloc(7);
- if(!s->inbuf)
- return error(s, errno, strerror(errno));
- s->buflen = 4;
- memset(s->inbuf, 0, 4);
+ if(!datagram) {
+ s->inbuf = malloc(7);
+ if(!s->inbuf)
+ return error(s, errno, strerror(errno));
+ s->buflen = 4;
+ memset(s->inbuf, 0, 4);
+ }
memcpy(s->label, label, labellen);
s->labellen = labellen;
}
// Stop a SPTPS session.
-bool stop_sptps(sptps_t *s) {
+bool sptps_stop(sptps_t *s) {
// Clean up any resources.
ecdh_free(&s->ecdh);
free(s->inbuf);
+ s->inbuf = NULL;
free(s->mykex);
+ s->mykex = NULL;
free(s->hiskex);
+ s->hiskex = NULL;
free(s->key);
+ s->key = NULL;
free(s->label);
+ s->label = NULL;
return true;
}