diff --git a/doc/Crypto.md b/doc/Crypto.md index a404c7f..83e116a 100644 --- a/doc/Crypto.md +++ b/doc/Crypto.md @@ -72,7 +72,7 @@ Its initialization relies on seeding with a value as random as possible. Various For general purpose hashing, n2n employs [Pearson Block Hashing](https://github.com/Logan007/pearsonB) as it offers variable hash sizes and is said not to be too "collidy". However, this is not a cryptographically secure hashing function which by the way is not required here: The hashing is never applied in a way that the hash value shall publically prove the knowledge of a secret without showing the secret itself. -_Pearson hashing is tweakable by using your own block-sized permutation._ Here, we use a three-round xor-rotate-multiply permutation scheme on 64-bit wide integer numbers with constants discovered by [David Stafford](http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html) (`mix13`, permission obtained via eMail) which, these days, is better known as part of `splitmix64()`. +_Pearson hashing is tweakable by using your own block-sized permutation._ Here, we use a three-round xor-rotate-multiply permutation scheme on 64-bit wide integer numbers with constants discovered by [David Stafford](http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html) (`mix13`, permission obtained via eMail) which, meanwhile, is better known as part of `splitmix64()`. _Pearson hashing allows verification of block-sized parts of the hash only – just in case performance requirements would urge to do so._ @@ -87,7 +87,7 @@ The COMMON section is built as follows: ``` 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - ! Version=3 ! TTL ! Flags ! + ! Version = 3 ! TTL ! Flags ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 4 ! Community : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -116,16 +116,18 @@ In case of a PACKET-type, it is succeeded by the fields depicted below: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 40 ! Compress'n ID ! Transform ID ! Payload ... ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + -44 ! +44 ! ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... ``` + ### Encryption If enabled (`-H`), all fields but the payload (which is handled seperately as outlined above) get encrypted using SPECK in CTR mode. As packet headers need to be decryptable by the supernode and we do not want to add another key (to keep it a simple interface), the community name serves as key (keep it secret!) because it is already known to the supernode. The community name consists of up to 16 characters (well, 15 + `0x00`), so key size of 128 bit is a reasonable choice here. The scheme applied tries to maintain compatibility with current packet format and works as follows: -- First line of 4 bytes (Version, TTL, Flags) goes to fifth line: +- First line of 4 bytes (Version, TTL, Flags) goes to fifth line: + ``` 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -137,12 +139,13 @@ The scheme applied tries to maintain compatibility with current packet format an +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 ! ... Community ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -16 ! Version=3 ! TTL ! Flags ! +16 ! Version = 3 ! TTL ! Flags ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` -- To be able to identify a correctly decrpyted header later on, a magic number is stamped in fourth line starting at byte number 12. We use "n2n" string and add the header length to be able to stop header decryption right before an eventually following payload begins – in case of PACKET-type, header-length does not equal packet-length. +- To be able to identify a correctly decrpyted header later on, a magic number is stamped in fourth line starting at byte number 12. We use "n2" string and add the 16-bit header length to be able to stop header decryption right before an eventually following ethernet data payload begins – in case of PACKET-type, header-length does not equal packet-length. 16-bit length is required because REGISTER_SUPER_ACK packets consist of header only and could grow quite large due to their payload (other supernodes of federation) – don't mix up this kind of payload (part of the header) with the ethernet data payload of PACKET messages. + +- The rest of the community field, namely the first 12 bytes, is reframed towards a 96-bit IV for the header encryption. -- The rest of the community field, namely the first 12 bytes, is reframed towards a 96-bit IV for the header encryption. ``` 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -152,42 +155,46 @@ The scheme applied tries to maintain compatibility with current packet format an +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 8 ! ... IV : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -12 ! 24-bit Magic Number, "n2n" = 0x6E326E ! Header Length ! +12 ! Magic Number "n2" = 0x6E32 ! Header Length ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -16 ! Version=3 ! TTL ! Flags ! +16 ! Version = 3 ! TTL ! Flags ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` -- As we use a stream cipher, the IV should be a nonce. The IV plays an additional role sketched later, see the following sections on checksum and replay protection. For use in header encryption and decryption, four bytes reading ASCII "n2n!" are appended to the 96-bit IV hereby internally making it a full 128-bit IV. +- As we use a stream cipher, the IV should be a nonce. The IV plays an additional role sketched later, see the following sections on checksum and replay protection. For use in header encryption and decryption, four bytes reading ASCII "n2n!" are appended to the transmitted 96-bit IV hereby internally making it a full 128-bit IV. - To make a less predictable use of the key space – just think of the typically reset MSB of ASCII characters of community names – we actually use a hash of the community name as key. -- Encryption starts at byte number 12 and ends at header's end. It does not comprise the payload which eventually has its own encryption scheme as chosen with the `-A_` options. +- Encryption starts at byte number 12 and ends at header's end. It does not comprise PACKET's ethernet data payload which eventually has its own encryption scheme as chosen with the `-A_` options. -Decryption checks all known communities (several in case of supernode, only one at edge) as keys. On success, the emerging magic number will reveal the correct community whose name will be copied back to the original fields allowing for regular packet handling. +Decryption checks all known communities (several in case of supernode, only one at edge) as keys. On success, the emerging magic number along with a reasonable header's length value will reveal the correct community whose name will be copied back to the original fields allowing for regular packet handling. Thus, header encryption will only work with previously determined community names introduced to the supernode by `-c ` parameter. Also, it should be clear that header encryption is a per-community decision, i.e. all nodes and the supernode need to have it enabled. However, the supernode supports encrpyted and unencrypted communities in parallel, it determines their status online at arrival of the first packet. Use a fresh community name for encrypted communities; do not use a previously used one of former unecrpyted communities: their names were transmitted openly. ### Checksum -The whole packet including the eventually present payload is checksummed using a modified Person hashing. It might seem a little short compared to usual message tags of 96 up to 128 bit, especially when using a stream cipher which easily allows for bit-flips. So, the 16-bit checksum is filled up with 80 more bits to obtain a 96-bit pre-IV. This pre-IV gets encrypted using a single block-cipher step to get the pseudo-random looking IV. This way, the checksum resists targeted bit-flips (to header, payload, and IV) as any change to the whole 96-bit IV would render the header un-decryptable. Also, as explained below, the checksum comes along with a time stamp minimizing opportunities for random attacks. +The whole packet including the eventually present payload is checksummed using a Person block hashing scheme. It might seem a little short compared to usual message tags of 96 up to 128 bit, especially when using a stream cipher which easily allows for bit-flips. So, the 64-bit checksum is exclusive-ored with a 64-bit time stamp and filled up with 32 more random bits to obtain a 96-bit pre-IV. This pre-IV gets encrypted using a single block-cipher step to get the pseudo-random looking IV. This way, the checksum resists targeted bit-flips (to header, payload, and IV) as any change to the whole 96-bit IV would render the header un-decryptable. Also, as explained below, the checksum comes along with a time stamp minimizing opportunities for random attacks. The single block-cipher step employs SPECK because it is fast and it offers a 96-bit version. The key is derived from the header key – a hash of the hash. -The checksum gets verified by the edges and the supernode. +The checksum is calculated by the edges and the supernode. Changes to the payload will cause a different locally calculated checksum. Extracting the time stamp by exclusive-oring an errorneous checksum will lead to an invalid timestamp. So, checksum errors are indirectly detected when checking for a valid time stamp. ### Replay Protection -The aforementioned fill-up does not completely rely on random bits. A 52-bit time stamp displaying a microsecond-accuracy is encoded to the 96-bit pre-IV as well: +The aforementioned 96-bit pre-IV can be depicted as follows: ``` 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 +------------------------------------------------------------------------------------------------+ - ! 52-bit time stamp with microsecond-accuracy ! 28 pseudo-random bits !16-bit checksum! + ! 64-bit checksum of the whole packet ! ! + + - - - - - - - - - - - - - - - - - - - - - - - XOR - - - - - - -! 32 pseudo-random bits ! + ! 52-bit time stamp with microsecond-accuracy ! 0x00 ! F ! ! +------------------------------------------------------------------------------------------------+ - ``` -Encrypting this pre-IV with a block cipher step will generate a pseudo-random looking IV which gets written to the packet and used for the header encryption. + +The time stamp consists of the 52-bit microsecond value, a 4-bit flag field F (accuracy indicator, other header encryption features – still under development), and is filled up with 8 zero-bits in between. + +Encrypting this pre-IV using a block cipher step will generate a pseudo-random looking IV which gets written to the packet and used for the header encryption. Due to the time-stamp encoded, the IV will more likely be unique, close to a real nonce. @@ -195,8 +202,8 @@ Upon receival, the time stamp as well as the checksum can be extracted from the - The (remote) time stamp is checked against the local clock. It may not deviate more than plus/minus 16 seconds. So, edges and supernode need to keep a somewhat current time. This limit can be adjusted by changing the `TIME_STAMP_FRAME` definition. It is time-zone indifferent as UTC is used. -- However, the systemic packets such as REGISTER_SUPER are not allowed any time stamp jitter because n2n relies on the actual sender's socket. A replay from another IP within any allowed jitter time frame would deviate the traffic which shall be prevented (even if it remains undecryptable). Under absolutely rare (!) circumstances, this might cause a re-registration requirement which happens automatically but might cause a small delay – security (including network availability) first! - - Valid (remote) time stamps get stored as "last valid time stamp" seen from each node (supernode and edges). So, a newly arriving packet's time stamp can be compared to the last valid one. It should be equal or higher. However, as UDP packets may overtake each other just by taking another path through the internet, they are allowed to be 160 millisecond earlier than the last valid one. This limit can be adjusted by changing the `TIME_STAMP_JITTER` definition. +- However, the systemic packets such as REGISTER_SUPER are not allowed any time stamp jitter because n2n relies on the actual sender's socket. A replay from another IP within any allowed jitter time frame would deviate the traffic which shall be prevented (even if it remains undecryptable). Under absolutely rare (!) circumstances, this might cause a re-registration requirement which happens automatically but might cause a small delay – security (including network availability) first! + The way the IV is used for replay protection and for checksumming makes enabled header encryption a prerequisite for these features. diff --git a/include/header_encryption.h b/include/header_encryption.h index 74d8c02..dfb96a3 100644 --- a/include/header_encryption.h +++ b/include/header_encryption.h @@ -1,5 +1,5 @@ /** - * (C) 2007-20 - ntop.org and contributors + * (C) 2007-21 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,14 +17,14 @@ */ -uint32_t packet_header_decrypt (uint8_t packet[], uint16_t packet_len, - char *community_name, he_context_t *ctx, - he_context_t *ctx_iv, - uint64_t *stamp, uint16_t *checksum); +int packet_header_decrypt (uint8_t packet[], uint16_t packet_len, + char *community_name, + he_context_t *ctx, he_context_t *ctx_iv, + uint64_t *stamp); -int32_t packet_header_encrypt (uint8_t packet[], uint8_t header_len, he_context_t *ctx, - he_context_t *ctx_iv, - uint64_t stamp, uint16_t checksum); +int packet_header_encrypt (uint8_t packet[], uint16_t header_len, uint16_t packet_len, + he_context_t *ctx, he_context_t *ctx_iv, + uint64_t stamp); -void packet_header_setup_key (const char *community_name, he_context_t **ctx, - he_context_t **ctx_iv); +void packet_header_setup_key (const char *community_name, + he_context_t **ctx, he_context_t **ctx_iv); diff --git a/include/n2n_define.h b/include/n2n_define.h index 044c8df..5e38675 100644 --- a/include/n2n_define.h +++ b/include/n2n_define.h @@ -1,5 +1,5 @@ /** - * (C) 2007-20 - ntop.org and contributors + * (C) 2007-21 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,8 +32,8 @@ /* Max available space to add supernodes' informations (sockets and MACs) in REGISTER_SUPER_ACK * Field sizes of REGISTER_SUPER_ACK as used in encode/decode fucntions in src/wire.c - * REVISIT: replace 255 by DEFAULT_MTU as soon as header encryption allows for longer packets to be encrypted. */ -#define REG_SUPER_ACK_PAYLOAD_SPACE (255 - (sizeof(n2n_common_t) + sizeof(n2n_REGISTER_SUPER_ACK_t))) + */ +#define REG_SUPER_ACK_PAYLOAD_SPACE (DEFAULT_MTU - (sizeof(n2n_common_t) + sizeof(n2n_REGISTER_SUPER_ACK_t))) /* Space needed to store socket and MAC address of a supernode */ #define REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE (sizeof(n2n_REGISTER_SUPER_ACK_payload_t)) @@ -68,7 +68,7 @@ /* parameters for replay protection */ #define TIME_STAMP_FRAME 0x0000001000000000LL /* clocks of different computers are allowed +/- 16 seconds to be off */ #define TIME_STAMP_JITTER 0x0000000027100000LL /* we allow a packet to arrive 160 ms (== 0x27100 us) before another - * set to 0x0000000000000000LL if increasing (or equal) time stamps allowed only */ + * set to 0x0000000000000000LL if increasing (or equal) time stamps allowed only */ #define TIME_STAMP_ALLOW_JITTER 1 /* constant for allowing or... */ #define TIME_STAMP_NO_JITTER 0 /* not allowing jitter to be considered */ diff --git a/src/edge_utils.c b/src/edge_utils.c index 48d3607..f548937 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -1,5 +1,5 @@ /** - * (C) 2007-20 - ntop.org and contributors + * (C) 2007-21 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -793,9 +793,9 @@ static void send_query_peer (n2n_edge_t * eee, traceEvent(TRACE_DEBUG, "send QUERY_PEER to supernode"); if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(pktbuf, idx, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - time_stamp (), pearson_hash_16 (pktbuf, idx)); + packet_header_encrypt(pktbuf, idx, idx, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + time_stamp ()); } sendto_sock(eee->udp_sock, pktbuf, idx, &(eee->supernode)); @@ -804,9 +804,9 @@ static void send_query_peer (n2n_edge_t * eee, traceEvent(TRACE_DEBUG, "send PING to supernodes"); if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(pktbuf, idx, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - time_stamp (), pearson_hash_16 (pktbuf, idx)); + packet_header_encrypt(pktbuf, idx, idx, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + time_stamp ()); } HASH_ITER(hh, eee->conf.supernodes, peer, tmp) { @@ -855,9 +855,9 @@ static void send_register_super (n2n_edge_t *eee) { sock_to_cstr(sockbuf, &(eee->curr_sn->sock))); if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) - packet_header_encrypt(pktbuf, idx, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - time_stamp(), pearson_hash_16(pktbuf, idx)); + packet_header_encrypt(pktbuf, idx, idx, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + time_stamp()); /* sent = */ sendto_sock(eee->udp_sock, pktbuf, idx, &(eee->curr_sn->sock)); } @@ -892,9 +892,9 @@ static void send_unregister_super (n2n_edge_t *eee) { sock_to_cstr(sockbuf, &(eee->curr_sn->sock))); if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) - packet_header_encrypt(pktbuf, idx, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - time_stamp(), pearson_hash_16(pktbuf, idx)); + packet_header_encrypt(pktbuf, idx, idx, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + time_stamp()); /* sent = */ sendto_sock(eee->udp_sock, pktbuf, idx, &(eee->curr_sn->sock)); @@ -985,9 +985,9 @@ static void send_register (n2n_edge_t * eee, sock_to_cstr(sockbuf, remote_peer)); if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) - packet_header_encrypt(pktbuf, idx, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - time_stamp(), pearson_hash_16(pktbuf, idx)); + packet_header_encrypt(pktbuf, idx, idx, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + time_stamp()); /* sent = */ sendto_sock(eee->udp_sock, pktbuf, idx, remote_peer); } @@ -1030,9 +1030,9 @@ static void send_register_ack (n2n_edge_t * eee, sock_to_cstr(sockbuf, remote_peer)); if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) - packet_header_encrypt(pktbuf, idx, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - time_stamp(), pearson_hash_16(pktbuf, idx)); + packet_header_encrypt(pktbuf, idx, idx, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + time_stamp()); /* sent = */ sendto_sock(eee->udp_sock, pktbuf, idx, remote_peer); } @@ -1797,9 +1797,9 @@ void edge_send_packet2net (n2n_edge_t * eee, (u_int)idx, (u_int)len, (u_int)(idx - len), tx_transop_idx); if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) - packet_header_encrypt(pktbuf, headerIdx, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - time_stamp(), pearson_hash_16(pktbuf, idx)); + packet_header_encrypt(pktbuf, headerIdx, idx, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + time_stamp()); #ifdef MTU_ASSERT_VALUE { @@ -1934,9 +1934,10 @@ void readFromIPSocket (n2n_edge_t * eee, int in_sock) { if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) { uint16_t checksum = 0; - if(packet_header_decrypt(udp_buf, recvlen, (char *)eee->conf.community_name, eee->conf.header_encryption_ctx, - eee->conf.header_iv_ctx, - &stamp, &checksum) == 0) { + if(packet_header_decrypt(udp_buf, recvlen, + (char *)eee->conf.community_name, + eee->conf.header_encryption_ctx, eee->conf.header_iv_ctx, + &stamp) == 0) { traceEvent(TRACE_DEBUG, "readFromIPSocket failed to decrypt header."); return; } @@ -1944,11 +1945,6 @@ void readFromIPSocket (n2n_edge_t * eee, int in_sock) { // time stamp verification follows in the packet specific section as it requires to determine the // sender from the hash list by its MAC, or the packet might be from the supernode, this all depends // on packet type, path taken (via supernode) and packet structure (MAC is not always in the same place) - - if(checksum != pearson_hash_16(udp_buf, recvlen)) { - traceEvent(TRACE_DEBUG, "readFromIPSocket dropped packet due to checksum error."); - return; - } } rem = recvlen; /* Counts down bytes of packet to protect against buffer overruns. */ diff --git a/src/header_encryption.c b/src/header_encryption.c index 1813d74..318ca2f 100644 --- a/src/header_encryption.c +++ b/src/header_encryption.c @@ -1,5 +1,5 @@ /** - * (C) 2007-20 - ntop.org and contributors + * (C) 2007-21 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,39 +23,46 @@ #define HASH_FIND_COMMUNITY(head, name, out) HASH_FIND_STR(head, name, out) -uint32_t packet_header_decrypt (uint8_t packet[], uint16_t packet_len, - char *community_name, he_context_t *ctx, - he_context_t *ctx_iv, uint64_t *stamp, uint16_t *checksum) { +int packet_header_decrypt (uint8_t packet[], uint16_t packet_len, + char *community_name, + he_context_t *ctx, he_context_t *ctx_iv, + uint64_t *stamp) { // assemble IV // the last four are ASCII "n2n!" and do not get overwritten uint8_t iv[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x32, 0x6E, 0x21 }; - // the first 96 bits of the packet get padded with ASCII "n2n!" - // to full 128 bit IV + // the first 96 bits of the packet get padded with ASCII "n2n!" to full 128 bit IV memcpy(iv, packet, 12); - // try community name as possible key and check for magic bytes - uint32_t magic = 0x6E326E00; // ="n2n_" + // try community name as possible key and check for magic bytes "n2__" + uint32_t magic = 0x6E320000; uint32_t test_magic; - // check for magic bytes and reasonable value in header len field + + // check for magic // so, as a first step, decrypt 4 bytes only starting at byte 12 speck_ctr((uint8_t*)&test_magic, &packet[12], 4, iv, (speck_context_t*)ctx); test_magic = be32toh(test_magic); - if((((test_magic >> 8) << 8) == magic) /* check the thre uppermost bytes */ - && (((uint8_t)test_magic) <= packet_len)) { /* lowest 8 bit of test_magic are header_len */ - // decrypt the complete header - speck_ctr(&packet[12], &packet[12], (uint8_t)(test_magic) - 12, iv, (speck_context_t*)ctx); - // extract time stamp (first 64 bit) and checksum (last 16 bit) blended in IV - speck_96_decrypt(iv, (speck_context_t*)ctx_iv); - *checksum = be16toh(((uint16_t*)iv)[5]); - *stamp = be64toh(((uint64_t*)iv)[0]); + //extract header length (lower 2 bytes) + uint32_t header_len = test_magic - magic; + + if (header_len <= packet_len) { + // decrypt the complete header + speck_ctr(&packet[12], &packet[12], header_len - 12, iv, (speck_context_t*)ctx); // restore original packet order memcpy(&packet[0], &packet[16], 4); memcpy(&packet[4], community_name, N2N_COMMUNITY_SIZE); + // extract time stamp (first 64 bit) and un-xor actual checksum (calculated here) from it + // if payload was altered (different checksum than original), time stamp verification will fail + speck_96_decrypt(iv, (speck_context_t*)ctx_iv); + + uint64_t checksum = pearson_hash_64(packet, packet_len); + + *stamp = be64toh(*(uint64_t*)iv) ^ checksum; + // successful return 1; } else { @@ -66,41 +73,51 @@ uint32_t packet_header_decrypt (uint8_t packet[], uint16_t packet_len, } -int32_t packet_header_encrypt (uint8_t packet[], uint8_t header_len, he_context_t *ctx, - he_context_t *ctx_iv, uint64_t stamp, uint16_t checksum) { +int packet_header_encrypt (uint8_t packet[], uint16_t header_len, uint16_t packet_len, + he_context_t *ctx, he_context_t *ctx_iv, + uint64_t stamp) { uint8_t iv[16]; - uint16_t *iv16 = (uint16_t*)&iv; uint32_t *iv32 = (uint32_t*)&iv; uint64_t *iv64 = (uint64_t*)&iv; - const uint32_t magic = 0x6E326E21; /* == ASCII "n2n!" */ + uint64_t checksum = 0; + uint32_t magic = 0x6E320000; /* == ASCII "n2__" */ + magic += header_len; - if(header_len < 20) { + if(packet_len < 20) { traceEvent(TRACE_DEBUG, "packet_header_encrypt dropped a packet too short to be valid."); return -1; - } + } + // we trust in the caller assuring header_len <= packet_len + checksum = pearson_hash_64(packet, packet_len); + + // re-order packet memcpy(&packet[16], &packet[00], 4); - iv64[0] = htobe64(stamp); - iv16[4] = n2n_rand(); - iv16[5] = htobe16(checksum); - iv32[3] = htobe32(magic); + // add time stamp, checksum and magic bytes to form the pre-IV + iv64[0] = htobe64(stamp ^ checksum); + iv32[2] = n2n_rand(); - // blend checksum into 96-bit IV + // encrypt this 96-bit pre-IV to IV speck_96_encrypt(iv, (speck_context_t*)ctx_iv); + // place IV in packet (including magic number) + iv32[3] = htobe32(magic); memcpy(packet, iv, 16); - packet[15] = header_len; + // replace magic number "n2__" by correct IV padding "n2n!" + iv32[3] = htobe32(0x6E326E21); + + // encrypt speck_ctr(&packet[12], &packet[12], header_len - 12, iv, (speck_context_t*)ctx); return 0; } -void packet_header_setup_key (const char *community_name, he_context_t **ctx, - he_context_t **ctx_iv) { +void packet_header_setup_key (const char *community_name, + he_context_t **ctx, he_context_t **ctx_iv) { uint8_t key[16]; pearson_hash_128(key, (uint8_t*)community_name, N2N_COMMUNITY_SIZE); diff --git a/src/n2n.c b/src/n2n.c index 4a2a955..87a7382 100644 --- a/src/n2n.c +++ b/src/n2n.c @@ -1,5 +1,5 @@ /** - * (C) 2007-20 - ntop.org and contributors + * (C) 2007-21 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -594,7 +594,7 @@ int gettimeofday (struct timeval *tp, void *tzp) { tp->tv_sec = clock; tp->tv_usec = wtm.wMilliseconds * 1000; - return (0); + return 0; } #endif @@ -605,62 +605,65 @@ uint64_t time_stamp (void) { struct timeval tod; uint64_t micro_seconds; - gettimeofday (&tod, NULL); - /* We will (roughly) calculate the microseconds since 1970 leftbound into the return value. - The leading 32 bits are used for tv_sec. The following 20 bits (sufficent as microseconds - fraction never exceeds 1,000,000,) encode the value tv_usec. The remaining lowest 12 bits - are kept random for use in IV */ - micro_seconds = n2n_rand(); - micro_seconds = ((((uint64_t)(tod.tv_sec) << 32) + (tod.tv_usec << 12)) - | (micro_seconds >> 52)); - // more exact but more costly due to the multiplication: - // micro_seconds = (tod.tv_sec * 1000000 + tod.tv_usec) << 12) | ... + gettimeofday(&tod, NULL); - return (micro_seconds); + // (roughly) calculate the microseconds since 1970, note that the 8 bits between time stamp and flags remain unset + micro_seconds = ((uint64_t)(tod.tv_sec) << 32) + ((uint64_t)tod.tv_usec << 12); + // more exact but more costly due to the multiplication: + // micro_seconds = ((uint64_t)(tod.tv_sec) * 1000000ULL + tod.tv_usec) << 12; + + // note that the lower 4 bits remain unset (flags, for later use) + + return micro_seconds; } // returns an initial time stamp for use with replay protection uint64_t initial_time_stamp (void) { - return (time_stamp() - TIME_STAMP_FRAME); + return time_stamp() - TIME_STAMP_FRAME; } // checks if a provided time stamp is consistent with current time and previously valid time stamps // and, in case of validity, updates the "last valid time stamp" -int time_stamp_verify_and_update (uint64_t stamp, uint64_t * previous_stamp, int allow_jitter) { +int time_stamp_verify_and_update (uint64_t stamp, uint64_t *previous_stamp, int allow_jitter) { int64_t diff; // do not change to unsigned + // clear any incoming flags (their handling is not suppoted yet) + stamp = (stamp >> 4) << 4; + + // are the 8 bits between time stamp and flags reset? this relies on flags being cleared above + if(stamp << 52) { + traceEvent(TRACE_DEBUG, "time_stamp_verify_and_update found a timestamp with middle bits set."); + return 0; // failure + } + // is it around current time (+/- allowed deviation TIME_STAMP_FRAME)? diff = stamp - time_stamp(); // abs() diff = (diff < 0 ? -diff : diff); if(diff >= TIME_STAMP_FRAME) { traceEvent(TRACE_DEBUG, "time_stamp_verify_and_update found a timestamp out of allowed frame."); - return (0); // failure + return 0; // failure } // if applicable: is it higher than previous time stamp (including allowed deviation of TIME_STAMP_JITTER)? if(NULL != previous_stamp) { - // always reset lowest three (random) nybbles -- important in case of no jitter, do not if() to avoid jumping - stamp = (stamp >> 12) << 12; - *previous_stamp = (*previous_stamp >> 12) << 12; - diff = stamp - *previous_stamp; - if (allow_jitter) { + if(allow_jitter) { diff += TIME_STAMP_JITTER; } if(diff <= 0) { traceEvent(TRACE_DEBUG, "time_stamp_verify_and_update found a timestamp too old compared to previous."); - return (0); // failure + return 0; // failure } // for not allowing to exploit the allowed TIME_STAMP_JITTER to "turn the clock backwards", // set the higher of the values *previous_stamp = (stamp > *previous_stamp ? stamp : *previous_stamp); } - return (1); // success + return 1; // success } diff --git a/src/sn_utils.c b/src/sn_utils.c index b1a68f2..f874515 100644 --- a/src/sn_utils.c +++ b/src/sn_utils.c @@ -1,5 +1,5 @@ /** - * (C) 2007-20 - ntop.org and contributors + * (C) 2007-21 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -698,9 +698,9 @@ static int re_register_and_purge_supernodes (n2n_sn_t *sss, struct sn_community traceEvent(TRACE_DEBUG, "send REGISTER_SUPER to %s", sock_to_cstr(sockbuf, &(peer->sock))); - packet_header_encrypt(pktbuf, idx, comm->header_encryption_ctx, - comm->header_iv_ctx, - time_stamp(), pearson_hash_16(pktbuf, idx)); + packet_header_encrypt(pktbuf, idx, idx, + comm->header_encryption_ctx, comm->header_iv_ctx, + time_stamp()); /* sent = */ sendto_sock(sss, &(peer->sock), pktbuf, idx); } @@ -954,17 +954,14 @@ static int process_udp (n2n_sn_t * sss, if(comm->header_encryption == HEADER_ENCRYPTION_NONE) { continue; } - uint16_t checksum = 0; - if((ret = packet_header_decrypt(udp_buf, udp_size, comm->community, comm->header_encryption_ctx, - comm->header_iv_ctx, - &stamp, &checksum))) { + if((ret = packet_header_decrypt(udp_buf, udp_size, + comm->community, + comm->header_encryption_ctx, comm->header_iv_ctx, + &stamp))) { // time stamp verification follows in the packet specific section as it requires to determine the // sender from the hash list by its MAC, this all depends on packet type and packet structure // (MAC is not always in the same place) - if(checksum != pearson_hash_16(udp_buf, udp_size)) { - traceEvent(TRACE_DEBUG, "process_udp dropped packet due to checksum error."); - return -1; - } + if(comm->header_encryption == HEADER_ENCRYPTION_UNKNOWN) { traceEvent(TRACE_INFO, "process_udp locked community '%s' to using " "encrypted headers.", comm->community); @@ -1068,9 +1065,9 @@ static int process_udp (n2n_sn_t * sss, encode_buf(encbuf, &encx, (udp_buf + idx), (udp_size - idx)); if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(rec_buf, oldEncx, comm->header_encryption_ctx, - comm->header_iv_ctx, - time_stamp(), pearson_hash_16(rec_buf, encx)); + packet_header_encrypt(rec_buf, oldEncx, encx, + comm->header_encryption_ctx, comm->header_iv_ctx, + time_stamp()); } } else { /* Already from a supernode. Nothing to modify, just pass to @@ -1082,9 +1079,9 @@ static int process_udp (n2n_sn_t * sss, encx = udp_size; if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(rec_buf, idx, comm->header_encryption_ctx, - comm->header_iv_ctx, - time_stamp(), pearson_hash_16(rec_buf, udp_size)); + packet_header_encrypt(rec_buf, idx, encx, + comm->header_encryption_ctx, comm->header_iv_ctx, + time_stamp()); } } @@ -1154,9 +1151,9 @@ static int process_udp (n2n_sn_t * sss, } if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(rec_buf, encx, comm->header_encryption_ctx, - comm->header_iv_ctx, - time_stamp(), pearson_hash_16(rec_buf, encx)); + packet_header_encrypt(rec_buf, encx, encx, + comm->header_encryption_ctx, comm->header_iv_ctx, + time_stamp()); } try_forward(sss, comm, &cmn, reg.dstMac, from_supernode, rec_buf, encx); /* unicast only */ } else { @@ -1331,9 +1328,9 @@ static int process_udp (n2n_sn_t * sss, encode_REGISTER_SUPER_NAK(ackbuf, &encx, &cmn2, &nak); if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(ackbuf, encx, comm->header_encryption_ctx, - comm->header_iv_ctx, - time_stamp(), pearson_hash_16(ackbuf, encx)); + packet_header_encrypt(ackbuf, encx, encx, + comm->header_encryption_ctx, comm->header_iv_ctx, + time_stamp()); } sendto(sss->sock, ackbuf, encx, 0, (struct sockaddr *)sender_sock, sizeof(struct sockaddr_in)); @@ -1354,9 +1351,9 @@ static int process_udp (n2n_sn_t * sss, encode_REGISTER_SUPER(ackbuf, &encx, &cmn2, ®); if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(ackbuf, encx, comm->header_encryption_ctx, - comm->header_iv_ctx, - time_stamp(), pearson_hash_16(ackbuf, encx)); + packet_header_encrypt(ackbuf, encx, encx, + comm->header_encryption_ctx, comm->header_iv_ctx, + time_stamp()); } try_broadcast(sss, NULL, &cmn, reg.edgeMac, from_supernode, ackbuf, encx); @@ -1367,9 +1364,9 @@ static int process_udp (n2n_sn_t * sss, encode_REGISTER_SUPER_ACK(ackbuf, &encx, &cmn2, &ack, payload_buf); if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(ackbuf, encx, comm->header_encryption_ctx, - comm->header_iv_ctx, - time_stamp(), pearson_hash_16(ackbuf, encx)); + packet_header_encrypt(ackbuf, encx, encx, + comm->header_encryption_ctx, comm->header_iv_ctx, + time_stamp()); } sendto(sss->sock, ackbuf, encx, 0, @@ -1621,9 +1618,9 @@ static int process_udp (n2n_sn_t * sss, if(comm) { if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(encbuf, encx, comm->header_encryption_ctx, + packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx, comm->header_iv_ctx, - time_stamp(), pearson_hash_16(encbuf, encx)); + time_stamp()); } } @@ -1653,9 +1650,9 @@ static int process_udp (n2n_sn_t * sss, encode_PEER_INFO(encbuf, &encx, &cmn2, &pi); if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(encbuf, encx, comm->header_encryption_ctx, + packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx, comm->header_iv_ctx, - time_stamp(), pearson_hash_16(encbuf, encx)); + time_stamp()); } if(cmn.flags & N2N_FLAGS_SOCKET) { @@ -1687,9 +1684,9 @@ static int process_udp (n2n_sn_t * sss, encode_QUERY_PEER(encbuf, &encx, &cmn2, &query); if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { - packet_header_encrypt(encbuf, encx, comm->header_encryption_ctx, + packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx, comm->header_iv_ctx, - time_stamp(), pearson_hash_16(encbuf, encx)); + time_stamp()); } try_broadcast(sss, NULL, &cmn, query.srcMac, from_supernode, encbuf, encx); diff --git a/tools/benchmark.c b/tools/benchmark.c index aa4a891..a8228e9 100644 --- a/tools/benchmark.c +++ b/tools/benchmark.c @@ -1,5 +1,5 @@ /* - * (C) 2007-20 - ntop.org and contributors + * (C) 2007-21 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -255,17 +255,17 @@ static void run_hashing_benchmark(void) { ssize_t tdiff = 0; // microseconds size_t num_packets = 0; - uint32_t hash; + uint64_t hash; printf("(%s)\t%s\t%.1f sec\t(%u bytes)", - "prs32", "hash", target_sec, (unsigned int)sizeof(PKT_CONTENT)); + "prs64", "hash", target_sec, (unsigned int)sizeof(PKT_CONTENT)); fflush(stdout); gettimeofday( &t1, NULL ); - nw = 4; + nw = 8; while(tdiff < target_usec) { - hash = pearson_hash_32 (PKT_CONTENT, sizeof(PKT_CONTENT)); + hash = pearson_hash_64(PKT_CONTENT, sizeof(PKT_CONTENT)); hash++; // clever compiler finds out that we do no use the variable num_packets++; if (!(num_packets & PACKETS_BEFORE_GETTIME)) {