X-Git-Url: https://tinc-vpn.org/git/browse?a=blobdiff_plain;f=src%2Fsptps.c;h=bdbfb89e61d7de3a64579f67b93c0b4582b5b1d9;hb=d7bf63c63ab397cf3e5ca4a065922364925788e7;hp=5088d65d388e71dfe8ab8ee347b7c40ed9ad20b1;hpb=65d6f023c46ac3a087f59b60762f87c869783f21;p=tinc diff --git a/src/sptps.c b/src/sptps.c index 5088d65d..bdbfb89e 100644 --- a/src/sptps.c +++ b/src/sptps.c @@ -54,8 +54,41 @@ 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]; // Create header with sequence number, length and record type @@ -85,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) @@ -102,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)); @@ -124,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)) @@ -219,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)); @@ -238,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)) @@ -264,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 @@ -282,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"); @@ -315,8 +352,16 @@ 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. @@ -331,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) { @@ -422,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; @@ -435,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; @@ -453,7 +572,7 @@ 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);