Use datagram SPTPS for packet exchange between nodes.
When two nodes which support SPTPS want to send packets to each other, they now always use SPTPS. The node initiating the SPTPS session send the first SPTPS packet via an extended REQ_KEY messages. All other handshake messages are sent using ANS_KEY messages. This ensures that intermediate nodes using an older version of tinc can still help with NAT traversal. After the authentication phase is over, SPTPS packets are sent via UDP, or are encapsulated in extended REQ_KEY messages instead of PACKET messages.
This commit is contained in:
parent
248d300f1b
commit
153abaa4d9
12 changed files with 213 additions and 134 deletions
|
|
@ -24,13 +24,13 @@
|
|||
#include "cipher.h"
|
||||
#include "connection.h"
|
||||
#include "crypto.h"
|
||||
#include "ecdh.h"
|
||||
#include "logger.h"
|
||||
#include "net.h"
|
||||
#include "netutl.h"
|
||||
#include "node.h"
|
||||
#include "prf.h"
|
||||
#include "protocol.h"
|
||||
#include "sptps.h"
|
||||
#include "utils.h"
|
||||
#include "xalloc.h"
|
||||
|
||||
|
|
@ -46,8 +46,20 @@ void send_key_changed(void) {
|
|||
|
||||
for(node = connection_tree->head; node; node = node->next) {
|
||||
c = node->data;
|
||||
if(c->status.active && c->node && c->node->status.reachable)
|
||||
send_ans_key(c->node);
|
||||
if(c->status.active && c->node && c->node->status.reachable) {
|
||||
if(!experimental || OPTION_VERSION(c->node->options) < 2)
|
||||
send_ans_key(c->node);
|
||||
}
|
||||
}
|
||||
|
||||
/* Force key exchange for connections using SPTPS */
|
||||
|
||||
if(experimental) {
|
||||
for(node = node_tree->head; node; node = node->next) {
|
||||
node_t *n = node->data;
|
||||
if(n->status.reachable && n->status.validkey && OPTION_VERSION(n->options) >= 2)
|
||||
sptps_force_kex(&n->sptps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,8 +84,10 @@ bool key_changed_h(connection_t *c, const char *request) {
|
|||
return true;
|
||||
}
|
||||
|
||||
n->status.validkey = false;
|
||||
n->last_req_key = 0;
|
||||
if(OPTION_VERSION(n->options) < 2) {
|
||||
n->status.validkey = false;
|
||||
n->last_req_key = 0;
|
||||
}
|
||||
|
||||
/* Tell the others */
|
||||
|
||||
|
|
@ -83,11 +97,26 @@ bool key_changed_h(connection_t *c, const char *request) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool send_initial_sptps_data(void *handle, uint8_t type, const char *data, size_t len) {
|
||||
node_t *to = handle;
|
||||
to->sptps.send_data = send_sptps_data;
|
||||
char buf[len * 4 / 3 + 5];
|
||||
b64encode(data, buf, len);
|
||||
return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, to->name, REQ_KEY, buf);
|
||||
}
|
||||
|
||||
bool send_req_key(node_t *to) {
|
||||
if(experimental && OPTION_VERSION(to->options) >= 2) {
|
||||
if(!node_read_ecdsa_public_key(to))
|
||||
if(!node_read_ecdsa_public_key(to)) {
|
||||
logger(DEBUG_ALWAYS, LOG_DEBUG, "No ECDSA key known for %s (%s)", to->name, to->hostname);
|
||||
send_request(to->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, to->name, REQ_PUBKEY);
|
||||
return true;
|
||||
}
|
||||
char label[25 + strlen(myself->name) + strlen(to->name)];
|
||||
snprintf(label, sizeof label, "tinc UDP key expansion %s %s", myself->name, to->name);
|
||||
return sptps_start(&to->sptps, to, true, true, myself->connection->ecdsa, to->ecdsa, label, sizeof label, send_initial_sptps_data, receive_sptps_record);
|
||||
}
|
||||
|
||||
return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name);
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +137,7 @@ static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, in
|
|||
return true;
|
||||
}
|
||||
|
||||
char pubkey[4096];
|
||||
char pubkey[MAX_STRING_SIZE];
|
||||
if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, pubkey) != 1 || !ecdsa_set_base64_public_key(&from->ecdsa, pubkey)) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_PUBKEY", from->name, from->hostname, "invalid pubkey");
|
||||
return true;
|
||||
|
|
@ -119,6 +148,30 @@ static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, in
|
|||
return true;
|
||||
}
|
||||
|
||||
case REQ_KEY: {
|
||||
if(!node_read_ecdsa_public_key(from)) {
|
||||
logger(DEBUG_ALWAYS, LOG_DEBUG, "No ECDSA key known for %s (%s)", from->name, from->hostname);
|
||||
send_request(from->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, from->name, REQ_PUBKEY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
case REQ_SPTPS: {
|
||||
char buf[MAX_STRING_SIZE];
|
||||
if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", from->name, from->hostname, "invalid SPTPS data");
|
||||
return true;
|
||||
}
|
||||
int len = b64decode(buf, buf, strlen(buf));
|
||||
|
||||
|
||||
char label[25 + strlen(from->name) + strlen(myself->name)];
|
||||
snprintf(label, sizeof label, "tinc UDP key expansion %s %s", from->name, myself->name);
|
||||
sptps_start(&from->sptps, from, false, true, myself->connection->ecdsa, from->ecdsa, label, sizeof label, send_sptps_data, receive_sptps_record);
|
||||
sptps_receive_data(&from->sptps, buf, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Unknown extended REQ_KEY request from %s (%s): %s", from->name, from->hostname, request);
|
||||
return true;
|
||||
|
|
@ -181,31 +234,9 @@ bool req_key_h(connection_t *c, const char *request) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool send_ans_key_ecdh(node_t *to) {
|
||||
int siglen = ecdsa_size(&myself->connection->ecdsa);
|
||||
char key[(ECDH_SIZE + siglen) * 2 + 1];
|
||||
|
||||
if(!ecdh_generate_public(&to->ecdh, key))
|
||||
return false;
|
||||
|
||||
if(!ecdsa_sign(&myself->connection->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE))
|
||||
return false;
|
||||
|
||||
b64encode(key, key, ECDH_SIZE + siglen);
|
||||
|
||||
int result = send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY,
|
||||
myself->name, to->name, key,
|
||||
cipher_get_nid(&myself->incipher),
|
||||
digest_get_nid(&myself->indigest),
|
||||
(int)digest_length(&myself->indigest),
|
||||
myself->incompression);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool send_ans_key(node_t *to) {
|
||||
if(experimental && OPTION_VERSION(to->options) >= 2)
|
||||
return send_ans_key_ecdh(to);
|
||||
abort();
|
||||
|
||||
size_t keylen = cipher_keylength(&myself->incipher);
|
||||
char key[keylen * 2 + 1];
|
||||
|
|
@ -296,6 +327,29 @@ bool ans_key_h(connection_t *c, const char *request) {
|
|||
return send_request(to->nexthop->connection, "%s", request);
|
||||
}
|
||||
|
||||
/* SPTPS or old-style key exchange? */
|
||||
|
||||
if(experimental && OPTION_VERSION(from->options) >= 2) {
|
||||
char buf[strlen(key)];
|
||||
int len = b64decode(key, buf, strlen(key));
|
||||
|
||||
if(!sptps_receive_data(&from->sptps, buf, len))
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Error processing SPTPS data from %s (%s)", from->name, from->hostname);
|
||||
|
||||
if(from->status.validkey) {
|
||||
if(*address && *port) {
|
||||
logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port);
|
||||
sockaddr_t sa = str2sockaddr(address, port);
|
||||
update_node_udp(from, &sa);
|
||||
}
|
||||
|
||||
if(from->options & OPTION_PMTU_DISCOVERY)
|
||||
send_mtu_probe(from);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check and lookup cipher and digest algorithms */
|
||||
|
||||
if(!cipher_open_by_nid(&from->outcipher, cipher)) {
|
||||
|
|
@ -320,100 +374,20 @@ bool ans_key_h(connection_t *c, const char *request) {
|
|||
|
||||
from->outcompression = compression;
|
||||
|
||||
/* ECDH or old-style key exchange? */
|
||||
|
||||
if(experimental && OPTION_VERSION(from->options) >= 2) {
|
||||
/* Check if we already have an ECDSA public key for this node. */
|
||||
/* Process key */
|
||||
|
||||
if(!node_read_ecdsa_public_key(from)) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "No ECDSA public key known for %s (%s), cannot verify ECDH key exchange!", from->name, from->hostname);
|
||||
return true;
|
||||
}
|
||||
keylen = hex2bin(key, key, sizeof key);
|
||||
|
||||
int siglen = ecdsa_size(&from->ecdsa);
|
||||
int keylen = b64decode(key, key, sizeof key);
|
||||
|
||||
if(keylen != ECDH_SIZE + siglen) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength! %d != %d", from->name, from->hostname, keylen, ECDH_SIZE + siglen);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ECDH_SHARED_SIZE < cipher_keylength(&from->outcipher)) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "ECDH key too short for cipher of %s!", from->name);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!ecdsa_verify(&from->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", from->name, from->hostname, "invalid ECDSA signature");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!from->ecdh) {
|
||||
if(!send_ans_key_ecdh(from))
|
||||
return true;
|
||||
}
|
||||
|
||||
char shared[ECDH_SHARED_SIZE * 2 + 1];
|
||||
|
||||
if(!ecdh_compute_shared(&from->ecdh, key, shared))
|
||||
return true;
|
||||
|
||||
/* Update our crypto end */
|
||||
|
||||
size_t mykeylen = cipher_keylength(&myself->incipher);
|
||||
size_t hiskeylen = cipher_keylength(&from->outcipher);
|
||||
|
||||
char *mykey;
|
||||
char *hiskey;
|
||||
char *seed;
|
||||
|
||||
if(strcmp(myself->name, from->name) < 0) {
|
||||
mykey = key;
|
||||
hiskey = key + mykeylen * 2;
|
||||
xasprintf(&seed, "tinc UDP key expansion %s %s", myself->name, from->name);
|
||||
} else {
|
||||
mykey = key + hiskeylen * 2;
|
||||
hiskey = key;
|
||||
xasprintf(&seed, "tinc UDP key expansion %s %s", from->name, myself->name);
|
||||
}
|
||||
|
||||
if(!prf(shared, ECDH_SHARED_SIZE, seed, strlen(seed), key, hiskeylen * 2 + mykeylen * 2))
|
||||
return true;
|
||||
|
||||
free(seed);
|
||||
|
||||
cipher_open_by_nid(&from->incipher, cipher_get_nid(&myself->incipher));
|
||||
digest_open_by_nid(&from->indigest, digest_get_nid(&myself->indigest), digest_length(&myself->indigest));
|
||||
from->incompression = myself->incompression;
|
||||
|
||||
cipher_set_key(&from->incipher, mykey, false);
|
||||
digest_set_key(&from->indigest, mykey + mykeylen, mykeylen);
|
||||
|
||||
cipher_set_key(&from->outcipher, hiskey, true);
|
||||
digest_set_key(&from->outdigest, hiskey + hiskeylen, hiskeylen);
|
||||
|
||||
// Reset sequence number and late packet window
|
||||
mykeyused = true;
|
||||
from->received_seqno = 0;
|
||||
if(replaywin)
|
||||
memset(from->late, 0, replaywin);
|
||||
|
||||
if(strcmp(myself->name, from->name) < 0)
|
||||
memmove(key, key + mykeylen * 2, hiskeylen * 2);
|
||||
} else {
|
||||
keylen = hex2bin(key, key, sizeof key);
|
||||
|
||||
if(keylen != cipher_keylength(&from->outcipher)) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Update our copy of the origin's packet key */
|
||||
|
||||
cipher_set_key(&from->outcipher, key, true);
|
||||
digest_set_key(&from->outdigest, key, keylen);
|
||||
if(keylen != cipher_keylength(&from->outcipher)) {
|
||||
logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Update our copy of the origin's packet key */
|
||||
|
||||
cipher_set_key(&from->outcipher, key, true);
|
||||
digest_set_key(&from->outdigest, key, keylen);
|
||||
|
||||
from->status.validkey = true;
|
||||
from->sent_seqno = 0;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue