From 8cb4dbb04af15e95e9a302670a4c6fd21e0ebfd6 Mon Sep 17 00:00:00 2001 From: Kirill Isakov Date: Thu, 14 Apr 2022 21:44:49 +0600 Subject: [PATCH] connection_t: allocate legacy context on first use Since the new protocol is preferred if available, if both sides of the connection are running modern versions of tinc, the old protocol may not be used at all. --- src/connection.c | 65 ++++++++++++++++++++++++--- src/connection.h | 29 +++++++++--- src/fsck.c | 4 +- src/keys.c | 12 ++--- src/keys.h | 2 +- src/meta.c | 16 +++---- src/net_setup.c | 6 ++- src/net_socket.c | 8 ---- src/protocol_auth.c | 107 ++++++++++++++++++++++---------------------- 9 files changed, 156 insertions(+), 93 deletions(-) diff --git a/src/connection.c b/src/connection.c index 0de51095..16878ea8 100644 --- a/src/connection.c +++ b/src/connection.c @@ -57,17 +57,72 @@ connection_t *new_connection(void) { return xzalloc(sizeof(connection_t)); } +#ifndef DISABLE_LEGACY +bool init_crypto_by_nid(legacy_crypto_t *c, int cipher, int digest) { + if(!cipher_open_by_nid(&c->cipher, cipher)) { + return false; + } + + if(!digest_open_by_nid(&c->digest, digest, DIGEST_ALGO_SIZE)) { + cipher_close(&c->cipher); + return false; + } + + c->budget = cipher_budget(&c->cipher); + return true; +} + +bool init_crypto_by_name(legacy_crypto_t *c, const char *cipher, const char *digest) { + if(!cipher_open_by_name(&c->cipher, cipher)) { + return false; + } + + if(!digest_open_by_name(&c->digest, digest, DIGEST_ALGO_SIZE)) { + cipher_close(&c->cipher); + return false; + } + + c->budget = cipher_budget(&c->cipher); + return true; +} + +bool decrease_budget(legacy_crypto_t *c, size_t bytes) { + if(bytes > c->budget) { + return false; + } else { + c->budget -= bytes; + return true; + } +} + +static void close_legacy_crypto(legacy_crypto_t *c) { + cipher_close(&c->cipher); + digest_close(&c->digest); +} + +legacy_ctx_t *new_legacy_ctx(rsa_t *rsa) { + legacy_ctx_t *ctx = xzalloc(sizeof(legacy_ctx_t)); + ctx->rsa = rsa; + return ctx; +} + +void free_legacy_ctx(legacy_ctx_t *ctx) { + if(ctx) { + close_legacy_crypto(&ctx->in); + close_legacy_crypto(&ctx->out); + rsa_free(ctx->rsa); + free(ctx); + } +} +#endif + void free_connection(connection_t *c) { if(!c) { return; } #ifndef DISABLE_LEGACY - cipher_close(&c->incipher); - digest_close(&c->indigest); - cipher_close(&c->outcipher); - digest_close(&c->outdigest); - rsa_free(c->rsa); + free_legacy_ctx(c->legacy); #endif sptps_stop(&c->sptps); diff --git a/src/connection.h b/src/connection.h index 3965127c..a176988f 100644 --- a/src/connection.h +++ b/src/connection.h @@ -60,6 +60,27 @@ typedef union connection_status_t { #include "net.h" #include "node.h" +#ifndef DISABLE_LEGACY +typedef struct legacy_crypto_t { + cipher_t cipher; + digest_t digest; + uint64_t budget; +} legacy_crypto_t; + +bool init_crypto_by_nid(legacy_crypto_t *c, int cipher, int digest) ATTR_WARN_UNUSED; +bool init_crypto_by_name(legacy_crypto_t *c, const char *cipher, const char *digest) ATTR_WARN_UNUSED; +bool decrease_budget(legacy_crypto_t *c, size_t bytes) ATTR_WARN_UNUSED; + +typedef struct legacy_ctx_t { + rsa_t *rsa; /* his public RSA key or my private RSA key */ + legacy_crypto_t in; /* cipher/digest he will use to send data to us */ + legacy_crypto_t out; /* cipher/digest we will use to send data to him */ +} legacy_ctx_t; + +legacy_ctx_t *new_legacy_ctx(rsa_t *rsa) ATTR_MALLOC; +void free_legacy_ctx(legacy_ctx_t *ctx); +#endif + typedef struct connection_t { char *name; /* name he claims to have */ char *hostname; /* the hostname of its real ip */ @@ -79,13 +100,7 @@ typedef struct connection_t { struct edge_t *edge; /* edge associated with this connection */ #ifndef DISABLE_LEGACY - rsa_t *rsa; /* his public RSA key */ - cipher_t incipher; /* Cipher he will use to send data to us */ - cipher_t outcipher; /* Cipher we will use to send data to him */ - digest_t indigest; - digest_t outdigest; - uint64_t inbudget; - uint64_t outbudget; + legacy_ctx_t *legacy; #endif ecdsa_t *ecdsa; /* his public ECDSA key */ diff --git a/src/fsck.c b/src/fsck.c index e7262727..28da620e 100644 --- a/src/fsck.c +++ b/src/fsck.c @@ -541,9 +541,7 @@ static bool check_public_keys(splay_tree_t *config, const char *name, rsa_t *rsa bool success = true; #ifndef DISABLE_LEGACY - rsa_t *rsa_pub = NULL; - read_rsa_public_key(&rsa_pub, config, name); - + rsa_t *rsa_pub = read_rsa_public_key(config, name); success = check_rsa_pubkey(rsa_priv, rsa_pub, host_file); rsa_free(rsa_pub); #endif diff --git a/src/keys.c b/src/keys.c index 0485c761..2db2b159 100644 --- a/src/keys.c +++ b/src/keys.c @@ -295,7 +295,7 @@ rsa_t *read_rsa_private_key(splay_tree_t *config_tree, char **keyfile) { return key; } -bool read_rsa_public_key(rsa_t **rsa, splay_tree_t *config_tree, const char *name) { +rsa_t *read_rsa_public_key(splay_tree_t *config_tree, const char *name) { FILE *fp; char *fname; char *n; @@ -303,9 +303,9 @@ bool read_rsa_public_key(rsa_t **rsa, splay_tree_t *config_tree, const char *nam /* First, check for simple PublicKey statement */ if(get_config_string(lookup_config(config_tree, "PublicKey"), &n)) { - *rsa = rsa_set_hex_public_key(n, "FFFF"); + rsa_t *rsa = rsa_set_hex_public_key(n, "FFFF"); free(n); - return *rsa != NULL; + return rsa; } /* Else, check for PublicKeyFile statement and read it */ @@ -322,15 +322,15 @@ bool read_rsa_public_key(rsa_t **rsa, splay_tree_t *config_tree, const char *nam return false; } - *rsa = rsa_read_pem_public_key(fp); + rsa_t *rsa = rsa_read_pem_public_key(fp); fclose(fp); - if(!*rsa) { + if(!rsa) { logger(DEBUG_ALWAYS, LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno)); } free(fname); - return *rsa != NULL; + return rsa; } #endif diff --git a/src/keys.h b/src/keys.h index 2c3ab408..1e0dc157 100644 --- a/src/keys.h +++ b/src/keys.h @@ -12,7 +12,7 @@ extern bool read_ecdsa_public_key(ecdsa_t **ecdsa, splay_tree_t **config_tree, c #ifndef DISABLE_LEGACY extern rsa_t *read_rsa_private_key(splay_tree_t *config, char **keyfile); -extern bool read_rsa_public_key(rsa_t **rsa, splay_tree_t *config_tree, const char *name); +extern rsa_t *read_rsa_public_key(splay_tree_t *config_tree, const char *name); #endif #endif // TINC_KEYS_H diff --git a/src/meta.c b/src/meta.c index 6a217d61..0ff7bef0 100644 --- a/src/meta.c +++ b/src/meta.c @@ -21,6 +21,8 @@ #include "system.h" +#include + #include "cipher.h" #include "connection.h" #include "logger.h" @@ -68,17 +70,16 @@ bool send_meta(connection_t *c, const void *buffer, size_t length) { #ifdef DISABLE_LEGACY return false; #else + assert(c->legacy); - if(length > c->outbudget) { + if(!decrease_budget(&c->legacy->out, length)) { logger(DEBUG_META, LOG_ERR, "Byte limit exceeded for encryption to %s (%s)", c->name, c->hostname); return false; - } else { - c->outbudget -= length; } size_t outlen = length; - if(!cipher_encrypt(&c->outcipher, buffer, length, buffer_prepare(&c->outbuf, length), &outlen, false) || outlen != length) { + if(!cipher_encrypt(&c->legacy->out.cipher, buffer, length, buffer_prepare(&c->outbuf, length), &outlen, false) || outlen != length) { logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting metadata to %s (%s)", c->name, c->hostname); return false; @@ -248,17 +249,16 @@ bool receive_meta(connection_t *c) { #ifdef DISABLE_LEGACY return false; #else + assert(c->legacy); - if((size_t)inlen > c->inbudget) { + if(!decrease_budget(&c->legacy->in, (size_t) inlen)) { logger(DEBUG_META, LOG_ERR, "Byte limit exceeded for decryption from %s (%s)", c->name, c->hostname); return false; - } else { - c->inbudget -= inlen; } size_t outlen = inlen; - if(!cipher_decrypt(&c->incipher, bufp, inlen, buffer_prepare(&c->inbuf, inlen), &outlen, false) || (size_t)inlen != outlen) { + if(!cipher_decrypt(&c->legacy->in.cipher, bufp, inlen, buffer_prepare(&c->inbuf, inlen), &outlen, false) || (size_t)inlen != outlen) { logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting metadata from %s (%s)", c->name, c->hostname); return false; diff --git a/src/net_setup.c b/src/net_setup.c index a829e822..f2534a47 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -788,9 +788,11 @@ static bool setup_myself(void) { } } - myself->connection->rsa = read_rsa_private_key(&config_tree, NULL); + rsa_t *rsa = read_rsa_private_key(&config_tree, NULL); - if(!myself->connection->rsa) { + if(rsa) { + myself->connection->legacy = new_legacy_ctx(rsa); + } else { if(experimental) { logger(DEBUG_ALWAYS, LOG_WARNING, "Support for legacy protocol disabled."); } else { diff --git a/src/net_socket.c b/src/net_socket.c index e154f486..af1bec22 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -648,10 +648,6 @@ begin: c->last_ping_time = time(NULL); c->status.connecting = true; c->name = xstrdup(outgoing->node->name); -#ifndef DISABLE_LEGACY - c->outcipher = myself->connection->outcipher; - c->outdigest = myself->connection->outdigest; -#endif c->outmaclength = myself->connection->outmaclength; c->outcompression = myself->connection->outcompression; c->last_ping_time = now.tv_sec; @@ -761,10 +757,6 @@ void handle_new_meta_connection(void *data, int flags) { c = new_connection(); c->name = xstrdup(""); -#ifndef DISABLE_LEGACY - c->outcipher = myself->connection->outcipher; - c->outdigest = myself->connection->outdigest; -#endif c->outmaclength = myself->connection->outmaclength; c->outcompression = myself->connection->outcompression; diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 16abc055..d1eb6c2c 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -513,44 +513,46 @@ bool id_h(connection_t *c, const char *request) { } #ifndef DISABLE_LEGACY +static const char *get_cipher_name(cipher_t *cipher) { + size_t keylen = cipher_keylength(cipher); + + if(keylen <= 16) { + return "aes-128-cfb"; + } else if(keylen <= 24) { + return "aes-192-cfb"; + } else { + return "aes-256-cfb"; + } +} + bool send_metakey(connection_t *c) { - if(!myself->connection->rsa) { + if(!myself->connection->legacy) { logger(DEBUG_CONNECTIONS, LOG_ERR, "Peer %s (%s) uses legacy protocol which we don't support", c->name, c->hostname); return false; } - if(!read_rsa_public_key(&c->rsa, c->config_tree, c->name)) { + rsa_t *rsa = read_rsa_public_key(c->config_tree, c->name); + + if(!rsa) { return false; } + legacy_ctx_t *ctx = new_legacy_ctx(rsa); + /* We need to use a stream mode for the meta protocol. Use AES for this, but try to match the key size with the one from the cipher selected by Cipher. */ - size_t keylen = cipher_keylength(myself->incipher); - const char *cipher_name; - - if(keylen <= 16) { - cipher_name = "aes-128-cfb"; - } else if(keylen <= 24) { - cipher_name = "aes-192-cfb"; - } else { - cipher_name = "aes-256-cfb"; - } - - if(!cipher_open_by_name(&c->outcipher, cipher_name)) { - return false; - } - - c->outbudget = cipher_budget(&c->outcipher); + const char *cipher_name = get_cipher_name(myself->incipher); - if(!digest_open_by_name(&c->outdigest, "sha256", DIGEST_ALGO_SIZE)) { - cipher_close(&c->outcipher); + if(!init_crypto_by_name(&ctx->out, cipher_name, "sha256")) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of cipher or digest to %s (%s)", c->name, c->hostname); + free_legacy_ctx(ctx); return false; } - const size_t len = rsa_size(c->rsa); + const size_t len = rsa_size(ctx->rsa); char *key = alloca(len); char *enckey = alloca(len); char *hexkey = alloca(2 * len + 1); @@ -571,7 +573,8 @@ bool send_metakey(connection_t *c) { key[0] &= 0x7F; - if(!cipher_set_key_from_rsa(&c->outcipher, key, len, true)) { + if(!cipher_set_key_from_rsa(&ctx->out.cipher, key, len, true)) { + free_legacy_ctx(ctx); return false; } @@ -587,11 +590,15 @@ bool send_metakey(connection_t *c) { with a length equal to that of the modulus of the RSA key. */ - if(!rsa_public_encrypt(c->rsa, key, len, enckey)) { + if(!rsa_public_encrypt(ctx->rsa, key, len, enckey)) { + free_legacy_ctx(ctx); logger(DEBUG_ALWAYS, LOG_ERR, "Error during encryption of meta key for %s (%s)", c->name, c->hostname); return false; } + free_legacy_ctx(c->legacy); + c->legacy = ctx; + /* Convert the encrypted random data to a hexadecimal formatted string */ bin2hex(enckey, hexkey, len); @@ -599,8 +606,8 @@ bool send_metakey(connection_t *c) { /* Send the meta key */ bool result = send_request(c, "%d %d %d %d %d %s", METAKEY, - cipher_get_nid(&c->outcipher), - digest_get_nid(&c->outdigest), c->outmaclength, + cipher_get_nid(&c->legacy->out.cipher), + digest_get_nid(&c->legacy->out.digest), c->outmaclength, c->outcompression, hexkey); c->status.encryptout = true; @@ -608,17 +615,17 @@ bool send_metakey(connection_t *c) { } bool metakey_h(connection_t *c, const char *request) { - if(!myself->connection->rsa) { + if(!myself->connection->legacy || !c->legacy) { return false; } char hexkey[MAX_STRING_SIZE]; - int cipher, digest, maclength, compression; - const size_t len = rsa_size(myself->connection->rsa); + int cipher, digest; + const size_t len = rsa_size(myself->connection->legacy->rsa); char *enckey = alloca(len); char *key = alloca(len); - if(sscanf(request, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, hexkey) != 5) { + if(sscanf(request, "%*d %d %d %*d %*d " MAX_STRING, &cipher, &digest, hexkey) != 3) { logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname); return false; } @@ -636,7 +643,7 @@ bool metakey_h(connection_t *c, const char *request) { /* Decrypt the meta key */ - if(!rsa_private_decrypt(myself->connection->rsa, enckey, len, key)) { + if(!rsa_private_decrypt(myself->connection->legacy->rsa, enckey, len, key)) { logger(DEBUG_ALWAYS, LOG_ERR, "Error during decryption of meta key for %s (%s)", c->name, c->hostname); return false; } @@ -648,28 +655,22 @@ bool metakey_h(connection_t *c, const char *request) { /* Check and lookup cipher and digest algorithms */ - if(cipher) { - if(!cipher_open_by_nid(&c->incipher, cipher) || !cipher_set_key_from_rsa(&c->incipher, key, len, false)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of cipher from %s (%s)", c->name, c->hostname); - return false; - } - } else { - logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "null cipher"); + if(!cipher || !digest) { + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): cipher %d, digest %d", c->name, c->hostname, cipher, digest); return false; } - c->inbudget = cipher_budget(&c->incipher); + if(!init_crypto_by_nid(&c->legacy->in, cipher, digest)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of cipher or digest from %s (%s)", c->name, c->hostname); + return false; + } - if(digest) { - if(!digest_open_by_nid(&c->indigest, digest, DIGEST_ALGO_SIZE)) { - logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of digest from %s (%s)", c->name, c->hostname); - return false; - } - } else { - logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "null digest"); + if(!cipher_set_key_from_rsa(&c->legacy->in.cipher, key, len, false)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error setting RSA key for %s (%s)", c->name, c->hostname); return false; } + c->status.decryptin = true; c->allow_request = CHALLENGE; @@ -678,7 +679,7 @@ bool metakey_h(connection_t *c, const char *request) { } bool send_challenge(connection_t *c) { - const size_t len = rsa_size(c->rsa); + const size_t len = rsa_size(c->legacy->rsa); char *buffer = alloca(len * 2 + 1); c->hischallenge = xrealloc(c->hischallenge, len); @@ -697,12 +698,12 @@ bool send_challenge(connection_t *c) { } bool challenge_h(connection_t *c, const char *request) { - if(!myself->connection->rsa) { + if(!myself->connection->legacy) { return false; } char buffer[MAX_STRING_SIZE]; - const size_t len = rsa_size(myself->connection->rsa); + const size_t len = rsa_size(myself->connection->legacy->rsa); if(sscanf(request, "%*d " MAX_STRING, buffer) != 1) { logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name, c->hostname); @@ -734,13 +735,13 @@ bool challenge_h(connection_t *c, const char *request) { } bool send_chal_reply(connection_t *c) { - const size_t len = rsa_size(myself->connection->rsa); - size_t digestlen = digest_length(&c->indigest); + const size_t len = rsa_size(myself->connection->legacy->rsa); + size_t digestlen = digest_length(&c->legacy->in.digest); char *digest = alloca(digestlen * 2 + 1); /* Calculate the hash from the challenge we received */ - if(!digest_create(&c->indigest, c->mychallenge, len, digest)) { + if(!digest_create(&c->legacy->in.digest, c->mychallenge, len, digest)) { return false; } @@ -771,7 +772,7 @@ bool chal_reply_h(connection_t *c, const char *request) { /* Check if the length of the hash is all right */ - if(inlen != digest_length(&c->outdigest)) { + if(inlen != digest_length(&c->legacy->out.digest)) { logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply length"); return false; } @@ -779,7 +780,7 @@ bool chal_reply_h(connection_t *c, const char *request) { /* Verify the hash */ - if(!digest_verify(&c->outdigest, c->hischallenge, rsa_size(c->rsa), hishash)) { + if(!digest_verify(&c->legacy->out.digest, c->hischallenge, rsa_size(c->legacy->rsa), hishash)) { logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply"); return false; } -- 2.20.1