diff --git a/doc/Authentication.md b/doc/Authentication.md index 6688419..0c16eb4 100644 --- a/doc/Authentication.md +++ b/doc/Authentication.md @@ -55,11 +55,14 @@ ntop[0-1][0-9] This example also lists another user `sister` (with the same secret password `007`). The users become part of the preceding community name, `netleo` in this case. The public keys are cryptographically tied only to the user name, not to the community name. That way, to switch a user from one community to another, her line can easily be copied from one community section to another. By the way, do not forget to provide the `community.list` file to the supernode through the `-c community.list` parameter. -Current supernode behavior does not limit the simultanious usage of usernames, i.e. one username can be used from several edges at the same time. However, it is recommended to use a distinct username and password for each edge or computer. Your management port output will be much more meaningful then with the HINT column showing the related username. Also, the auto IP address feature, i.e. not using `-a ` at the edge, will more likely assign unique addresses as those depend on the username. +Current supernode behavior does not limit the simultanious usage of usernames, i.e. one username can be used from several edges at the same time. However, it is recommended to use a distinct username and password for each edge or computer. Your management port output will be much more meaningful then with the `HINT` column showing the related username. Also, the auto IP address feature, i.e. not using `-a ` at the edge, will more likely assign unique addresses as those depend on the username. -If a user chooses a new password or needs to be excluded from accessing the community (stolen edge scenario), the corresponding line of the `community.list` file can be replaced with a newly generated one or be deleted respectively. Restarting the supernode is required after performing changes because the supernode reads in this data only once at start-up. +If a user chooses a new password or needs to be excluded from accessing the community (stolen edge scenario), the corresponding line of the `community.list` file can be replaced with a newly generated one or be deleted respectively. Restarting the supernode or issuing the `reload_communities` command to the management port is required after performing changes to make the supernode(s) read in this data again. + +When using this feature federation-wide, i.e. across several supernodes, please make sure to keep all supernodes' `community.list` files in sync. So, if you delete or change a user one supernode (or add it), you need to do it at all supernodes. There is no built-in sync for the `community.list` files across the federation. External tools such as _Syncthing_ or your very own script-driven scp-based-file-distribution might be of assistance. Also, with every change, you need to restart the supernode or issue the `reload_communites` command to the management port as outlined above. + +With a view to the detailed explanations below, your supernode(s) should have a non-default federation name given by the `-F ` command line parameter, e.g. `-F secretFed`. It is used to derive a priavte key at the supernode side and is only to be shared among supernodes. -With a view to the detailed explanations below, your supernode should have a non-default federation name given by the `-F ` command line parameter, e.g. `-F secretFed`. It is used to derive a priavte key on the supernode side and is only to be shared among supernodes. However, a federation-wide support for this feature, i.e. across several supernodes, is possible but not implemented yet. #### Edge @@ -84,18 +87,19 @@ Considering all this, our example expands to [user@host n2n]$ sudo ./edge -l -c netleo -I logan -J 007 -A5 -k mySecretKey -P opIyaWhWjKLJSNOHNpKnGmelhHWRqkmY5pAx7lbDHp4 ``` +You might want to consider the use of [`.conf` files](https://github.com/ntop/n2n/blob/dev/doc/ConfigurationFiles.md) to accomodate all the command line paramters more easily. + + #### How Does It Work? In order to make this authentication scheme work, the existing header encryption scheme is split into using two keys: a _static_ and a _dynamic_ one. The static key remains unchanged and is the [classic header encryption key](https://github.com/ntop/n2n/blob/dev/doc/Crypto.md#header) derived from the community name. It only is applied to the very basic registration traffic between edge and supernode (REGISTER_SUPER, REGISTER_SUPER_ACK, REGISTER_SUPER_NAK). The dynamic key is derived (among others) from the federation name – keep it secret! – and appplied to all the other packets, especially the data packets (PAKET) and peer-to-peer building packets (REGISTER), but also the ping and peer information (QUERY_PEER, PEER_INFO). An edge not provided with a valid dynamic key is not able to participate in the further communication. In regular header encryption mode, static key and dynamic key are equal. With activated user-password scheme, the supernode generates and transmits a dynamic key with the REGISTER_SUPER for further use. This happens in a secure way based on public key cryptography. A non-autheticated edge, i.e. without corresponding entry at the supernode or valid credentials, will not receive a valid dynmic key for communication beyond registration. -In user-password scheme, the packets encrypted with the static key (REGISER_SUPER, REGISTER_SUPER_ACK, useless for REGISTER_SUPER_NAK) are "signed" with an encrypted outer hash using the shared secret which is only known to the federated supernodes and that specific edge. +In user-password scheme, the packets encrypted with the static key (REGISTER_SUPER, REGISTER_SUPER_ACK, useless for REGISTER_SUPER_NAK) are "signed" with an encrypted outer hash using the shared secret which is only known to the federated supernodes and that specific edge. #### Possible Extensions Tools for automizing [`.conf` file](https://github.com/ntop/n2n/blob/dev/doc/ConfigurationFiles.md) generation for deployment ot delivery to freshly registered and approved users could greatly enhance this ecosystem; a user would not have to mess around with command line parameters but just copy a `.conf` file into a specified directory. -Implementation of a federation-wide support for user-password authentication would allow much bigger networks using this feature. Still required steps are outlined [in some earlier post](https://github.com/ntop/n2n/issues/670#issuecomment-802119568). - Let us know if you are intrerested in implementing or furthering these ideas. diff --git a/include/n2n_typedefs.h b/include/n2n_typedefs.h index e122daa..0231b6f 100644 --- a/include/n2n_typedefs.h +++ b/include/n2n_typedefs.h @@ -356,6 +356,7 @@ typedef struct n2n_REGISTER_SUPER { n2n_ip_subnet_t dev_addr; /**< IP address of the tuntap adapter. */ n2n_desc_t dev_desc; /**< Hint description correlated with the edge */ n2n_auth_t auth; /**< Authentication scheme and tokens */ + uint32_t key_time; /**< key time for dynamic key, used between federatred supernodes only */ } n2n_REGISTER_SUPER_t; @@ -374,6 +375,8 @@ typedef struct n2n_REGISTER_SUPER_ACK { */ uint8_t num_sn; /**< Number of supernodes that were send * even if we cannot store them all. */ + + uint32_t key_time; /**< key time for dynamic key, used between federatred supernodes only */ } n2n_REGISTER_SUPER_ACK_t; @@ -748,7 +751,6 @@ struct sn_community { he_context_t *header_encryption_ctx_dynamic; /* Header encryption cipher context. */ he_context_t *header_iv_ctx_static; /* Header IV encryption cipher context, REMOVE as soon as separate fields for checksum and replay protection available */ he_context_t *header_iv_ctx_dynamic; /* Header IV encryption cipher context, REMOVE as soon as separate fields for checksum and replay protection available */ - uint32_t last_dynamic_key_time; /* UTC time of last dynamic key generation (second accuracy) */ uint8_t dynamic_key[N2N_AUTH_CHALLENGE_SIZE]; /* dynamic key */ struct peer_info *edges; /* Link list of registered edges. */ node_supernode_association_t *assoc; /* list of other edges from this community and their supernodes */ @@ -806,6 +808,7 @@ typedef struct n2n_sn { struct sn_community *federation; n2n_private_public_key_t private_key; /* private federation key derived from federation name */ n2n_auth_t auth; + uint32_t dynamic_key_time; /* UTC time of last dynamic key generation (second accuracy) */ uint8_t override_spoofing_protection; /* set if overriding MAC/IP spoofing protection (cli option '-M') */ n2n_resolve_parameter_t *resolve_parameter;/*Pointer to name resolver's parameter block */ } n2n_sn_t; diff --git a/src/auth.c b/src/auth.c index 595b465..cfdf936 100644 --- a/src/auth.c +++ b/src/auth.c @@ -145,27 +145,35 @@ int bind_private_key_to_username (n2n_private_public_key_t prv, uint8_t *usernam } -// calculate HASH( HASH³(time) ^ HASH²(comm) ^ HASH(fed) ) +// calculate SPECK ( plain = HASH³(time), key = HASH³(comm) ^ HASH³(fed) ) int calculate_dynamic_key (uint8_t out_key[N2N_AUTH_CHALLENGE_SIZE], uint32_t key_time, n2n_community_t comm, n2n_community_t fed) { - uint8_t tmp[N2N_AUTH_CHALLENGE_SIZE]; + uint8_t key[N2N_AUTH_CHALLENGE_SIZE]; + uint8_t tmp[N2N_AUTH_CHALLENGE_SIZE]; + speck_context_t *ctx; - memset(tmp, 0, N2N_AUTH_CHALLENGE_SIZE); - - // we know that N2N_AUTH_CHALLENGE_TYPE == 16, i.e. 128 bit that can take the hash value - pearson_hash_128(out_key, (uint8_t*)&key_time, sizeof(key_time)); - pearson_hash_128(out_key, out_key, N2N_AUTH_CHALLENGE_SIZE); - pearson_hash_128(out_key, out_key, N2N_AUTH_CHALLENGE_SIZE); - - pearson_hash_128(tmp, comm, sizeof(n2n_community_t)); - pearson_hash_128(tmp, tmp, N2N_AUTH_CHALLENGE_SIZE); - memxor(out_key, tmp, N2N_AUTH_CHALLENGE_SIZE); + // we know that N2N_AUTH_CHALLENGE_SIZE == 16, i.e. 128 bit that can take the hash value + pearson_hash_128(key, comm, sizeof(n2n_community_t)); + pearson_hash_128(key, key, N2N_AUTH_CHALLENGE_SIZE); + pearson_hash_128(key, key, N2N_AUTH_CHALLENGE_SIZE); pearson_hash_128(tmp, fed, sizeof(n2n_community_t)); - memxor(out_key, tmp, N2N_AUTH_CHALLENGE_SIZE); + pearson_hash_128(tmp, tmp, N2N_AUTH_CHALLENGE_SIZE); + pearson_hash_128(tmp, tmp, N2N_AUTH_CHALLENGE_SIZE); - pearson_hash_128(out_key, out_key, N2N_AUTH_CHALLENGE_SIZE); + memxor(key, tmp, N2N_AUTH_CHALLENGE_SIZE); + + ctx = (speck_context_t*)calloc(1, sizeof(speck_context_t)); + speck_init((speck_context_t**)&ctx, key, 128); + + pearson_hash_128(tmp, (uint8_t*)&key_time, sizeof(key_time)); + pearson_hash_128(tmp, tmp, N2N_AUTH_CHALLENGE_SIZE); + pearson_hash_128(out_key, tmp, N2N_AUTH_CHALLENGE_SIZE); + + speck_128_encrypt(out_key, ctx); + + free(ctx); return 0; } diff --git a/src/edge_utils.c b/src/edge_utils.c index eca5b59..d03ca8a 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -1233,7 +1233,7 @@ static int sort_supernodes (n2n_edge_t *eee, time_t now) { if(now - eee->last_sweep > SWEEP_TIME) { // this routine gets periodically called - if(eee->sn_wait == 0) { + if(!eee->sn_wait) { // sort supernodes in ascending order of their selection_criterion fields sn_selection_sort(&(eee->conf.supernodes)); } @@ -1249,6 +1249,7 @@ static int sort_supernodes (n2n_edge_t *eee, time_t now) { supernode_ip(eee), HASH_COUNT(eee->conf.supernodes), (unsigned int)eee->sup_attempts); send_register_super(eee); + eee->last_register_req = now; eee->sn_wait = 1; } @@ -1424,13 +1425,23 @@ void update_supernode_reg (n2n_edge_t * eee, time_t now) { struct peer_info *peer, *tmp_peer; int cnt = 0; + int off = 0; - if(eee->sn_wait && (now > (eee->last_register_req + (eee->conf.register_interval/10)))) { + if((eee->sn_wait && (now > (eee->last_register_req + (eee->conf.register_interval / 10)))) + ||(eee->sn_wait == 2)) /* immediately re-register in case of RE_REGISTER_SUPER */ { /* fall through */ traceEvent(TRACE_DEBUG, "update_supernode_reg: doing fast retry."); } else if(now < (eee->last_register_req + eee->conf.register_interval)) return; /* Too early */ + // determine time offset to apply on last_register_req for + // all edges's next re-registration does not happen all at once + if (eee->sn_wait == 2) { + // remaining 1/4 is greater than 1/10 fast retry allowance; + // '%' might be expensive but does not happen all too often + off = n2n_rand() % ((eee->conf.register_interval * 3) / 4); + } + check_join_multicast_group(eee); if(0 == eee->sup_attempts) { @@ -1490,14 +1501,14 @@ void update_supernode_reg (n2n_edge_t * eee, time_t now) { // if supernode repeatedly not responding (already waiting), safeguard the // current known connections to peers by re-registering - if(eee->sn_wait) + if(eee->sn_wait == 1) HASH_ITER(hh, eee->known_peers, peer, tmp_peer) if((now - peer->last_seen) > REGISTER_SUPER_INTERVAL_DFL) send_register(eee, &(peer->sock), peer->mac_addr); eee->sn_wait = 1; - eee->last_register_req = now; + eee->last_register_req = now - off; } /* ************************************** */ @@ -2321,7 +2332,6 @@ void process_udp (n2n_edge_t *eee, const struct sockaddr_in *sender_sock, const (char *)eee->conf.community_name, eee->conf.header_encryption_ctx_static, eee->conf.header_iv_ctx_static, &stamp); - header_enc = 1; } if(!header_enc) { traceEvent(TRACE_DEBUG, "readFromIPSocket failed to decrypt header."); @@ -2741,7 +2751,7 @@ void process_udp (n2n_edge_t *eee, const struct sockaddr_in *sender_sock, const traceEvent(TRACE_INFO, "Rx RE_REGISTER_SUPER"); - eee->sn_wait = 1; + eee->sn_wait = 2; /* immediately */ break; } diff --git a/src/sn_selection.c b/src/sn_selection.c index d17518b..803e026 100644 --- a/src/sn_selection.c +++ b/src/sn_selection.c @@ -162,8 +162,11 @@ extern char * sn_selection_criterion_str (selection_criterion_str_t out, peer_in return NULL; } memset(out, 0, SN_SELECTION_CRITERION_BUF_SIZE); - - if(peer->selection_criterion >= 0) { + + // keep off the super-big values (used for "bad" or "good" or "undetermined" supernodes, + // easier to sort to the end of the list). + // Alternatively, typecast to (int16_t) and check for greater or equal zero + if(peer->selection_criterion < (UINT32_MAX >> 2)) { #ifndef SN_SELECTION_RTT snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE, "load = %8d", peer->selection_criterion); #else diff --git a/src/sn_utils.c b/src/sn_utils.c index 1c556ec..4396569 100644 --- a/src/sn_utils.c +++ b/src/sn_utils.c @@ -45,6 +45,12 @@ static int update_edge (n2n_sn_t *sss, int skip_add, time_t now); +static int re_register_and_purge_supernodes (n2n_sn_t *sss, + struct sn_community *comm, + time_t *p_last_re_reg_and_purge, + time_t now, + uint8_t forced); + static int purge_expired_communities (n2n_sn_t *sss, time_t* p_last_purge, time_t now); @@ -129,6 +135,76 @@ void calculate_shared_secrets (n2n_sn_t *sss) { } +// calculate dynamic keys +void calculate_dynamic_keys (n2n_sn_t *sss) { + + struct sn_community *comm, *tmp_comm = NULL; + + traceEvent(TRACE_INFO, "calculating dynamic keys"); + HASH_ITER(hh, sss->communities, comm, tmp_comm) { + // skip federation + if(comm->is_federation) { + continue; + } + + // calculate dynamic keys if this is a user/pw auth'ed community + if(comm->allowed_users) { + calculate_dynamic_key(comm->dynamic_key, /* destination */ + sss->dynamic_key_time, /* time - same for all */ + comm->community, /* community name */ + sss->federation->community); /* federation name */ + packet_header_change_dynamic_key(comm->dynamic_key, + &(comm->header_encryption_ctx_dynamic), + &(comm->header_iv_ctx_dynamic)); + traceEvent(TRACE_DEBUG, "calculated dynamic key for community '%s'", comm->community); + } + } +} + + +// send RE_REGISTER_SUPER to all edges from user/pw auth'ed communites +void send_re_register_super (n2n_sn_t *sss) { + + struct sn_community *comm, *tmp_comm = NULL; + struct peer_info *edge, *tmp_edge = NULL; + n2n_common_t cmn; + uint8_t rereg_buf[N2N_SN_PKTBUF_SIZE]; + size_t encx = 0; + n2n_sock_str_t sockbuf; + + HASH_ITER(hh, sss->communities, comm, tmp_comm) { + if(comm->is_federation) { + continue; + } + + // send RE_REGISTER_SUPER to edges if this is a user/pw auth community + if(comm->allowed_users) { + // prepare + cmn.ttl = N2N_DEFAULT_TTL; + cmn.pc = n2n_re_register_super; + cmn.flags = N2N_FLAGS_FROM_SUPERNODE; + memcpy(cmn.community, comm->community, N2N_COMMUNITY_SIZE); + + HASH_ITER(hh, comm->edges, edge, tmp_edge) { + // encode + encx = 0; + encode_common(rereg_buf, &encx, &cmn); + + // send + traceEvent(TRACE_DEBUG, "send RE_REGISTER_SUPER to %s", + sock_to_cstr(sockbuf, &(edge->sock))); + + packet_header_encrypt(rereg_buf, encx, encx, + comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic, + time_stamp()); + + /* sent = */ sendto_peer(sss, edge, rereg_buf, encx); + } + } + } +} + + /** Load the list of allowed communities. Existing/previous ones will be removed, * return 0 on success, -1 if file not found, -2 if no valid entries found */ @@ -151,11 +227,7 @@ int load_allowed_sn_community (n2n_sn_t *sss) { struct peer_info *edge, *tmp_edge; node_supernode_association_t *assoc, *tmp_assoc; n2n_tcp_connection_t *conn; - - n2n_common_t cmn; - uint8_t rereg_buf[N2N_SN_PKTBUF_SIZE]; - size_t encx = 0; - n2n_sock_str_t sockbuf; + time_t any_time = 0; uint32_t num_communities = 0; @@ -168,34 +240,18 @@ int load_allowed_sn_community (n2n_sn_t *sss) { return -1; } + // reset data structures ------------------------------ + + // send RE_REGISTER_SUPER to all edges from user/pw auth communites, this is safe because + // follow-up REGISTER_SUPER cannot be handled before this function ends + send_re_register_super(sss); + // remove communities (not: federation) HASH_ITER(hh, sss->communities, comm, tmp_comm) { if(comm->is_federation) { continue; } - // send RE_REGISTER_SUPER to edges if this is a user/pw auth community - if(comm->allowed_users) { - // prepare - cmn.ttl = N2N_DEFAULT_TTL; - cmn.pc = n2n_re_register_super; - cmn.flags = N2N_FLAGS_FROM_SUPERNODE; - memcpy(cmn.community, comm->community, N2N_COMMUNITY_SIZE); - encode_common(rereg_buf, &encx, &cmn); - - HASH_ITER(hh, comm->edges, edge, tmp_edge) { - // send - traceEvent(TRACE_DEBUG, "send RE_REGISTER_SUPER to %s", - sock_to_cstr(sockbuf, &(edge->sock))); - - packet_header_encrypt(rereg_buf, encx, encx, - comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic, - time_stamp()); - - /* sent = */ sendto_peer(sss, edge, rereg_buf, encx); - } - } - // remove all edges from community HASH_ITER(hh, comm->edges, edge, tmp_edge) { // remove all edge associations (with other supernodes) @@ -240,6 +296,14 @@ int load_allowed_sn_community (n2n_sn_t *sss) { free(re); } + // prepare reading data ------------------------------- + + // new key_time for all communities, requires dynamic keys to be recalculated (see further below), + // and edges to re-register (see above) and ... + sss->dynamic_key_time = time(NULL); + // ... federated supernodes to re-register + re_register_and_purge_supernodes(sss, sss->federation, &any_time, any_time, 1 /* forced */); + // format definition for possible user-key entries sprintf(format, "%c %%%ds %%%ds", N2N_USER_KEY_LINE_STARTER, N2N_DESC_SIZE - 1, sizeof(ascii_public_key)-1); @@ -285,16 +349,7 @@ int load_allowed_sn_community (n2n_sn_t *sss) { &(last_added_comm->header_encryption_ctx_dynamic), &(last_added_comm->header_iv_ctx_static), &(last_added_comm->header_iv_ctx_dynamic)); - // dynamic key setup - last_added_comm->last_dynamic_key_time = time(NULL); - calculate_dynamic_key(last_added_comm->dynamic_key, /* destination */ - last_added_comm->last_dynamic_key_time, /* time */ - last_added_comm->community, /* community name */ - sss->federation->community); /* federation name */ - packet_header_change_dynamic_key(last_added_comm->dynamic_key, - &(last_added_comm->header_encryption_ctx_dynamic), - &(last_added_comm->header_iv_ctx_dynamic)); - + // dynamic key setup follows at a later point in code } continue; } @@ -391,7 +446,13 @@ int load_allowed_sn_community (n2n_sn_t *sss) { traceEvent(TRACE_NORMAL, "Loaded %u regular expressions for community name matching from %s", num_regex, sss->community_file); - /* No new communities will be allowed */ + // calculate allowed user's shared secrets (shared with federation) + calculate_shared_secrets(sss); + + // calculcate communties' dynamic keys + calculate_dynamic_keys(sss); + + // no new communities will be allowed sss->lock_communities = 1; return 0; @@ -1241,26 +1302,30 @@ static int find_edge_time_stamp_and_verify (struct peer_info * edges, } -static int re_register_and_purge_supernodes (n2n_sn_t *sss, struct sn_community *comm, time_t *p_last_re_reg_and_purge, time_t now) { +static int re_register_and_purge_supernodes (n2n_sn_t *sss, struct sn_community *comm, time_t *p_last_re_reg_and_purge, time_t now, uint8_t forced) { time_t time; struct peer_info *peer, *tmp; - if((now - (*p_last_re_reg_and_purge)) < RE_REG_AND_PURGE_FREQUENCY) { - return 0; - } + if(!forced) { + if((now - (*p_last_re_reg_and_purge)) < RE_REG_AND_PURGE_FREQUENCY) { + return 0; + } - // purge long-time-not-seen supernodes - purge_expired_nodes(&(comm->edges), sss->sock, &sss->tcp_connections, p_last_re_reg_and_purge, - RE_REG_AND_PURGE_FREQUENCY, LAST_SEEN_SN_INACTIVE); + // purge long-time-not-seen supernodes + purge_expired_nodes(&(comm->edges), sss->sock, &sss->tcp_connections, p_last_re_reg_and_purge, + RE_REG_AND_PURGE_FREQUENCY, LAST_SEEN_SN_INACTIVE); + } if(comm != NULL) { HASH_ITER(hh,comm->edges,peer,tmp) { time = now - peer->last_seen; - if(time <= LAST_SEEN_SN_ACTIVE) { - continue; + if(!forced) { + if(time <= LAST_SEEN_SN_ACTIVE) { + continue; + } } /* re-register (send REGISTER_SUPER) */ @@ -1288,6 +1353,8 @@ static int re_register_and_purge_supernodes (n2n_sn_t *sss, struct sn_community reg.dev_addr.net_bitlen = mask2bitlen(ntohl(peer->dev_addr.net_bitlen)); get_local_auth(sss, &(reg.auth)); + reg.key_time = sss->dynamic_key_time; + idx = 0; encode_mac(reg.edgeMac, &idx, sss->mac_addr); @@ -1447,7 +1514,6 @@ static int process_mgmt (n2n_sn_t *sss, sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); return 0; /* no status output afterwards */ } - calculate_shared_secrets(sss); ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, "OK.\n"); sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); @@ -1584,6 +1650,7 @@ static int process_udp (n2n_sn_t * sss, uint32_t header_enc = 0; /* 1 == encrypted by static key, 2 == encrypted by dynamic key */ uint64_t stamp; int skip_add; + time_t any_time = 0; traceEvent(TRACE_DEBUG, "Processing incoming UDP packet [len: %lu][sender: %s:%u]", udp_size, intoa(ntohl(sender_sock->sin_addr.s_addr), buf, sizeof(buf)), @@ -2055,6 +2122,7 @@ static int process_udp (n2n_sn_t * sss, macaddr_str(mac_buf, reg.edgeMac), sock_to_cstr(sockbuf, &(ack.sock))); + // check authentication ret_value = update_edge_no_change; if(comm->is_federation != IS_FEDERATION) { /* REVISIT: auth among supernodes is not implemented yet */ if(cmn.flags & N2N_FLAGS_SOCKET) { @@ -2066,6 +2134,7 @@ static int process_udp (n2n_sn_t * sss, } if(ret_value == update_edge_auth_fail) { + // send REGISTER_SUPER_NAK cmn2.pc = n2n_register_super_nak; memcpy(&(nak.cookie), &(reg.cookie), sizeof(n2n_cookie_t)); memcpy(nak.srcMac, reg.edgeMac, sizeof(n2n_mac_t)); @@ -2103,11 +2172,36 @@ static int process_udp (n2n_sn_t * sss, packet_header_encrypt(ackbuf, encx, encx, comm->header_encryption_ctx_static, comm->header_iv_ctx_static, time_stamp()); + // if user-password-auth + if(comm->allowed_users) { + // append an encrypted packet hash + pearson_hash_128(hash_buf, ackbuf, encx); + // same 'user' as above + speck_128_encrypt(hash_buf, (speck_context_t*)user->shared_secret_ctx); + encode_buf(ackbuf, &encx, hash_buf, N2N_REG_SUP_HASH_CHECK_LEN); + } } try_broadcast(sss, NULL, &cmn, reg.edgeMac, from_supernode, ackbuf, encx); } + // dynamic key time handling if appropriate + ack.key_time = 0; + if(comm->is_federation == IS_FEDERATION) { + if(reg.key_time > sss->dynamic_key_time) { + traceEvent(TRACE_DEBUG, "setting new key time"); + // have all edges re_register (using old dynamic key) + send_re_register_super(sss); + // set new key time + sss->dynamic_key_time = reg.key_time; + // calculate new dynamic keys for all communities + calculate_dynamic_keys(sss); + // force re-register with all supernodes + re_register_and_purge_supernodes(sss, sss->federation, &any_time, now, 1 /* forced */); + } + ack.key_time = sss->dynamic_key_time; + } + // send REGISTER_SUPER_ACK encx = 0; cmn2.pc = n2n_register_super_ack; @@ -2256,8 +2350,8 @@ static int process_udp (n2n_sn_t * sss, } if(0 == memcmp(ack.cookie, scan->last_cookie, N2N_COOKIE_SIZE)) { - payload = (n2n_REGISTER_SUPER_ACK_payload_t *)dec_tmpbuf; + payload = (n2n_REGISTER_SUPER_ACK_payload_t *)dec_tmpbuf; for(i = 0; i < ack.num_sn; i++) { skip_add = SN_ADD; tmp = add_sn_to_list_by_mac_or_sock(&(sss->federation->edges), &(payload->sock), payload->mac, &skip_add); @@ -2271,6 +2365,19 @@ static int process_udp (n2n_sn_t * sss, // shift to next payload entry payload++; } + + if(ack.key_time > sss->dynamic_key_time) { + traceEvent(TRACE_DEBUG, "setting new key time"); + // have all edges re_register (using old dynamic key) + send_re_register_super(sss); + // set new key time + sss->dynamic_key_time = ack.key_time; + // calculate new dynamic keys for all communities + calculate_dynamic_keys(sss); + // force re-register with all supernodes + re_register_and_purge_supernodes(sss, sss->federation, &any_time, now, 1 /* forced */); + } + } else { traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_ACK with wrong or old cookie."); } @@ -2759,7 +2866,7 @@ int run_sn_loop (n2n_sn_t *sss, int *keep_running) { traceEvent(TRACE_DEBUG, "timeout"); } - re_register_and_purge_supernodes(sss, sss->federation, &last_re_reg_and_purge, now); + re_register_and_purge_supernodes(sss, sss->federation, &last_re_reg_and_purge, now, 0 /* not forced */); purge_expired_communities(sss, &last_purge_edges, now); sort_communities(sss, &last_sort_communities, now); resolve_check(sss->resolve_parameter, 0 /* presumably, no special resolution requirement */, now); diff --git a/src/wire.c b/src/wire.c index 04a8990..c018eb0 100644 --- a/src/wire.c +++ b/src/wire.c @@ -356,6 +356,7 @@ int encode_REGISTER_SUPER (uint8_t *base, retval += encode_uint16(base, idx, reg->auth.scheme); retval += encode_uint16(base, idx, reg->auth.token_size); retval += encode_buf(base, idx, reg->auth.token, reg->auth.token_size); + retval += encode_uint32(base, idx, reg->key_time); return retval; } @@ -381,6 +382,7 @@ int decode_REGISTER_SUPER (n2n_REGISTER_SUPER_t *reg, retval += decode_uint16(&(reg->auth.scheme), base, rem, idx); retval += decode_uint16(&(reg->auth.token_size), base, rem, idx); retval += decode_buf(reg->auth.token, reg->auth.token_size, base, rem, idx); + retval += decode_uint32(&(reg->key_time), base, rem, idx); return retval; } @@ -492,6 +494,8 @@ int encode_REGISTER_SUPER_ACK (uint8_t *base, retval += encode_uint8(base, idx, reg->num_sn); retval += encode_buf(base, idx, tmpbuf, (reg->num_sn*REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE)); + retval += encode_uint32(base, idx, reg->key_time); + return retval; } @@ -523,6 +527,8 @@ int decode_REGISTER_SUPER_ACK (n2n_REGISTER_SUPER_ACK_t *reg, retval += decode_uint8(&(reg->num_sn), base, rem, idx); retval += decode_buf(tmpbuf, (reg->num_sn * REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE), base, rem, idx); + retval += decode_uint32(&(reg->key_time), base, rem, idx); + return retval; }