diff options
author | Matthias Schiffer <mschiffer@universe-factory.net> | 2012-04-14 13:06:11 +0200 |
---|---|---|
committer | Matthias Schiffer <mschiffer@universe-factory.net> | 2012-04-14 13:06:11 +0200 |
commit | 701fcc7c7e353def78d89d9ee0ca52d32fb894b9 (patch) | |
tree | e39e37e1bf8e8bd5d38885d059c61006ce90f172 /src/protocol_ec25519_fhmqvc.c | |
parent | ac235fb7d28da6690f5ef7d7dc37d40bcebbd87a (diff) | |
download | fastd-701fcc7c7e353def78d89d9ee0ca52d32fb894b9.tar fastd-701fcc7c7e353def78d89d9ee0ca52d32fb894b9.zip |
Separate handshake from encryption method
Diffstat (limited to 'src/protocol_ec25519_fhmqvc.c')
-rw-r--r-- | src/protocol_ec25519_fhmqvc.c | 749 |
1 files changed, 749 insertions, 0 deletions
diff --git a/src/protocol_ec25519_fhmqvc.c b/src/protocol_ec25519_fhmqvc.c new file mode 100644 index 0000000..3168d03 --- /dev/null +++ b/src/protocol_ec25519_fhmqvc.c @@ -0,0 +1,749 @@ +/* + Copyright (c) 2012, Matthias Schiffer <mschiffer@universe-factory.net> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define _GNU_SOURCE + +#include "fastd.h" +#include "handshake.h" +#include "peer.h" +#include "task.h" + +#include <arpa/inet.h> + +#include <libuecc/ecc.h> +#include <crypto_auth_hmacsha256.h> +#include <crypto_hash_sha256.h> + + +#define PUBLICKEYBYTES 32 +#define SECRETKEYBYTES 32 +#define HMACBYTES crypto_auth_hmacsha256_BYTES +#define HASHBYTES crypto_hash_sha256_BYTES + + +#if HASHBYTES != crypto_auth_hmacsha256_KEYBYTES +#error bug: HASHBYTES != crypto_auth_hmacsha256_KEYBYTES +#endif + +#if HASHBYTES != SECRETKEYBYTES +#error bug: HASHBYTES != SECRETKEYBYTES +#endif + + +struct _fastd_protocol_config { + ecc_secret_key_256 secret_key; + ecc_public_key_256 public_key; +}; + +typedef enum _handshake_state { + HANDSHAKE_STATE_INIT, + HANDSHAKE_STATE_RESPONSE, + HANDSHAKE_STATE_ESTABLISHED +} handshake_state; + +struct _fastd_protocol_peer_config { + ecc_public_key_256 public_key; +}; + +typedef struct _protocol_handshake { + const fastd_peer_config *peer_config; + + handshake_state state; + ecc_secret_key_256 secret_key; + ecc_public_key_256 public_key; + ecc_public_key_256 peer_key; + ecc_public_key_256 sigma; + uint8_t shared_handshake_key[HASHBYTES]; +} protocol_handshake; + +typedef struct _protocol_session { + bool handshakes_cleaned; + bool refreshing; + + fastd_method_session_state *method_state; +} protocol_session; + +struct _fastd_protocol_peer_state { + protocol_session old_session; + protocol_session session; + + protocol_handshake *initiating_handshake; + protocol_handshake *accepting_handshake; +}; + + +#define RECORD_SENDER_KEY RECORD_PROTOCOL1 +#define RECORD_RECEIPIENT_KEY RECORD_PROTOCOL2 +#define RECORD_SENDER_HANDSHAKE_KEY RECORD_PROTOCOL3 +#define RECORD_RECEIPIENT_HANDSHAKE_KEY RECORD_PROTOCOL4 +#define RECORD_T RECORD_PROTOCOL5 + + +static void protocol_send(fastd_context *ctx, fastd_peer *peer, fastd_buffer buffer); + + +static inline bool read_key(uint8_t key[32], const char *hexkey) { + if ((strlen(hexkey) != 64) || (strspn(hexkey, "0123456789abcdefABCDEF") != 64)) + return false; + + int i; + for (i = 0; i < 32; i++) + sscanf(&hexkey[2*i], "%02hhx", &key[i]); + + return true; +} + +static inline bool is_session_valid(fastd_context *ctx, const protocol_session *session) { + return ctx->conf->method->session_is_valid(ctx, session->method_state); +} + +static inline void check_session_refresh(fastd_context *ctx, fastd_peer *peer) { + protocol_session *session = &peer->protocol_state->session; + + if (!session->refreshing && ctx->conf->method->session_want_refresh(ctx, session->method_state)) { + pr_debug(ctx, "refreshing session with %P", peer); + session->refreshing = true; + fastd_task_schedule_handshake(ctx, peer, 0); + } +} + +static fastd_protocol_config* protocol_init(fastd_context *ctx) { + fastd_protocol_config *protocol_config = malloc(sizeof(fastd_protocol_config)); + + if (!ctx->conf->secret) + exit_error(ctx, "no secret key configured"); + + if (!read_key(protocol_config->secret_key.s, ctx->conf->secret)) + exit_error(ctx, "invalid secret key"); + + ecc_25519_work work; + ecc_25519_scalarmult_base(&work, &protocol_config->secret_key); + ecc_25519_store(&protocol_config->public_key, &work); + + return protocol_config; +} + +static void protocol_peer_configure(fastd_context *ctx, fastd_peer_config *peer_conf) { + ecc_public_key_256 key; + + if (!peer_conf->key) { + pr_warn(ctx, "no key configured for `%s', disabling peer", peer_conf->name); + peer_conf->enabled = false; + return; + } + + if (!read_key(key.p, peer_conf->key)) { + pr_warn(ctx, "invalid key configured for `%s', disabling peer", peer_conf->name); + peer_conf->enabled = false; + return; + } + + peer_conf->protocol_config = malloc(sizeof(fastd_protocol_peer_config)); + peer_conf->protocol_config->public_key = key; +} + +static void init_peer_state(fastd_context *ctx, fastd_peer *peer) { + if (peer->protocol_state) + return; + + peer->protocol_state = malloc(sizeof(fastd_protocol_peer_state)); + memset(peer->protocol_state, 0, sizeof(fastd_protocol_peer_state)); +} + +static inline void free_handshake(protocol_handshake *handshake) { + if (handshake) { + memset(handshake, 0, sizeof(protocol_handshake)); + free(handshake); + } +} + +static void protocol_peer_config_purged(fastd_context *ctx, fastd_peer_config *peer_conf) { + fastd_peer *peer; + for (peer = ctx->peers; peer; peer = peer->next) { + if (!peer->protocol_state) + continue; + + if (peer->protocol_state->initiating_handshake && + peer->protocol_state->initiating_handshake->peer_config == peer_conf) { + free_handshake(peer->protocol_state->initiating_handshake); + peer->protocol_state->initiating_handshake = NULL; + } + + if (peer->protocol_state->accepting_handshake && + peer->protocol_state->accepting_handshake->peer_config == peer_conf) { + free_handshake(peer->protocol_state->accepting_handshake); + peer->protocol_state->accepting_handshake = NULL; + } + } +} + +static protocol_handshake* new_handshake(fastd_context *ctx, fastd_peer *peer, const fastd_peer_config *peer_config, bool initiate) { + protocol_handshake **handshake; + + if (initiate) + handshake = &peer->protocol_state->initiating_handshake; + else + handshake = &peer->protocol_state->accepting_handshake; + + free_handshake(*handshake); + + *handshake = malloc(sizeof(protocol_handshake)); + + (*handshake)->peer_config = peer_config; + + (*handshake)->state = HANDSHAKE_STATE_INIT; + + fastd_random_bytes(ctx, (*handshake)->secret_key.s, 32, false); + ecc_25519_secret_sanitize(&(*handshake)->secret_key, &(*handshake)->secret_key); + + ecc_25519_work work; + ecc_25519_scalarmult_base(&work, &(*handshake)->secret_key); + ecc_25519_store(&(*handshake)->public_key, &work); + + return *handshake; +} + +static void protocol_handshake_init(fastd_context *ctx, fastd_peer *peer) { + init_peer_state(ctx, peer); + + fastd_buffer buffer = fastd_handshake_new_init(ctx, peer, 3*(4+PUBLICKEYBYTES) /* sender key, receipient key, handshake key */); + + protocol_handshake *handshake = new_handshake(ctx, peer, peer->config, true); + + fastd_handshake_add(ctx, &buffer, RECORD_SENDER_KEY, PUBLICKEYBYTES, ctx->conf->protocol_config->public_key.p); + + if (handshake->peer_config) + fastd_handshake_add(ctx, &buffer, RECORD_RECEIPIENT_KEY, PUBLICKEYBYTES, handshake->peer_config->protocol_config->public_key.p); + else + pr_debug(ctx, "sending handshake to unknown peer %P", peer); + + fastd_handshake_add(ctx, &buffer, RECORD_SENDER_HANDSHAKE_KEY, PUBLICKEYBYTES, handshake->public_key.p); + + fastd_task_put_send_handshake(ctx, peer, buffer); +} + +static inline bool has_field(const fastd_handshake *handshake, uint8_t type, size_t length) { + return (handshake->records[type].length == length); +} + +static void respond_handshake(fastd_context *ctx, fastd_peer *peer, const fastd_handshake *handshake) { + pr_debug(ctx, "responding handshake with %P...", peer); + + uint8_t hashinput[5*PUBLICKEYBYTES]; + uint8_t hashbuf[HASHBYTES]; + uint8_t hmacbuf[HMACBYTES]; + + memcpy(hashinput, peer->protocol_state->accepting_handshake->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->peer_key.p, PUBLICKEYBYTES); + memcpy(hashinput+2*PUBLICKEYBYTES, ctx->conf->protocol_config->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+3*PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->peer_config->protocol_config->public_key.p, PUBLICKEYBYTES); + + crypto_hash_sha256(hashbuf, hashinput, 4*PUBLICKEYBYTES); + + ecc_secret_key_256 d = {{0}}, e = {{0}}, eb, s; + + memcpy(d.s, hashbuf, HASHBYTES/2); + memcpy(e.s, hashbuf+HASHBYTES/2, HASHBYTES/2); + + d.s[15] |= 0x80; + e.s[15] |= 0x80; + + ecc_25519_secret_mult(&eb, &e, &ctx->conf->protocol_config->secret_key); + ecc_25519_secret_add(&s, &eb, &peer->protocol_state->accepting_handshake->secret_key); + + ecc_25519_work work, workX; + ecc_25519_load(&work, &peer->protocol_state->accepting_handshake->peer_config->protocol_config->public_key); + ecc_25519_load(&workX, &peer->protocol_state->accepting_handshake->peer_key); + + ecc_25519_scalarmult(&work, &d, &work); + ecc_25519_add(&work, &workX, &work); + ecc_25519_scalarmult(&work, &s, &work); + + if (ecc_25519_is_identity(&work)) + return; + + ecc_25519_store(&peer->protocol_state->accepting_handshake->sigma, &work); + + memcpy(hashinput+4*PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->sigma.p, PUBLICKEYBYTES); + crypto_hash_sha256(peer->protocol_state->accepting_handshake->shared_handshake_key, hashinput, 5*PUBLICKEYBYTES); + + memcpy(hashinput, ctx->conf->protocol_config->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->public_key.p, PUBLICKEYBYTES); + + crypto_auth_hmacsha256(hmacbuf, hashinput, 2*PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->shared_handshake_key); + + fastd_buffer buffer = fastd_handshake_new_reply(ctx, peer, handshake, 4*(4+PUBLICKEYBYTES) + 4+HMACBYTES); + + fastd_handshake_add(ctx, &buffer, RECORD_SENDER_KEY, PUBLICKEYBYTES, ctx->conf->protocol_config->public_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_RECEIPIENT_KEY, PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->peer_config->protocol_config->public_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_SENDER_HANDSHAKE_KEY, PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->public_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_RECEIPIENT_HANDSHAKE_KEY, PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->peer_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_T, HMACBYTES, hmacbuf); + + fastd_task_put_send_handshake(ctx, peer, buffer); + + peer->protocol_state->accepting_handshake->state = HANDSHAKE_STATE_RESPONSE; +} + +static void establish(fastd_context *ctx, fastd_peer *peer, const fastd_peer_config *peer_config, bool initiator, + const ecc_public_key_256 *A, const ecc_public_key_256 *B, const ecc_public_key_256 *X, + const ecc_public_key_256 *Y, const ecc_public_key_256 *sigma) { + uint8_t hashinput[5*PUBLICKEYBYTES]; + uint8_t hash[HASHBYTES]; + + pr_verbose(ctx, "New session with %P established.", peer); + + if (is_session_valid(ctx, &peer->protocol_state->session) && !is_session_valid(ctx, &peer->protocol_state->old_session)) { + ctx->conf->method->session_free(ctx, peer->protocol_state->old_session.method_state); + peer->protocol_state->old_session = peer->protocol_state->session; + } + else { + ctx->conf->method->session_free(ctx, peer->protocol_state->session.method_state); + } + + memcpy(hashinput, X->p, PUBLICKEYBYTES); + memcpy(hashinput+PUBLICKEYBYTES, Y->p, PUBLICKEYBYTES); + memcpy(hashinput+2*PUBLICKEYBYTES, A->p, PUBLICKEYBYTES); + memcpy(hashinput+3*PUBLICKEYBYTES, B->p, PUBLICKEYBYTES); + memcpy(hashinput+4*PUBLICKEYBYTES, sigma->p, PUBLICKEYBYTES); + crypto_hash_sha256(hash, hashinput, 5*PUBLICKEYBYTES); + + peer->protocol_state->session.handshakes_cleaned = false; + peer->protocol_state->session.refreshing = false; + peer->protocol_state->session.method_state = ctx->conf->method->session_init(ctx, hash, HASHBYTES, initiator); + + free_handshake(peer->protocol_state->initiating_handshake); + peer->protocol_state->initiating_handshake = NULL; + + free_handshake(peer->protocol_state->accepting_handshake); + peer->protocol_state->accepting_handshake = NULL; + + fastd_peer_seen(ctx, peer); + + if (peer_config != peer->config) { + fastd_peer *perm_peer; + for (perm_peer = ctx->peers; perm_peer; perm_peer = perm_peer->next) { + if (perm_peer->config == peer_config) { + peer = fastd_peer_set_established_merge(ctx, perm_peer, peer); + break; + } + } + } + else { + fastd_peer_set_established(ctx, peer); + } + + fastd_task_schedule_keepalive(ctx, peer, ctx->conf->keepalive_interval*1000); + + if (!initiator) + protocol_send(ctx, peer, fastd_buffer_alloc(0, ctx->conf->method->min_encrypt_head_space(ctx), 0)); +} + +static void finish_handshake(fastd_context *ctx, fastd_peer *peer, const fastd_handshake *handshake) { + pr_debug(ctx, "finishing handshake with %P...", peer); + + uint8_t hashinput[5*PUBLICKEYBYTES]; + uint8_t hashbuf[HASHBYTES]; + uint8_t hmacbuf[HMACBYTES]; + + memcpy(hashinput, peer->protocol_state->initiating_handshake->peer_key.p, PUBLICKEYBYTES); + memcpy(hashinput+PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+2*PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->peer_config->protocol_config->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+3*PUBLICKEYBYTES, ctx->conf->protocol_config->public_key.p, PUBLICKEYBYTES); + + crypto_hash_sha256(hashbuf, hashinput, 4*PUBLICKEYBYTES); + + ecc_secret_key_256 d = {{0}}, e = {{0}}, da, s; + + memcpy(d.s, hashbuf, HASHBYTES/2); + memcpy(e.s, hashbuf+HASHBYTES/2, HASHBYTES/2); + + d.s[15] |= 0x80; + e.s[15] |= 0x80; + + ecc_25519_secret_mult(&da, &d, &ctx->conf->protocol_config->secret_key); + ecc_25519_secret_add(&s, &da, &peer->protocol_state->initiating_handshake->secret_key); + + ecc_25519_work work, workY; + ecc_25519_load(&work, &peer->protocol_state->initiating_handshake->peer_config->protocol_config->public_key); + ecc_25519_load(&workY, &peer->protocol_state->initiating_handshake->peer_key); + + ecc_25519_scalarmult(&work, &e, &work); + ecc_25519_add(&work, &workY, &work); + ecc_25519_scalarmult(&work, &s, &work); + + if (ecc_25519_is_identity(&work)) + return; + + ecc_25519_store(&peer->protocol_state->initiating_handshake->sigma, &work); + + memcpy(hashinput+4*PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->sigma.p, PUBLICKEYBYTES); + crypto_hash_sha256(peer->protocol_state->initiating_handshake->shared_handshake_key, hashinput, 5*PUBLICKEYBYTES); + + memcpy(hashinput, peer->protocol_state->initiating_handshake->peer_config->protocol_config->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->peer_key.p, PUBLICKEYBYTES); + + if(crypto_auth_hmacsha256_verify(handshake->records[RECORD_T].data, hashinput, 2*PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->shared_handshake_key) != 0) { + pr_warn(ctx, "received invalid protocol handshake response from %P", peer); + return; + } + + memcpy(hashinput, ctx->conf->protocol_config->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->public_key.p, PUBLICKEYBYTES); + crypto_auth_hmacsha256(hmacbuf, hashinput, 2*PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->shared_handshake_key); + + fastd_buffer buffer = fastd_handshake_new_reply(ctx, peer, handshake, 4*(4+PUBLICKEYBYTES) + 4+HMACBYTES); + + fastd_handshake_add(ctx, &buffer, RECORD_SENDER_KEY, PUBLICKEYBYTES, ctx->conf->protocol_config->public_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_RECEIPIENT_KEY, PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->peer_config->protocol_config->public_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_SENDER_HANDSHAKE_KEY, PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->public_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_RECEIPIENT_HANDSHAKE_KEY, PUBLICKEYBYTES, peer->protocol_state->initiating_handshake->peer_key.p); + fastd_handshake_add(ctx, &buffer, RECORD_T, HMACBYTES, hmacbuf); + + fastd_task_put_send_handshake(ctx, peer, buffer); + + establish(ctx, peer, peer->protocol_state->initiating_handshake->peer_config, true, + &peer->protocol_state->initiating_handshake->public_key, + &peer->protocol_state->initiating_handshake->peer_key, + &ctx->conf->protocol_config->public_key, + &peer->protocol_state->initiating_handshake->peer_config->protocol_config->public_key, + &peer->protocol_state->initiating_handshake->sigma); +} + +static void handle_finish_handshake(fastd_context *ctx, fastd_peer *peer, const fastd_handshake *handshake) { + pr_debug(ctx, "handling handshake finish with %P...", peer); + + uint8_t hashinput[2*PUBLICKEYBYTES]; + + memcpy(hashinput, peer->protocol_state->accepting_handshake->peer_config->protocol_config->public_key.p, PUBLICKEYBYTES); + memcpy(hashinput+PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->peer_key.p, PUBLICKEYBYTES); + + if(crypto_auth_hmacsha256_verify(handshake->records[RECORD_T].data, hashinput, 2*PUBLICKEYBYTES, peer->protocol_state->accepting_handshake->shared_handshake_key) != 0) { + pr_warn(ctx, "received invalid protocol handshake finish from %P", peer); + return; + } + + establish(ctx, peer, peer->protocol_state->accepting_handshake->peer_config, false, + &peer->protocol_state->accepting_handshake->peer_key, + &peer->protocol_state->accepting_handshake->public_key, + &peer->protocol_state->accepting_handshake->peer_config->protocol_config->public_key, + &ctx->conf->protocol_config->public_key, + &peer->protocol_state->accepting_handshake->sigma); +} + +static inline const fastd_peer_config* match_sender_key(fastd_context *ctx, const fastd_peer *peer, const unsigned char key[32]) { + if (peer->config) { + if (memcmp(peer->config->protocol_config->public_key.p, key, PUBLICKEYBYTES) == 0) + return peer->config; + } + + if (fastd_peer_is_temporary(peer) || fastd_peer_is_floating(peer)) { + fastd_peer_config *config; + for (config = ctx->conf->peers; config; config = config->next) { + if (!fastd_peer_config_is_floating(config)) + continue; + + if (memcmp(config->protocol_config->public_key.p, key, PUBLICKEYBYTES) == 0) + return config; + } + } + + return NULL; +} + +static void kill_handshakes(fastd_context *ctx, fastd_peer *peer) { + pr_debug(ctx, "there is a handshake conflict, retrying in a moment..."); + + free_handshake(peer->protocol_state->initiating_handshake); + peer->protocol_state->initiating_handshake = NULL; + + free_handshake(peer->protocol_state->accepting_handshake); + peer->protocol_state->accepting_handshake = NULL; +} + +static void protocol_handshake_handle(fastd_context *ctx, fastd_peer *peer, const fastd_handshake *handshake) { + init_peer_state(ctx, peer); + + if (!has_field(handshake, RECORD_SENDER_KEY, PUBLICKEYBYTES)) { + pr_debug(ctx, "received handshake without sender key from %P", peer); + return; + } + + const fastd_peer_config *peer_config = match_sender_key(ctx, peer, handshake->records[RECORD_SENDER_KEY].data); + + if (handshake->type > 1 && !has_field(handshake, RECORD_RECEIPIENT_KEY, PUBLICKEYBYTES)) { + pr_debug(ctx, "received handshake reply without receipient key from %P", peer); + return; + } + else if(has_field(handshake, RECORD_RECEIPIENT_KEY, PUBLICKEYBYTES)) { + if (memcmp(ctx->conf->protocol_config->public_key.p, handshake->records[RECORD_RECEIPIENT_KEY].data, PUBLICKEYBYTES) != 0) { + pr_debug(ctx, "received protocol handshake with wrong receipient key from %P", peer); + return; + } + } + + if (!has_field(handshake, RECORD_SENDER_HANDSHAKE_KEY, PUBLICKEYBYTES)) { + pr_debug(ctx, "received handshake without sender handshake key from %P", peer); + return; + } + + if (handshake->type > 1 && !has_field(handshake, RECORD_RECEIPIENT_HANDSHAKE_KEY, PUBLICKEYBYTES)) { + pr_debug(ctx, "received handshake reply without receipient handshake key from %P", peer); + return; + } + + if (handshake->type > 1 && !has_field(handshake, RECORD_T, HMACBYTES)) { + pr_debug(ctx, "received handshake reply without HMAC from %P", peer); + return; + } + + switch(handshake->type) { + case 1: + new_handshake(ctx, peer, peer_config, false); + memcpy(peer->protocol_state->accepting_handshake->peer_key.p, handshake->records[RECORD_SENDER_HANDSHAKE_KEY].data, PUBLICKEYBYTES); + respond_handshake(ctx, peer, handshake); + break; + + case 2: + if (!peer->protocol_state->initiating_handshake || peer->protocol_state->initiating_handshake->state != HANDSHAKE_STATE_INIT) { + pr_debug(ctx, "received unexpected handshake response from %P", peer); + return; + } + + if (peer->protocol_state->initiating_handshake->peer_config != peer_config) { + if (peer->protocol_state->initiating_handshake->peer_config) { + pr_debug(ctx, "received handshake response with wrong sender key from %P", peer); + return; + } + else { + peer->protocol_state->initiating_handshake->peer_config = peer_config; + } + } + + if (memcmp(peer->protocol_state->initiating_handshake->public_key.p, handshake->records[RECORD_RECEIPIENT_HANDSHAKE_KEY].data, PUBLICKEYBYTES) != 0) { + pr_debug(ctx, "received handshake response with unexpected receipient handshake key from %P", peer); + return; + } + + pr_debug(ctx, "received handshake response from %P", peer); + + if (peer->protocol_state->accepting_handshake) { + kill_handshakes(ctx, peer); + return; + } + + memcpy(peer->protocol_state->initiating_handshake->peer_key.p, handshake->records[RECORD_SENDER_HANDSHAKE_KEY].data, PUBLICKEYBYTES); + + finish_handshake(ctx, peer, handshake); + break; + + case 3: + if (!peer->protocol_state->accepting_handshake || peer->protocol_state->accepting_handshake->state != HANDSHAKE_STATE_RESPONSE) { + pr_debug(ctx, "received unexpected protocol handshake finish from %P", peer); + return; + } + + if (peer->protocol_state->accepting_handshake->peer_config != peer_config) { + pr_debug(ctx, "received protocol handshake finish with wrong sender key from %P", peer); + return; + } + + if (memcmp(peer->protocol_state->accepting_handshake->public_key.p, handshake->records[RECORD_RECEIPIENT_HANDSHAKE_KEY].data, PUBLICKEYBYTES) != 0) { + pr_debug(ctx, "received handshake response with unexpected receipient handshake key from %P", peer); + return; + } + + if (memcmp(peer->protocol_state->accepting_handshake->peer_key.p, handshake->records[RECORD_SENDER_HANDSHAKE_KEY].data, PUBLICKEYBYTES) != 0) { + pr_debug(ctx, "received handshake response with unexpected sender handshake key from %P", peer); + return; + } + + pr_debug(ctx, "received handshake finish from %P", peer); + + if (peer->protocol_state->initiating_handshake && peer->protocol_state->initiating_handshake->state != HANDSHAKE_STATE_INIT) { + kill_handshakes(ctx, peer); + return; + } + + handle_finish_handshake(ctx, peer, handshake); + break; + + default: + pr_debug(ctx, "received handshake reply with unknown type %u", handshake->type); + } +} + +static void protocol_handle_recv(fastd_context *ctx, fastd_peer *peer, fastd_buffer buffer) { + if (!fastd_peer_is_established(peer)) { + pr_debug(ctx, "received unexpected packet from %P", peer); + + if (fastd_peer_is_temporary(peer)) { + pr_debug(ctx, "sending handshake to temporary peer %P", peer); + fastd_task_schedule_handshake(ctx, peer, 0); + } + + goto fail; + } + + if (!peer->protocol_state || !is_session_valid(ctx, &peer->protocol_state->session)) + goto fail; + + fastd_buffer recv_buffer; + bool ok = false; + + if (is_session_valid(ctx, &peer->protocol_state->old_session)) { + if (ctx->conf->method->decrypt(ctx, peer->protocol_state->old_session.method_state, &recv_buffer, buffer)) + ok = true; + } + + if (!ok) { + if (ctx->conf->method->decrypt(ctx, peer->protocol_state->session.method_state, &recv_buffer, buffer)) { + ok = true; + + if (!peer->protocol_state->session.handshakes_cleaned) { + pr_debug(ctx, "cleaning left handshakes with %P", peer); + fastd_task_delete_peer_handshakes(ctx, peer); + peer->protocol_state->session.handshakes_cleaned = true; + + if (ctx->conf->method->session_is_initiator(ctx, peer->protocol_state->session.method_state)) + protocol_send(ctx, peer, fastd_buffer_alloc(0, ctx->conf->method->min_encrypt_head_space(ctx), 0)); + } + + if (peer->protocol_state->old_session.method_state) { + pr_debug(ctx, "invalidating old session with %P", peer); + ctx->conf->method->session_free(ctx, peer->protocol_state->old_session.method_state); + peer->protocol_state->old_session.method_state = NULL; + } + + check_session_refresh(ctx, peer); + } + } + + if (!ok) { + pr_debug(ctx, "verification failed for packet received from %P", peer); + goto fail; + } + + fastd_peer_seen(ctx, peer); + + if (recv_buffer.len) + fastd_task_put_handle_recv(ctx, peer, recv_buffer); + else + fastd_buffer_free(recv_buffer); + + return; + + fail: + fastd_buffer_free(buffer); +} + +static void protocol_send(fastd_context *ctx, fastd_peer *peer, fastd_buffer buffer) { + if (!peer->protocol_state || !is_session_valid(ctx, &peer->protocol_state->session)) + goto fail; + + check_session_refresh(ctx, peer); + + protocol_session *session; + if (ctx->conf->method->session_is_initiator(ctx, peer->protocol_state->session.method_state) && is_session_valid(ctx, &peer->protocol_state->old_session)) { + pr_debug(ctx, "sending packet for old session to %P", peer); + session = &peer->protocol_state->old_session; + } + else { + session = &peer->protocol_state->session; + } + + fastd_buffer send_buffer; + if (!ctx->conf->method->encrypt(ctx, session->method_state, &send_buffer, buffer)) + goto fail; + + fastd_task_put_send(ctx, peer, send_buffer); + + fastd_task_delete_peer_keepalives(ctx, peer); + fastd_task_schedule_keepalive(ctx, peer, ctx->conf->keepalive_interval*1000); + return; + + fail: + fastd_buffer_free(buffer); +} + +static void protocol_free_peer_state(fastd_context *ctx, fastd_peer *peer) { + if (peer->protocol_state) { + free_handshake(peer->protocol_state->initiating_handshake); + free_handshake(peer->protocol_state->accepting_handshake); + + ctx->conf->method->session_free(ctx, peer->protocol_state->old_session.method_state); + ctx->conf->method->session_free(ctx, peer->protocol_state->session.method_state); + + free(peer->protocol_state); + } +} + + +static void hexdump(const char *desc, unsigned char d[32]) { + printf("%s", desc); + + int i; + for (i = 0; i < 32; i++) + printf("%02x", d[i]); + + printf("\n"); +} + +static void protocol_generate_key(fastd_context *ctx) { + ecc_secret_key_256 secret_key; + ecc_public_key_256 public_key; + + pr_info(ctx, "Reading 32 bytes from /dev/random..."); + + fastd_random_bytes(ctx, secret_key.s, 32, true); + ecc_25519_secret_sanitize(&secret_key, &secret_key); + + ecc_25519_work work; + ecc_25519_scalarmult_base(&work, &secret_key); + ecc_25519_store(&public_key, &work); + + hexdump("Secret: ", secret_key.s); + hexdump("Public: ", public_key.p); +} + + +const fastd_protocol fastd_protocol_ec25519_fhmqvc = { + .name = "ec25519-fhmqvc", + + .init = protocol_init, + .peer_configure = protocol_peer_configure, + .peer_config_purged = protocol_peer_config_purged, + + .handshake_init = protocol_handshake_init, + .handshake_handle = protocol_handshake_handle, + + .handle_recv = protocol_handle_recv, + .send = protocol_send, + + .free_peer_state = protocol_free_peer_state, + + .generate_key = protocol_generate_key, +}; |