From cff27a258f3b3a97b5d2e309c264eceea41dff3a Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sat, 16 Jul 2011 20:21:44 +0200 Subject: [PATCH] Use ECDSA to sign ECDH key exchange for UDP session keys. The ECDSA public keys will also be included in the ANS_KEY requests, but are only used when no ECDSA public key is known yet. --- src/conf.c | 4 +-- src/conf.h | 2 +- src/connection.c | 1 + src/net.h | 1 + src/net_setup.c | 47 +++++++++++++++++++++++++++++++++ src/node.c | 3 +++ src/node.h | 1 + src/openssl/ecdh.c | 7 +++++ src/openssl/ecdh.h | 1 + src/protocol_auth.c | 2 +- src/protocol_key.c | 64 ++++++++++++++++++++++++++++++++++++--------- 11 files changed, 116 insertions(+), 17 deletions(-) diff --git a/src/conf.c b/src/conf.c index 0bbee09..f47faef 100644 --- a/src/conf.c +++ b/src/conf.c @@ -400,9 +400,9 @@ bool read_connection_config(connection_t *c) { return x; } -bool append_connection_config(const connection_t *c, const char *key, const char *value) { +bool append_config_file(const char *name, const char *key, const char *value) { char *fname; - xasprintf(&fname, "%s/hosts/%s", confbase, c->name); + xasprintf(&fname, "%s/hosts/%s", confbase, name); FILE *fp = fopen(fname, "a"); diff --git a/src/conf.h b/src/conf.h index 20fc95e..bd3850b 100644 --- a/src/conf.h +++ b/src/conf.h @@ -61,7 +61,7 @@ extern bool read_config_file(splay_tree_t *, const char *); extern void read_config_options(splay_tree_t *, const char *); extern bool read_server_config(void); extern bool read_connection_config(struct connection_t *); -extern bool append_connection_config(const struct connection_t *, const char *, const char *); +extern bool append_config_file(const char *, const char *, const char *); extern FILE *ask_and_open(const char *, const char *, const char *); extern bool is_safe_path(const char *); extern bool disable_old_keys(FILE *); diff --git a/src/connection.c b/src/connection.c index 713a40c..bae86b9 100644 --- a/src/connection.c +++ b/src/connection.c @@ -69,6 +69,7 @@ void free_connection(connection_t *c) { cipher_close(&c->outcipher); digest_close(&c->outdigest); + ecdh_free(&c->ecdh); ecdsa_free(&c->ecdsa); rsa_free(&c->rsa); diff --git a/src/net.h b/src/net.h index a4ac430..c511a5f 100644 --- a/src/net.h +++ b/src/net.h @@ -141,6 +141,7 @@ extern void close_network_connections(void); extern int main_loop(void); extern void terminate_connection(struct connection_t *, bool); extern void flush_queue(struct node_t *); +extern bool node_read_ecdsa_public_key(struct node_t *); extern bool read_ecdsa_public_key(struct connection_t *); extern bool read_rsa_public_key(struct connection_t *); extern void send_mtu_probe(struct node_t *); diff --git a/src/net_setup.c b/src/net_setup.c index f3de53a..91f7609 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -45,6 +45,53 @@ char *myport; static struct event device_ev; +bool node_read_ecdsa_public_key(node_t *n) { + if(ecdsa_active(&n->ecdsa)) + return true; + + splay_tree_t *config_tree; + FILE *fp; + char *fname; + char *p; + bool result = false; + + xasprintf(&fname, "%s/hosts/%s", confbase, n->name); + + init_configuration(&config_tree); + if(!read_config_file(config_tree, fname)) + goto exit; + + /* First, check for simple ECDSAPublicKey statement */ + + if(get_config_string(lookup_config(config_tree, "ECDSAPublicKey"), &p)) { + result = ecdsa_set_base64_public_key(&n->ecdsa, p); + free(p); + goto exit; + } + + /* Else, check for ECDSAPublicKeyFile statement and read it */ + + free(fname); + + if(!get_config_string(lookup_config(config_tree, "ECDSAPublicKeyFile"), &fname)) + xasprintf(&fname, "%s/hosts/%s", confbase, n->name); + + fp = fopen(fname, "r"); + + if(!fp) { + logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s", fname, strerror(errno)); + goto exit; + } + + result = ecdsa_read_pem_public_key(&n->ecdsa, fp); + fclose(fp); + +exit: + exit_configuration(&config_tree); + free(fname); + return result; +} + bool read_ecdsa_public_key(connection_t *c) { FILE *fp; char *fname; diff --git a/src/node.c b/src/node.c index 7e6dd8e..38123e2 100644 --- a/src/node.c +++ b/src/node.c @@ -85,6 +85,9 @@ void free_node(node_t *n) { cipher_close(&n->outcipher); digest_close(&n->outdigest); + ecdh_free(&n->ecdh); + ecdsa_free(&n->ecdsa); + if(timeout_initialized(&n->mtuevent)) event_del(&n->mtuevent); diff --git a/src/node.h b/src/node.h index 05d751c..0ce7542 100644 --- a/src/node.h +++ b/src/node.h @@ -49,6 +49,7 @@ typedef struct node_t { node_status_t status; time_t last_req_key; + ecdsa_t ecdsa; /* His public ECDSA key */ ecdh_t ecdh; /* State for ECDH key exchange */ cipher_t incipher; /* Cipher for UDP packets */ diff --git a/src/openssl/ecdh.c b/src/openssl/ecdh.c index 8432420..4dd399f 100644 --- a/src/openssl/ecdh.c +++ b/src/openssl/ecdh.c @@ -75,3 +75,10 @@ bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) { return true; } + +void ecdh_free(ecdh_t *ecdh) { + if(*ecdh) { + EC_KEY_free(*ecdh); + *ecdh = NULL; + } +} diff --git a/src/openssl/ecdh.h b/src/openssl/ecdh.h index bf32266..ef7de6e 100644 --- a/src/openssl/ecdh.h +++ b/src/openssl/ecdh.h @@ -29,5 +29,6 @@ typedef EC_KEY *ecdh_t; extern bool ecdh_generate_public(ecdh_t *ecdh, void *pubkey); extern bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared); +extern void ecdh_free(ecdh_t *ecdh); #endif diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 7595c48..4331e94 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -547,7 +547,7 @@ static bool upgrade_h(connection_t *c, char *request) { } logger(LOG_INFO, "Got ECDSA public key from %s (%s), upgrading!", c->name, c->hostname); - append_connection_config(c, "ECDSAPublicKey", pubkey); + append_config_file(c->name, "ECDSAPublicKey", pubkey); c->allow_request = TERMREQ; return send_termreq(c); } diff --git a/src/protocol_key.c b/src/protocol_key.c index 313681b..5246e7d 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -145,18 +145,31 @@ bool req_key_h(connection_t *c, char *request) { } bool send_ans_key_ecdh(node_t *to) { - char key[ECDH_SIZE * 2 + 1]; + int siglen = ecdsa_size(&myself->connection->ecdsa); + char key[(ECDH_SIZE + siglen) * 2 + 1]; - ecdh_generate_public(&to->ecdh, key); + if(!ecdh_generate_public(&to->ecdh, key)) + return false; - b64encode(key, key, ECDH_SIZE); + if(!ecdsa_sign(&myself->connection->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) + return false; - return send_request(to->nexthop->connection, "%d %s %s ECDH:%s %d %d %zu %d", ANS_KEY, - myself->name, to->name, key, + b64encode(key, key, ECDH_SIZE + siglen); + + char *pubkey = ecdsa_get_base64_public_key(&myself->connection->ecdsa); + + if(!pubkey) + return false; + + int result = send_request(to->nexthop->connection, "%d %s %s ECDH:%s:%s %d %d %zu %d", ANS_KEY, + myself->name, to->name, key, pubkey, cipher_get_nid(&myself->incipher), digest_get_nid(&myself->indigest), digest_length(&myself->indigest), myself->incompression); + + free(pubkey); + return result; } bool send_ans_key(node_t *to) { @@ -279,28 +292,53 @@ bool ans_key_h(connection_t *c, char *request) { /* ECDH or old-style key exchange? */ if(experimental && !strncmp(key, "ECDH:", 5)) { + char *pubkey = strchr(key + 5, ':'); + if(pubkey) + *pubkey++ = 0; + + /* Check if we already have an ECDSA public key for this node. + * If not, use the one from the key exchange, and store it. */ + + if(!node_read_ecdsa_public_key(from)) { + if(!pubkey) { + logger(LOG_ERR, "No ECDSA public key known for %s (%s), cannot verify ECDH key exchange!", from->name, from->hostname); + return true; + } + + if(!ecdsa_set_base64_public_key(&from->ecdsa, pubkey)) + return true; + + append_config_file(from->name, "ECDSAPublicKey", pubkey); + } + + int siglen = ecdsa_size(&from->ecdsa); int keylen = b64decode(key + 5, key + 5, sizeof key - 5); - if(keylen != ECDH_SIZE) { - logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); - return false; + if(keylen != ECDH_SIZE + siglen) { + logger(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(LOG_ERR, "ECDH key too short for cipher of %s!", from->name); - return false; + return true; + } + + if(!ecdsa_verify(&from->ecdsa, key + 5, ECDH_SIZE, key + 5 + ECDH_SIZE)) { + logger(LOG_ERR, "Possible intruder %s (%s): %s", from->name, from->hostname, "invalid ECDSA signature"); + return true; } if(!from->ecdh) { from->status.ecdh = true; if(!send_ans_key(from)) - return false; + return true; } char shared[ECDH_SHARED_SIZE * 2 + 1]; if(!ecdh_compute_shared(&from->ecdh, key + 5, shared)) - return false; + return true; /* Update our crypto end */ @@ -322,7 +360,7 @@ bool ans_key_h(connection_t *c, char *request) { } if(!prf(shared, ECDH_SHARED_SIZE, seed, strlen(seed), key, hiskeylen * 2 + mykeylen * 2)) - return false; + return true; free(seed); @@ -349,7 +387,7 @@ bool ans_key_h(connection_t *c, char *request) { if(keylen != cipher_keylength(&from->outcipher)) { logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); - return false; + return true; } /* Update our copy of the origin's packet key */ -- 2.25.1