X-Git-Url: https://www.tinc-vpn.org/git/browse?a=blobdiff_plain;f=src%2Fsptps.c;h=03a1e9aac468fd638d28cea2d4582c42060c8f18;hb=d03dc91e27b31851f87351c03cfc9a43c1b06458;hp=b907dadf919dc9b6cb4ebfe727f730d6e67d6d00;hpb=6bc8df3e010509f69af95d2cc14ec893def6f644;p=tinc diff --git a/src/sptps.c b/src/sptps.c index b907dadf..03a1e9aa 100644 --- a/src/sptps.c +++ b/src/sptps.c @@ -1,6 +1,6 @@ /* sptps.c -- Simple Peer-to-Peer Security - Copyright (C) 2011-2012 Guus Sliepen , + Copyright (C) 2011-2013 Guus Sliepen , 2010 Brandon L. Black This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #include "digest.h" #include "ecdh.h" #include "ecdsa.h" +#include "logger.h" #include "prf.h" #include "sptps.h" @@ -50,13 +51,36 @@ unsigned int sptps_replaywin = 16; Make sure ECC operations are fixed time (aka prevent side-channel attacks). */ +void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap) { +} + +void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap) { + vfprintf(stderr, format, ap); + fputc('\n', stderr); +} + +void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap) = sptps_log_stderr; + // Log an error message. -static bool error(sptps_t *s, int s_errno, const char *msg) { - fprintf(stderr, "SPTPS error: %s\n", msg); +static bool error(sptps_t *s, int s_errno, const char *format, ...) { + if(format) { + va_list ap; + va_start(ap, format); + sptps_log(s, s_errno, format, ap); + va_end(ap); + } + errno = s_errno; return false; } +static void warning(sptps_t *s, const char *format, ...) { + va_list ap; + va_start(ap, format); + sptps_log(s, 0, format, ap); + va_end(ap); +} + // 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]; @@ -74,11 +98,13 @@ static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const char *data 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)) + if(!cipher_set_counter(s->outcipher, &seqno, sizeof seqno)) + return false; + + 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)) + if(!digest_create(s->outdigest, buffer, len + 7UL, buffer + 7UL + len)) return false; return s->send_data(s->handle, type, buffer + 2, len + 21UL); @@ -107,10 +133,10 @@ static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_ if(s->outstate) { // If first handshake has finished, encrypt and HMAC - if(!cipher_counter_xor(&s->outcipher, buffer + 4, len + 3UL, buffer + 4)) + if(!cipher_counter_xor(s->outcipher, buffer + 4, len + 3UL, buffer + 4)) return false; - if(!digest_create(&s->outdigest, buffer, len + 7UL, buffer + 7UL + len)) + if(!digest_create(s->outdigest, buffer, len + 7UL, buffer + 7UL + len)) return false; return s->send_data(s->handle, type, buffer + 4, len + 19UL); @@ -151,7 +177,7 @@ static bool send_kex(sptps_t *s) { randomize(s->mykex + 1, 32); // Create a new ECDH public key. - if(!ecdh_generate_public(&s->ecdh, s->mykex + 1 + 32)) + if(!(s->ecdh = ecdh_generate_public(s->mykex + 1 + 32))) return false; return send_record_priv(s, SPTPS_HANDSHAKE, s->mykex, 1 + 32 + keylen); @@ -160,7 +186,7 @@ static bool send_kex(sptps_t *s) { // Send a SIGnature record, containing an ECDSA signature over both KEX records. static bool send_sig(sptps_t *s) { size_t keylen = ECDH_SIZE; - size_t siglen = ecdsa_size(&s->mykey); + size_t siglen = ecdsa_size(s->mykey); // 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]; @@ -172,7 +198,7 @@ static bool send_sig(sptps_t *s) { memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); // Sign the result. - if(!ecdsa_sign(&s->mykey, msg, sizeof msg, sig)) + if(!ecdsa_sign(s->mykey, msg, sizeof msg, sig)) return false; // Send the SIG exchange record. @@ -183,17 +209,16 @@ static bool send_sig(sptps_t *s) { static bool generate_key_material(sptps_t *s, const char *shared, size_t len) { // Initialise cipher and digest structures if necessary if(!s->outstate) { - bool result - = 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) + s->incipher = cipher_open_by_name("aes-256-ecb"); + s->outcipher = cipher_open_by_name("aes-256-ecb"); + s->indigest = digest_open_by_name("sha256", 16); + s->outdigest = digest_open_by_name("sha256", 16); + if(!s->incipher || !s->outcipher || !s->indigest || !s->outdigest) return false; } // Allocate memory for key material - size_t keylen = digest_keylength(&s->indigest) + digest_keylength(&s->outdigest) + cipher_keylength(&s->incipher) + cipher_keylength(&s->outcipher); + size_t keylen = digest_keylength(s->indigest) + digest_keylength(s->outdigest) + cipher_keylength(s->incipher) + cipher_keylength(s->outcipher); s->key = realloc(s->key, keylen); if(!s->key) @@ -209,7 +234,7 @@ static bool generate_key_material(sptps_t *s, const char *shared, size_t len) { memcpy(seed + 13, s->hiskex + 1, 32); memcpy(seed + 45, s->mykex + 1, 32); } - memcpy(seed + 78, s->label, s->labellen); + memcpy(seed + 77, s->label, s->labellen); // Use PRF to generate the key material if(!prf(shared, len, seed, s->labellen + 64 + 13, s->key, keylen)) @@ -230,14 +255,14 @@ static bool receive_ack(sptps_t *s, const char *data, uint16_t len) { 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)); + = 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)); + = 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; } @@ -272,7 +297,7 @@ static bool receive_kex(sptps_t *s, const char *data, uint16_t len) { // Receive a SIGnature record, verify it, if it passed, compute the shared secret and calculate the session keys. static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { size_t keylen = ECDH_SIZE; - size_t siglen = ecdsa_size(&s->hiskey); + size_t siglen = ecdsa_size(s->hiskey); // Verify length of KEX record. if(len != siglen) @@ -287,13 +312,14 @@ static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); // Verify signature. - if(!ecdsa_verify(&s->hiskey, msg, sizeof msg, data)) + if(!ecdsa_verify(s->hiskey, msg, sizeof msg, data)) return false; // Compute shared secret. char shared[ECDH_SHARED_SIZE]; - if(!ecdh_compute_shared(&s->ecdh, s->hiskex + 1 + 32, shared)) + if(!ecdh_compute_shared(s->ecdh, s->hiskex + 1 + 32, shared)) return false; + s->ecdh = NULL; // Generate key material from shared secret. if(!generate_key_material(s, shared, sizeof shared)) @@ -312,14 +338,14 @@ static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { // TODO: only set new keys after ACK has been set/received if(s->initiator) { bool result - = 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)); + = 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_counter_key(&s->outcipher, s->key) - && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest)); + = 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; } @@ -339,7 +365,6 @@ bool sptps_force_kex(sptps_t *s) { // Receive a handshake record. static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) { // Only a few states to deal with handshaking. - fprintf(stderr, "Received handshake message, current state %d\n", s->state); switch(s->state) { case SPTPS_SECONDARY_KEX: // We receive a secondary KEX request, first respond by sending our own. @@ -390,7 +415,7 @@ bool sptps_verify_datagram(sptps_t *s, const char *data, size_t len) { memcpy(buffer, &netlen, 2); memcpy(buffer + 2, data, len); - return digest_verify(&s->indigest, buffer, len - 14, buffer + len - 14); + return digest_verify(s->indigest, buffer, len - 14, buffer + len - 14); } // Receive incoming data, datagram version. @@ -403,10 +428,8 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len 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"); - } + if(seqno != s->inseqno) + return error(s, EIO, "Invalid packet seqno: %d != %d", seqno, s->inseqno); s->inseqno = seqno + 1; @@ -418,6 +441,17 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len return receive_handshake(s, data + 5, len - 5); } + // Check HMAC. + uint16_t netlen = htons(len - 21); + + char buffer[len + 23]; + + memcpy(buffer, &netlen, 2); + memcpy(buffer + 2, data, len); + + if(!digest_verify(s->indigest, buffer, len - 14, buffer + len - 14)) + return error(s, EIO, "Invalid HMAC"); + // Replay protection using a sliding window of configurable size. // s->inseqno is expected sequence number // seqno is received sequence number @@ -427,19 +461,16 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len 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) { - fprintf(stderr, "Packet is %d seqs in the future, dropped (%u)\n", seqno - s->inseqno, s->farfuture); - return false; - } + 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. - fprintf(stderr, "Lost %d packets\n", seqno - s->inseqno); + warning(s, "Lost %d packets\n", seqno - s->inseqno); memset(s->late, 0, s->replaywin); } else if (seqno < s->inseqno) { // If the sequence number is farther in the past than the bitmap goes, or if the packet was already received, drop it. - if((s->inseqno >= s->replaywin * 8 && seqno < s->inseqno - s->replaywin * 8) || !(s->late[(seqno / 8) % s->replaywin] & (1 << seqno % 8))) { - fprintf(stderr, "Received late or replayed packet, seqno %d, last received %d", seqno, s->inseqno); - return false; - } + if((s->inseqno >= s->replaywin * 8 && seqno < s->inseqno - s->replaywin * 8) || !(s->late[(seqno / 8) % s->replaywin] & (1 << seqno % 8))) + return error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno); } else { // We missed some packets. Mark them in the bitmap as being late. for(int i = s->inseqno; i < seqno; i++) @@ -455,21 +486,16 @@ static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len if(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); + if(!s->inseqno) + s->received = 0; + else + s->received++; + // Decrypt. 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)) + if(!cipher_set_counter(s->incipher, &seqno, sizeof seqno)) + return false; + if(!cipher_counter_xor(s->incipher, buffer + 6, len - 4, buffer + 6)) return false; // Append a NULL byte for safety. @@ -509,7 +535,7 @@ bool sptps_receive_data(sptps_t *s, const char *data, size_t len) { s->buflen += toread; len -= toread; data += toread; - + // Exit early if we don't have the full length. if(s->buflen < 6) return true; @@ -517,7 +543,7 @@ bool sptps_receive_data(sptps_t *s, const char *data, size_t len) { // Decrypt the length bytes if(s->instate) { - if(!cipher_counter_xor(&s->incipher, s->inbuf + 4, 2, &s->reclen)) + if(!cipher_counter_xor(s->incipher, s->inbuf + 4, 2, &s->reclen)) return false; } else { memcpy(&s->reclen, s->inbuf + 4, 2); @@ -555,10 +581,10 @@ bool sptps_receive_data(sptps_t *s, const char *data, size_t len) { // Check HMAC and decrypt. if(s->instate) { - if(!digest_verify(&s->indigest, s->inbuf, s->reclen + 7UL, s->inbuf + s->reclen + 7UL)) + 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)) + if(!cipher_counter_xor(s->incipher, s->inbuf + 6UL, s->reclen + 1UL, s->inbuf + 6UL)) return false; } @@ -586,7 +612,7 @@ bool sptps_receive_data(sptps_t *s, const char *data, 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 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); @@ -628,18 +654,17 @@ 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. - ecdh_free(&s->ecdh); + cipher_close(s->incipher); + cipher_close(s->outcipher); + digest_close(s->indigest); + digest_close(s->outdigest); + 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; free(s->late); - s->late = NULL; + memset(s, 0, sizeof *s); return true; }