Add datagram mode to the SPTPS protocol.
* Everything is identical except the headers of the records. * Instead of sending explicit message length and having an implicit sequence number, datagram mode has an implicit message length and an explicit sequence number. * The sequence number is used to set the most significant bytes of the counter.
This commit is contained in:
parent
03e06fd43a
commit
3a4fe104a0
4 changed files with 130 additions and 12 deletions
|
@ -144,7 +144,7 @@ bool id_h(connection_t *c, char *request) {
|
||||||
else
|
else
|
||||||
snprintf(label, sizeof label, "tinc TCP key expansion %s %s", c->name, myself->name);
|
snprintf(label, sizeof label, "tinc TCP key expansion %s %s", c->name, myself->name);
|
||||||
|
|
||||||
return sptps_start(&c->sptps, c, c->outgoing, myself->connection->ecdsa, c->ecdsa, label, sizeof label, send_meta_sptps, receive_meta_sptps);
|
return sptps_start(&c->sptps, c, c->outgoing, false, myself->connection->ecdsa, c->ecdsa, label, sizeof label, send_meta_sptps, receive_meta_sptps);
|
||||||
} else {
|
} else {
|
||||||
return send_metakey(c);
|
return send_metakey(c);
|
||||||
}
|
}
|
||||||
|
|
124
src/sptps.c
124
src/sptps.c
|
@ -54,8 +54,41 @@ static bool error(sptps_t *s, int s_errno, const char *msg) {
|
||||||
return false;
|
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).
|
// 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 char *data, uint16_t len) {
|
||||||
|
if(s->datagram)
|
||||||
|
return send_record_priv_datagram(s, type, data, len);
|
||||||
|
|
||||||
char buffer[len + 23UL];
|
char buffer[len + 23UL];
|
||||||
|
|
||||||
// Create header with sequence number, length and record type
|
// Create header with sequence number, length and record type
|
||||||
|
@ -102,6 +135,8 @@ static bool send_kex(sptps_t *s) {
|
||||||
size_t keylen = ECDH_SIZE;
|
size_t keylen = ECDH_SIZE;
|
||||||
|
|
||||||
// Make room for our KEX message, which we will keep around since send_sig() needs it.
|
// 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);
|
s->mykex = realloc(s->mykex, 1 + 32 + keylen);
|
||||||
if(!s->mykex)
|
if(!s->mykex)
|
||||||
return error(s, errno, strerror(errno));
|
return error(s, errno, strerror(errno));
|
||||||
|
@ -219,6 +254,8 @@ static bool receive_kex(sptps_t *s, const char *data, uint16_t len) {
|
||||||
// Ignore version number for now.
|
// Ignore version number for now.
|
||||||
|
|
||||||
// Make a copy of the KEX message, send_sig() and receive_sig() need it
|
// Make a copy of the KEX message, send_sig() and receive_sig() need it
|
||||||
|
if(s->hiskex)
|
||||||
|
abort();
|
||||||
s->hiskex = realloc(s->hiskex, len);
|
s->hiskex = realloc(s->hiskex, len);
|
||||||
if(!s->hiskex)
|
if(!s->hiskex)
|
||||||
return error(s, errno, strerror(errno));
|
return error(s, errno, strerror(errno));
|
||||||
|
@ -315,7 +352,6 @@ static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) {
|
||||||
// If we already sent our secondary public ECDH key, we expect the peer to send his.
|
// If we already sent our secondary public ECDH key, we expect the peer to send his.
|
||||||
if(!receive_sig(s, data, len))
|
if(!receive_sig(s, data, len))
|
||||||
return false;
|
return false;
|
||||||
// s->state = SPTPS_ACK;
|
|
||||||
s->state = SPTPS_ACK;
|
s->state = SPTPS_ACK;
|
||||||
return true;
|
return true;
|
||||||
case SPTPS_ACK:
|
case SPTPS_ACK:
|
||||||
|
@ -331,8 +367,79 @@ static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
// 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 char *data, size_t len) {
|
||||||
|
if(s->datagram)
|
||||||
|
return sptps_receive_data_datagram(s, data, len);
|
||||||
|
|
||||||
while(len) {
|
while(len) {
|
||||||
// First read the 2 length bytes.
|
// First read the 2 length bytes.
|
||||||
if(s->buflen < 6) {
|
if(s->buflen < 6) {
|
||||||
|
@ -422,12 +529,13 @@ bool sptps_receive_data(sptps_t *s, const char *data, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a SPTPS session.
|
// Start a SPTPS session.
|
||||||
bool sptps_start(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
|
// Initialise struct sptps
|
||||||
memset(s, 0, sizeof *s);
|
memset(s, 0, sizeof *s);
|
||||||
|
|
||||||
s->handle = handle;
|
s->handle = handle;
|
||||||
s->initiator = initiator;
|
s->initiator = initiator;
|
||||||
|
s->datagram = datagram;
|
||||||
s->mykey = mykey;
|
s->mykey = mykey;
|
||||||
s->hiskey = hiskey;
|
s->hiskey = hiskey;
|
||||||
|
|
||||||
|
@ -435,11 +543,13 @@ bool sptps_start(sptps_t *s, void *handle, bool initiator, ecdsa_t mykey, ecdsa_
|
||||||
if(!s->label)
|
if(!s->label)
|
||||||
return error(s, errno, strerror(errno));
|
return error(s, errno, strerror(errno));
|
||||||
|
|
||||||
s->inbuf = malloc(7);
|
if(!datagram) {
|
||||||
if(!s->inbuf)
|
s->inbuf = malloc(7);
|
||||||
return error(s, errno, strerror(errno));
|
if(!s->inbuf)
|
||||||
s->buflen = 4;
|
return error(s, errno, strerror(errno));
|
||||||
memset(s->inbuf, 0, 4);
|
s->buflen = 4;
|
||||||
|
memset(s->inbuf, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(s->label, label, labellen);
|
memcpy(s->label, label, labellen);
|
||||||
s->labellen = labellen;
|
s->labellen = labellen;
|
||||||
|
|
|
@ -45,6 +45,7 @@ typedef bool (*receive_record_t)(void *handle, uint8_t type, const char *data, u
|
||||||
|
|
||||||
typedef struct sptps {
|
typedef struct sptps {
|
||||||
bool initiator;
|
bool initiator;
|
||||||
|
bool datagram;
|
||||||
int state;
|
int state;
|
||||||
|
|
||||||
char *inbuf;
|
char *inbuf;
|
||||||
|
@ -76,7 +77,7 @@ typedef struct sptps {
|
||||||
receive_record_t receive_record;
|
receive_record_t receive_record;
|
||||||
} sptps_t;
|
} sptps_t;
|
||||||
|
|
||||||
extern bool sptps_start(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);
|
extern 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);
|
||||||
extern bool sptps_stop(sptps_t *s);
|
extern bool sptps_stop(sptps_t *s);
|
||||||
extern bool sptps_send_record(sptps_t *s, uint8_t type, const char *data, uint16_t len);
|
extern bool sptps_send_record(sptps_t *s, uint8_t type, const char *data, uint16_t len);
|
||||||
extern bool sptps_receive_data(sptps_t *s, const char *data, size_t len);
|
extern bool sptps_receive_data(sptps_t *s, const char *data, size_t len);
|
||||||
|
|
|
@ -51,9 +51,16 @@ static bool receive_record(void *handle, uint8_t type, const char *data, uint16_
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
bool initiator = false;
|
bool initiator = false;
|
||||||
|
bool datagram = false;
|
||||||
|
|
||||||
if(argc < 3) {
|
if(argc > 1 && !strcmp(argv[1], "-d")) {
|
||||||
fprintf(stderr, "Usage: %s my_ecdsa_key_file his_ecdsa_key_file [host] port\n", argv[0]);
|
datagram = true;
|
||||||
|
argc--;
|
||||||
|
argv++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argc < 4) {
|
||||||
|
fprintf(stderr, "Usage: %s [-d] my_ecdsa_key_file his_ecdsa_key_file [host] port\n", argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +130,7 @@ int main(int argc, char *argv[]) {
|
||||||
fprintf(stderr, "Keys loaded\n");
|
fprintf(stderr, "Keys loaded\n");
|
||||||
|
|
||||||
sptps_t s;
|
sptps_t s;
|
||||||
if(!sptps_start(&s, &sock, initiator, mykey, hiskey, "sptps_test", 10, send_data, receive_record))
|
if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
|
|
Loading…
Reference in a new issue