Browse Source

enhanced header encryption to full 128 bit iv (#589)

pull/591/head
Logan oos Even 4 years ago
committed by GitHub
parent
commit
e4e6efacf0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      doc/Communities.md
  2. 71
      doc/Crypto.md
  3. 40
      doc/Hacking.md
  4. 2
      include/n2n_define.h
  5. 10
      include/speck.h
  6. 7
      src/edge_utils.c
  7. 78
      src/header_encryption.c
  8. 1
      src/n2n.c
  9. 4
      src/sn_utils.c
  10. 68
      src/speck.c

4
doc/Communities.md

@ -3,9 +3,9 @@
## Names
As communities designate virtual networks, they must be distingushable from each other. Its their name that makes them distinguishable and which therefore should be unique per network. The community name is composed of 15 byte-sized characters and it internally always is terminated by an additional zero character totalling up to 16 characters. Hence, the zero character cannot be part of the regular community name. There are some other characters that cannot be used, namely `. * + ? [ ] \`.
As communities designate virtual networks, they must be distingushable from each other. Its their name that makes them distinguishable and which therefore should be unique per network. The community name is composed of 19 byte-sized characters and it internally always is terminated by an additional zero character totalling up to 20 characters. Hence, the zero character cannot be part of the regular community name. There are some other characters that cannot be used, namely `. * + ? [ ] \`.
To make full use of character space, hex values could be used, e.g. from Linux bash applying the `edge … -c $(echo -en '\x3a\x3b\x4a\x6a\xfa') …` command line syntax. If used with a configuration file, the bytes must be directly filled as characters into a corresponding `-c=:;Jjþ` line.
To make full use of character space, hex values could be used, e.g. from Linux bash applying the `edge … -c $(echo -en '\x3a\x3b\x4a\x6a\xfa') …` command line syntax. If used with a configuration file, the bytes must be directly filled as characters into a corresponding `-c :;Jjþ` line.
## Restrict Supernode Access

71
doc/Crypto.md

@ -89,13 +89,15 @@ The COMMON section is built as follows:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
! Version = 3 ! TTL ! Flags !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4 ! Community :
4 ! Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8 ! ... Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12 ! ... Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 ! ... Community !
16 ! ... Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20 ! ... Community !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```
@ -104,29 +106,29 @@ In case of a PACKET-type, it is succeeded by the fields depicted below:
```
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20 ! Source MAC Address :
24 ! Source MAC Address :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24 : ! Destination MAC Address :
28 : ! Destination MAC Address :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28 : !
32 : !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32 ! Socket Flags (v=IPv4) ! Destination UDP Port !
36 ! Socket Flags (v=IPv4) ! Destination UDP Port !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36 ! Destination IPv4 Address !
40 ! Destination IPv4 Address !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40 ! Compress'n ID ! Transform ID ! Payload ... !
44 ! Compress'n ID ! Transform ID ! Payload ... !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
44 ! !
48 ! !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+...
```
### 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.
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 20 characters (well, 19 + `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 sixth 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,14 +139,16 @@ The scheme applied tries to maintain compatibility with current packet format an
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8 ! ... Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12 ! ... Community !
12 ! ... Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 ! Version = 3 ! TTL ! Flags !
16 ! ... Community !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20 ! 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 "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.
- To be able to identify a correctly decrpyted header later on, a magic number is stamped in fourth line starting at byte number 16. 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 16 bytes, is reframed towards a 128-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
@ -153,19 +157,21 @@ The scheme applied tries to maintain compatibility with current packet format an
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4 ! ... IV ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8 ! ... IV :
8 ! ... IV ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12 ! ... IV !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12 ! Magic Number "n2" = 0x6E32 ! Header Length !
16 ! Magic Number "n2" = 0x6E32 ! Header Length !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 ! Version = 3 ! TTL ! Flags !
20 ! 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 transmitted 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.
- 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 PACKET's ethernet data payload which eventually has its own encryption scheme as chosen with the `-A_` options.
- Encryption starts at byte number 16 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 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.
@ -173,37 +179,38 @@ Thus, header encryption will only work with previously determined community name
### Checksum
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 whole packet including the eventually present payload is checksummed using a Person block hashing scheme. The 64-bit checksum is exclusive-ored with a (shifted by 32 bit) 64-bit time stamp and filled up with 32 more random bits to obtain a 128-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 128-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 single block-cipher step employs SPECK because it is quite fast and it offers a 128-bit block cipher version. The key is derived from the header key – a hash of the hash.
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 96-bit pre-IV can be depicted as follows:
The aforementioned 128-bit pre-IV can be depicted as follows:
```
012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
+------------------------------------------------------------------------------------------------+
! 64-bit checksum of the whole packet ! !
+ - - - - - - - - - - - - - - - - - - - - - - - XOR - - - - - - -! 32 pseudo-random bits !
! 52-bit time stamp with microsecond-accuracy ! 0x00 ! F ! !
+------------------------------------------------------------------------------------------------+
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
+----------------------------------------------------------------+-------------------------------+-------------------------------+
! 64-bit checksum of the whole packet ! 0x00 ! !
+ - - - - - - - - - - - - - - - - - - - - - - - XOR - - - - - - - - - - - - - - - - - - - - - - -! 32 pseudo-random bits !
! 0x00 ! 52-bit time stamp with microsecond-accuracy ! resrvd ! F ! !
+------------------------------------------------------------------------------------------------+-------------------------------+
```
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.
The time stamp consists of the 52-bit microsecond value, a still reserved field for additional accuracy information such as a counter (still under development), a 4-bit flag field F (accuracy indicator, other header encryption features – still under development, too).
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.
Upon receival, the time stamp as well as the checksum can be extracted from the IV by performing a 96-bit block-cipher decryption step. Verification of the time stamp happens in two steps:
Upon receival, the time stamp as well as the checksum can be extracted from the IV by performing a 128-bit block-cipher decryption step. Verification of the time stamp happens in two steps:
- 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.
- 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!
- 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! REGISTER packets from the local multicast environment are excempt from the very strict no-jitter requirement because they indeed regularily can show some deviation if compared to time stamps in packets received on the regular socket. As these packets are incoming on different sockets, their processing is more likely to no take place in the order these packets were sent.
The way the IV is used for replay protection and for checksumming makes enabled header encryption a prerequisite for these features.

40
doc/Hacking.md

@ -2,9 +2,9 @@
--------
This program and document is free software; you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3 of the
This program and document is free software; you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
@ -212,25 +212,27 @@ Version 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12 ! ... Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 ! ... Community ... !
16 ! ... Community ... :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20 ! Source MAC Address :
20 ! ... Community ... !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24 : ! Destination MAC Address :
24 ! Source MAC Address :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28 : !
28 : ! Destination MAC Address :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32 ! Socket Flags (v=IPv4) ! Destination UDP Port !
32 : !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36 ! Destination IPv4 Address !
36 ! Socket Flags (v=IPv4) ! Destination UDP Port !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40 ! Compress'n ID ! Transform ID !
40 ! Destination IPv4 Address !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44 ! Compress'n ID ! Transform ID !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44 ! Payload
48 ! Payload
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```
So each n2n PACKET has a 44 byte overhead. For a 1500 byte ethernet packet this
So each n2n PACKET has a 48 byte overhead. For a 1500 byte ethernet packet this
is roughly 3%.
Socket flags provides support for IPv6. In this case the PACKET message ends as
@ -238,19 +240,19 @@ follows:
```
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32 ! Socket Flags (v=IPv6) ! Destination UDP Port !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36 ! Destination IPv6 Address :
36 ! Socket Flags (v=IPv6) ! Destination UDP Port !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40 : :
40 ! Destination IPv6 Address :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44 : :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48 : !
48 : :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
52 : !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
52 ! Compress'n ID ! Transform ID !
56 ! Compress'n ID ! Transform ID !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
56 ! Encapsulated ethernet payload
60 ! Encapsulated ethernet payload
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

2
include/n2n_define.h

@ -145,7 +145,7 @@ enum skip_add{SN_ADD = 0, SN_ADD_SKIP = 1, SN_ADD_ADDED = 2};
#define N2N_PKT_VERSION 3
#define N2N_DEFAULT_TTL 2 /* can be forwarded twice at most */
#define N2N_COMMUNITY_SIZE 16
#define N2N_COMMUNITY_SIZE 20
#define N2N_MAC_SIZE 6
#define N2N_COOKIE_SIZE 4
#define N2N_DESC_SIZE 16

10
include/speck.h

@ -111,17 +111,13 @@ int speck_deinit (speck_context_t *ctx);
// ----------------------------------------------------------------------------------------------------------------
// cipher SPECK -- 96 bit block size -- 96 bit key size -- ECB mode
// cipher SPECK -- 128 bit block size -- 128 bit key size -- ECB mode
// follows endianess rules as used in official implementation guide and NOT as in original 2013 cipher presentation
// used for IV in header encryption
// used for IV in header encryption (one block); encrytion via speck_ctr with null_block as data
// for now: just plain C -- probably no need for AVX, SSE, NEON
int speck_96_encrypt (unsigned char *inout, speck_context_t *ctx);
int speck_96_decrypt (unsigned char *inout, speck_context_t *ctx);
int speck_96_expand_key (speck_context_t *ctx, const unsigned char *k);
int speck_128_decrypt (unsigned char *inout, speck_context_t *ctx);
#endif // SPECK_H

7
src/edge_utils.c

@ -2008,8 +2008,11 @@ void readFromIPSocket (n2n_edge_t * eee, int in_sock) {
decode_REGISTER(&reg, &cmn, udp_buf, &rem, &idx);
via_multicast = is_null_mac(reg.dstMac);
if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) {
if(!find_peer_time_stamp_and_verify (eee, from_supernode, reg.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
if(!find_peer_time_stamp_and_verify (eee, from_supernode, reg.srcMac, stamp,
via_multicast ? TIME_STAMP_ALLOW_JITTER : TIME_STAMP_NO_JITTER)) {
traceEvent(TRACE_DEBUG, "readFromIPSocket dropped REGISTER due to time stamp error.");
return;
}
@ -2018,8 +2021,6 @@ void readFromIPSocket (n2n_edge_t * eee, int in_sock) {
if(is_valid_peer_sock(&reg.sock))
orig_sender = &(reg.sock);
via_multicast = is_null_mac(reg.dstMac);
if(via_multicast && !memcmp(reg.srcMac, eee->device.mac_addr, N2N_MAC_SIZE)) {
traceEvent(TRACE_DEBUG, "Skipping REGISTER from self");
break;

78
src/header_encryption.c

@ -28,20 +28,14 @@ int packet_header_decrypt (uint8_t packet[], uint16_t packet_len,
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
memcpy(iv, packet, 12);
// try community name as possible key and check for magic bytes "n2__"
uint32_t magic = 0x6E320000;
uint32_t test_magic;
uint32_t checksum_high = 0;
// 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);
// so, as a first step, decrypt last 4 bytes from where originally the community name would be
speck_ctr((uint8_t*)&test_magic, &packet[16], 4, packet, (speck_context_t*)ctx);
test_magic = be32toh(test_magic);
//extract header length (lower 2 bytes)
@ -49,19 +43,30 @@ int packet_header_decrypt (uint8_t packet[], uint16_t packet_len,
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);
speck_ctr(&packet[16], &packet[16], header_len - 16, packet, (speck_context_t*)ctx);
// extract time stamp (first 64 bit) and un-xor actual checksum (calculated here) from it
// extract time stamp 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);
// use speck block cipher step (1 block == 128 bit == 16 bytes)
speck_128_decrypt(packet, (speck_context_t*)ctx_iv);
// extract the required data
*stamp = be64toh(*(uint64_t*)&packet[4]);
checksum_high = be32toh(*(uint32_t*)packet);
// restore original packet order before calculating checksum
memcpy(&packet[0], &packet[20], 4);
memcpy(&packet[4], community_name, N2N_COMMUNITY_SIZE);
uint64_t checksum = pearson_hash_64(packet, packet_len);
*stamp = be64toh(*(uint64_t*)iv) ^ checksum;
if((checksum >> 32) != checksum_high) {
traceEvent(TRACE_DEBUG, "packet_header_decrypt dropped a packet with invalid checksum.");
// unsuccessful
return 0;
}
*stamp = *stamp ^ (checksum << 32);
// successful
return 1;
@ -77,14 +82,14 @@ int packet_header_encrypt (uint8_t packet[], uint16_t header_len, uint16_t packe
he_context_t *ctx, he_context_t *ctx_iv,
uint64_t stamp) {
uint8_t iv[16];
uint32_t *iv32 = (uint32_t*)&iv;
uint64_t *iv64 = (uint64_t*)&iv;
const uint8_t null_block[16] = { 0 };
uint32_t *p32 = (uint32_t*)packet;
uint64_t *p64 = (uint64_t*)packet;
uint64_t checksum = 0;
uint32_t magic = 0x6E320000; /* == ASCII "n2__" */
magic += header_len;
if(packet_len < 20) {
if(packet_len < 24) {
traceEvent(TRACE_DEBUG, "packet_header_encrypt dropped a packet too short to be valid.");
return -1;
}
@ -93,24 +98,25 @@ int packet_header_encrypt (uint8_t packet[], uint16_t header_len, uint16_t packe
checksum = pearson_hash_64(packet, packet_len);
// re-order packet
memcpy(&packet[16], &packet[00], 4);
p32[5] = p32[0];
// add time stamp, checksum, and random to form the pre-IV
p64[0] = htobe64(checksum);
// add time stamp, checksum and magic bytes to form the pre-IV
iv64[0] = htobe64(stamp ^ checksum);
iv32[2] = n2n_rand();
p32[1] = p32[1] ^ htobe32((uint32_t)(stamp >> 32));
p32[2] = htobe32((uint32_t)stamp);
// encrypt this 96-bit pre-IV to IV
speck_96_encrypt(iv, (speck_context_t*)ctx_iv);
p32[3] = n2n_rand();
// place IV in packet (including magic number)
iv32[3] = htobe32(magic);
memcpy(packet, iv, 16);
// encrypt this pre-IV to IV
// use speck ctr with null_block as data to make it a block cipher step
speck_ctr(packet, null_block, 16, packet, (speck_context_t*)ctx_iv);
// replace magic number "n2__" by correct IV padding "n2n!"
iv32[3] = htobe32(0x6E326E21);
// place IV plus magic in packet
p32[4] = htobe32(magic);
// encrypt
speck_ctr(&packet[12], &packet[12], header_len - 12, iv, (speck_context_t*)ctx);
// encrypt, starting from magic
speck_ctr(&packet[16], &packet[16], header_len - 16, packet, (speck_context_t*)ctx);
return 0;
}
@ -125,9 +131,9 @@ void packet_header_setup_key (const char *community_name,
*ctx = (he_context_t*)calloc(1, sizeof (speck_context_t));
speck_init((speck_context_t**)ctx, key, 128);
// hash again and use last 96 bit (skipping 4 bytes) as key for IV encryption
// hash again and use as key for IV encryption
// REMOVE as soon as checksum and replay protection get their own fields
pearson_hash_128(key, key, sizeof (key));
*ctx_iv = (he_context_t*)calloc(1, sizeof (speck_context_t));
speck_96_expand_key((speck_context_t*)*ctx_iv, &key[4]);
speck_init((speck_context_t**)ctx_iv, key, 128);
}

1
src/n2n.c

@ -626,7 +626,6 @@ uint64_t time_stamp (void) {
// 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;
}

4
src/sn_utils.c

@ -915,11 +915,11 @@ static int process_udp (n2n_sn_t * sss,
/* check if header is unencrypted. the following check is around 99.99962 percent reliable.
* it heavily relies on the structure of packet's common part
* changes to wire.c:encode/decode_common need to go together with this code */
if(udp_size < 20) {
if(udp_size < 24) {
traceEvent(TRACE_DEBUG, "process_udp dropped a packet too short to be valid.");
return -1;
}
if((udp_buf[19] == (uint8_t)0x00) // null terminated community name
if((udp_buf[23] == (uint8_t)0x00) // null terminated community name
&& (udp_buf[00] == N2N_PKT_VERSION) // correct packet version
&& ((be16toh(*(uint16_t*)&(udp_buf[02])) & N2N_FLAGS_TYPE_MASK) <= MSG_TYPE_MAX_TYPE) // message type
&& ( be16toh(*(uint16_t*)&(udp_buf[02])) < N2N_FLAGS_OPTIONS) // flags

68
src/speck.c

@ -638,7 +638,6 @@ static int speck_encrypt (u64 *u, u64 *v, speck_context_t *ctx, int numrounds) {
for(i = 0; i < numrounds; i++)
R(x, y, ctx->key[i]);
*u = x; *v = y;
return 0;
@ -764,73 +763,30 @@ int speck_deinit (speck_context_t *ctx) {
// ----------------------------------------------------------------------------------------------------------------
// cipher SPECK -- 96 bit block size -- 96 bit key size -- ECB mode
// cipher SPECK -- 128 bit block size -- 128 bit key size -- ECB mode (decrypt only)
// follows endianess rules as used in official implementation guide and NOT as in original 2013 cipher presentation
// used for IV in header encryption, thus the in/postfix 'he_iv'
// used for IV in header encryption (one block); encrytion via speck_ctr with null_block as data
// for now: just plain C -- probably no need for AVX, SSE, NEON
// prerequisite: lower 16 bit reset
#define ROTL48(x,r) (((((x)<<(r)) | (x>>(48-(r)))) >> 16) << 16)
#define ROTR48(x,r) (((((x)>>(r)) | ((x)<<(48-(r)))) >> 16) << 16)
#define ER96(x,y,k) (x=ROTR48(x,8), x+=y, x^=k, y=ROTL48(y,3), y^=x)
#define DR96(x,y,k) (y^=x, y=ROTR48(y,3), x^=k, x-=y, x=ROTL48(x,8))
int speck_96_encrypt (unsigned char *inout, speck_context_t *ctx) {
u64 x, y;
int i;
x = htole64( *(u64*)&inout[0] ); x <<= 16;
y = htole64( *(u64*)&inout[4] ); y >>= 16; y <<= 16;
for(i = 0; i < 28; i++)
ER96(y, x, ctx->key[i]);
x >>= 16; x |= y << 32;
y >>= 32;
((u64*)inout)[0] = le64toh(x);
((u32*)inout)[2] = le32toh(y);
return 0;
}
#define ROTL64(x,r) (((x)<<(r))|((x)>>(64-(r))))
#define ROTR64(x,r) (((x)>>(r))|((x)<<(64-(r))))
#define DR128(x,y,k) (y^=x, y=ROTR64(y,3), x^=k, x-=y, x=ROTL64(x,8))
int speck_96_decrypt (unsigned char *inout, speck_context_t *ctx) {
int speck_128_decrypt (unsigned char *inout, speck_context_t *ctx) {
u64 x, y;
int i;
x = htole64( *(u64*)&inout[0] ); x <<= 16;
y = htole64( *(u64*)&inout[4] ); y >>= 16; y <<= 16;
for(i = 27; i >= 0; i--)
DR96(y, x, ctx->key[i]);
x = le64toh( *(u64*)&inout[8] );
y = le64toh( *(u64*)&inout[0] );
x >>= 16; x |= y << 32;
y >>= 32;
for(i = 31; i >= 0; i--)
DR128(x, y, ctx->key[i]);
((u64*)inout)[0] = le64toh(x);
((u32*)inout)[2] = le32toh(y);
((u64*)inout)[1] = htole64(x);
((u64*)inout)[0] = htole64(y);
return 0;
}
int speck_96_expand_key (speck_context_t *ctx, const unsigned char *k) {
u64 A, B;
int i;
A = htole64( *(u64 *)&k[0] ); A <<= 16;
B = htole64( *(u64 *)&k[4] ); B >>= 16; B <<= 16;
for(i = 0; i < 28; i++) {
ctx->key[i] = A;
ER96(B, A, i << 16);
}
return 1;
}

Loading…
Cancel
Save