Protocol Messages¶
The following chapter gives a brief outline how neuropil messages are constructed and encrypted.
Note
The documentation of the message structure is work in progress. Documentation and implementation will change as we progress with a standardization of the neuropil protocol.
1024 byte structure of neuropil messages¶
The neuropil cybersecurity mesh uses a layered approach as many other internet protocols as well. The general message format consists of seven distinct sections, which are shown below:
------------------------------------------------------------------------
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
------------------------------------------------------------------------
* mac(n): the MAC between to be used for the next hop nodes
* instructions: short extra information required for the communication between two nodes
* mac(i): mac of the message (including header)
* header: initial header of the original sender. cannot be modified after creation (see mac(i))
* attributes: extra attributes of a message (similar to http header fields)
* body: the real payload
* nonce: a nonce to further secure the crypto routines
Node to node encryption details¶
Messages between two nodes are encrypted with the following pattern, if the payload is already end-to-end encrypted:
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| <- crypted -> | <- sig -> |
* mac(n) = arg(mac / maclen_p)
* instructions header = arg(m / mlen)
* attributes / body / mac(i) / nonce = arg(ad / adlen)
* nonce = arg(npub)
Otherwise the transport encryption between two nodes uses the following layout:
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| <- crypted -> | sig? |
* mac(n) = arg(mac / maclen_p)
* instructions / header / attributes / body / mac(i) = arg(m / mlen)
* nonce = arg(ad / adlen) ?
* nonce = arg(npub)
End to end encryption details¶
The end-to-end encryption covers only the inner part of the message structure:
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| mac(i) | <sig> | <- e2e crypted -> |
* mac(i) = arg(mac/maclen_p)
* body / attributes = arg(m / mlen)
* header / nonce = arg(ad / adlen) ?
* nonce = arg(npub)
Even if no transport encryption is applied, the body contents are still safe. Only metadata hash values will be visible.
Key exchange and messages¶
the n2n key generation is based on the PFS/DHKE by exchanging handshake messages. We plan to extend transport encryption with attribute based chained hmac values in the future.
the e2e key generation is based on random symmetric key data. attribute based chained hmac values are also an option for later releases
the required e2e key exchange is handled by encrypting the symmetric key with PFS taken from discovery messages * send it over with separate message sequence number of ‘0/4’ * the body of this message contains the encrypted symmetric key plus the uuid of the token to be used
the follow up e2e encryption of messages after the key exchange: * messages will be encrypted with the symmetric key * seq number will then be ‘1/4’ to ‘4/4’
we voluntarily do not use ratcheting. the possibility to do attribute based encryption brings us a greater benefit.
Message encryption¶
For the encryption the XCacha20 algorithms are used, for the signature the Poly1305 tags are added to the messages. Both are covered in the following libsodium function:
.. code-block:: c
int crypto_aead_xchacha20poly1305_ietf_encrypt_detached(unsigned char *c,
unsigned char *mac,
unsigned long long *maclen_p,
const unsigned char *m,
unsigned long long mlen,
const unsigned char *ad,
unsigned long long adlen,
const unsigned char *nsec,
const unsigned char *npub,
const unsigned char *k);
with:
c := the encrypted message (part)
mac := the message authentication code
maclen := the length of message authentication code
m := the message (part) to encrypt
mlen := the length of the message (part) to encrypt
ad := the additional data to sign
adlen := the length of the additional data to sign
nsec := not used
npub := the nonce to use for encryption / signatures
k := the key material to use for encryption
Protocol messages and signed / crypted content details¶
1. Handshake message¶
Handshake messages are exchanged to establish transport encryption layer.
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| n2n crypted (mlen=0 !!!) |
2. Pure node to node messages (join, leave)¶
Join and leave messages to inform peers about starting/stopping nodes.
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| e2e crypted (mlen=0 !!!) |
| n2n crypted |
3. Pure node to node messages (ping, piggy, …)¶
Simple messages for health-checks and exchange of peer nodes.
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| e2e crypted (mlen=0 !!!) |
| n2n crypted |
*note*: node token 'from' header hash value must be used to verify signature of the node
4. forward modified node to node messages (update, …)¶
Some internal message types are forwarded to additional nodes but require a little modification.
1st hop:
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| e2e crypted (mlen=0 !!!) |
| n2n crypted |
*note*: id token in body contains hash value of node token, which is present in the header
*note*: node token and it's public key has been transmitted in the handshake message
5. Forward unmodified/discovery messages¶
Specific definition of discovery messages (additional MAC on the body of the message)
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| e2e crypted (mlen=0 !!!) |
| n2n crypted |
* body contains signed message intent token
6. End-to-end encrypted messages¶
| mac(n) | instructions | mac(i) | header | attributes | body | nonce |
| e2e crypted |
| n2n crypted |
Message serialization format¶
We use the msgpack format to serialize messages. Some parts of the message can still be used directly, as the position in the 1024 byte blocks is always is the same. The message object can then be composed of the following parts:
message := fixarray(7)(mac(n)|instructions|mac(i)|header|attr|body|nonce) (1)
* mac(n) := bin8(16) (17)
* instructions := int32 | int16 (8)
* mac(i) := bin8(16) (17)
* header := ts | int32 | bin8(32) | bin8(32) | bin8(32) | bin8(18) | uint32 | uint8 | uint16 | uint16
* attr() := bin(16) (min 3)
* body() := bin(32) (min 5)
* nonce := bin8(24) (25)
* ts := ext8(40-bit signed int) | ext8(24-bit uint) (14)
All together this sums up to 220 bytes of protocol parts (1+17+8+17+150+3+5+25=236) msgpack definitions sum up to 29 bytes and could be optimized (removed) further in the future. Right now it is easier to keep msg protocol definitions to ba able to add further fields in the future.
Message parts details¶
The following three sections define each single part of a message.
Message header contents¶
tstamp | (int = 5 bytes) | int 3 bytes | sent timestamp of message (signed second + unsigned nanoseconds)
ttl | (double = 4 bytes) | time to live for the message (in seconds)
to | (8 * uint32_t = 32 bytes) | np_id of the receiver (can be an abstract np_id)
subj | (8 * uint32_t = 32 bytes) | np_id of the message subject
from | (8 * uint32_t = 32 bytes) | np_id of the sending node
parts | (2 * uint16_t = 4 bytes) | current/total number of message parts
mhop | (uint8_t = 1 bytes) | max numbers of hops for this message
seq | (uint32_t = 4 bytes) | sender id sequence number (always increasing)
uuid unique id for each message (18 bytes)
in total: 135 bytes
0 8 16 24 32 bytes
[-------------------------------------------------------------------]
[16 bytes MAC(n) ]XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[----------------|----------------|----------------|----------------]
[instructions ]
...
[----------------|----------------|----------------|----------------]
[16 bytes MAC(i) ]XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[----------------|----------------|----------------|----------------]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[tstamp(8) |ttl(4) XXXXXXXX]
[subj(32) ]
[to (32) ]
[from(32) ]
[uuid(18) |seq(4)|mhops(1)|parts(4)]XXXXXXX sum=135 bytes
[-------------------------------------------------------------------]
[attributes ]
...
[-------------------------------------------------------------------]
[body ]
...
[-------------------------------------------------------------------]
[NONCE ]XXXXXXXXXXXXXXXXX
Message instructions contents¶
_np.seq | (uint32_t = 4 bytes) | intermediate node sequence number (always increasing)
_np.sendnr | (uint32_t = 2 bytes) | resend / hop counter. each (intermediate) node increases this counter for a given message. If too high (greater than maxhop of the message), then the message will be dropped.
0 8 16 24 32 bytes
[----------------|----------------|----------------|----------------]
[MAC ]XX
[----------------|----------------|----------------|----------------]
[instructions ]
[seq(4) |sendnr(2)]XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX sum = 6 bytes
[-------------------------------------------------------------------]
[MAC ]XX
[----------------|----------------|----------------|----------------]
[header ]
...
[-------------------------------------------------------------------]
[attributes ]
...
[-------------------------------------------------------------------]
[body ]
...
[-------------------------------------------------------------------]
[NONCE ]XXXXXXXXXXXXXXXXX
Message mac/nonce details¶
_np.nonce | (24 bytes) | a unique nonce for each single message on the transport
_np.mac(n) | (16 bytes) | an mac using authentication code of the node
_np.mac(i) | (16 bytes) | an mac using authentication code of the identity
0 8 16 24 32 bytes
[16 bytes MAC(n) ]XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[----------------|----------------|----------------|----------------]
[instructions ]
...
[----------------|----------------|----------------|----------------]
[16 bytes MAC(i) ]XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[----------------|----------------|----------------|----------------]
[header ]
...
[-------------------------------------------------------------------]
[attributes ]
...
[-------------------------------------------------------------------]
[body ]
...
[-------------------------------------------------------------------]
[24 bytes NONCE ]XXXXXXXXXXXXXXXXX