X-Git-Url: https://tinc-vpn.org/git/browse?a=blobdiff_plain;f=src%2Fsptps.c;h=ff7c4168e0410b3564ceb32e74fce0da780b4f4a;hb=248d300f1be0d5f2aae39202041699ab2b46c56b;hp=6668763a939e74c5fbeff774926893f4397184ab;hpb=efd21e232dced3225f119aeb7a585ebf55b7cf77;p=tinc diff --git a/src/sptps.c b/src/sptps.c index 6668763a..ff7c4168 100644 --- a/src/sptps.c +++ b/src/sptps.c @@ -27,9 +27,6 @@ #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 @@ -57,10 +54,42 @@ static bool error(sptps_t *s, int s_errno, const char *msg) { 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) { + if(s->datagram) + return send_record_priv_datagram(s, type, data, len); + char buffer[len + 23UL]; - //char ciphertext[len + 19]; // Create header with sequence number, length and record type uint32_t seqno = htonl(s->outseqno++); @@ -89,7 +118,7 @@ static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_ } // 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) @@ -106,6 +135,8 @@ static bool send_kex(sptps_t *s) { 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)); @@ -128,13 +159,14 @@ static bool send_sig(sptps_t *s) { 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)) @@ -223,6 +255,8 @@ static bool receive_kex(sptps_t *s, const char *data, uint16_t len) { // 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)); @@ -242,11 +276,12 @@ static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { 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)) @@ -268,7 +303,7 @@ static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { s->hiskex = NULL; // Send cipher change record - if(!send_ack(s)) + if(s->outstate && !send_ack(s)) return false; // TODO: only set new keys after ACK has been set/received @@ -286,13 +321,11 @@ static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { return false; } - s->outstate = 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"); @@ -319,13 +352,22 @@ 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(!receive_sig(s, data, len)) return false; - // s->state = SPTPS_ACK; - s->state = SPTPS_ACK; + 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? @@ -334,8 +376,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. -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) { @@ -425,12 +538,13 @@ bool receive_data(sptps_t *s, const char *data, size_t len) { } // 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; @@ -438,11 +552,13 @@ bool start_sptps(sptps_t *s, void *handle, bool initiator, ecdsa_t mykey, ecdsa_ 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; @@ -456,13 +572,18 @@ bool start_sptps(sptps_t *s, void *handle, bool initiator, ecdsa_t mykey, ecdsa_ } // 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; }