Skip to content

Latest commit

 

History

History
325 lines (255 loc) · 11.8 KB

messages.md

File metadata and controls

325 lines (255 loc) · 11.8 KB

Message protocol

  • Most algorithms are described from the view of the sender. Modifications (reversing keys) apply for recipient.
  • Provides confidentiality.
  • Provides authentication.
  • Provides integrity.
  • Provides future security and forward secrecy.
  • Provides deniability.

1. Header Encryption

RecipientIdentityPub == Long term public key of recipient (from keyserver UID Message). curve25519
RecipientIdentityPriv == Private key corresponding to ECDH-RecipientIdentityPub. curve25519

SenderHeaderPub == One-time use only public key generated by Sender. curve25519
SenderHeaderPriv == Private key corresponding to ECDH-SenderHeaderPub. curve25519

HeaderKey == Symmetric key calculated by (internal to NaCL):

  1. By Sender: HeaderKey = curve25519.ecdh( SenderHeaderPriv , RecipientIdentityPub)
  2. By Recipient: HeaderKey = curve25519.ecdh( RecipientIdentityPriv, SenderHeaderPub)

Encrypt header by:

  ciphersuite: must be "CURVE25519 XSALSA20 POLY1305" (NaCL)
  nonce = 32byte random // unique for each message
  SenderHeaderPub, SenderHeaderPriv == GenerateDHKeypair() // unique for each message
  EncryptedHeader = NaCL.SEAL(Nonce, RecipientIdentityPub, SenderHeaderPriv, Header)
  HeaderPacket = Nonce | Length(EncryptedHeader) | EncryptedHeader

After header encryption, the sender deletes SenderHeaderPriv (it will never be used again).

The recipient tries to decrypt the header with all available RecipientIdentityPriv and caches the successful result, if any.

2. Root Key Agreement

Sender has:

  • SenderIdentityPub, SenderIdentityPriv == Long term public key of sender (keyserver UID Message)

For each new session or session refresh, sender creates:

  • SenderSessionPub, SenderSessionPriv == One Time use only keypair for session creation.

Sender fetches from keyserver:

  • RecipientIdentityPub == Long term public key of recipient (from keyserver UID Message). curve25519.
  • RecipientKeyInitPub == Single use public key of recipient from KeyInit repository. curve25519.

Sender calculates:

  // If this is a fresh session, previous_root_key is null.
  // Otherwise roll over previous_root_key.
  t1 = DH.ecdh( SenderIdentityPriv, RecipientKeyInitPub )  // e.g. curve25519.ScalarMult
  t2 = DH.ecdh( SenderSessionPriv, RecipientKeyInitPub )
  t3 = DH.ecdh( SenderSessionPriv, RecipientIdentityPub )

  root_key =  KDF( t1 | t2 | t3 | HASH(previous_root_key) )

Important : SenderIdentityPriv and RecipientIdentityPub must never be in the same DH!

2.1 Encrypted Header contents

Sender constructs header to send to recipient (JSON encoding):

  • Ciphersuite: String. Ciphersuite to use. Defines DH, KDF, Symmetric, Hash. Keys must match this. Governs content of header and body, does not affect header encryption (which is always NaCL).
  • RecipientPubHash: SHA512(RecipientIdentityPub)
  • RecipientTempHash: SHA512(RecipientKeyInitPub) || SHA512(RecipientSessionPub) SHA512(RecipientSessionPub): If previous SenderSessionPub from other party has been received.
  • SenderIdentity: Identity string of sender.
  • SenderSessionPub: SenderSessionPub (type KeyEntry, see keyserver)
  • SenderIdentityPubHash: SHA512(SenderIdentityPub)
  • SenderIdentityPub: Duplicate from SenderUID for easy parsing (type KeyEntry, see keyserver).
  • NextSenderSessionPub: New SenderSessionPub to refresh the session (type KeyEntry, see keyserver). Optional. Should be given by both parties after sending/receiving a defined maximum number of messages with the previous keypair.
  • NextRecipientSessionPubSeen: Currently known NextSenderSessionPub of the other party (type KeyEntry, see keyserver). Session refresh happens when both parties have created new NextSenderSessionPub and one party sees its own NextSenderSessionPub reflected in NextRecipientSessionPubSeen.
  • NymAddress: Address to receive future messages at.
  • MaxDelay: Maximum delay that sender requests from mix (obfuscated by rand(1,2)*MaxDelay. Constant per session). Can be used in retention calculation.
  • SenderSessionCount: Total number of messages sent in sessions before this SenderSessionPub was used.
  • SenderMessageCount: Total number of messages sent with this SenderSessionPub. Can be used in retention.
  • SenderUID: Complete UID Message (including SenderIdentityPub).
  • SenderLastKeychainHash: Last entry known to sender from keyserver hashchain.
  • Status: 2=Error, 1=Reset, 0=OK. Status of session, see below "invalid header". Error tries to start a session with the given remote SenderSessionPub. Reset deletes all keys, including root_key, and tries to start a new session with public key material.
  • Padding: Random padding to bring encrypted header to fixed size.

Sender encrypts header (see 1. Header encryption).

2.2 Receiving an encrypted header

Recipient has:

  • RecipientIdentityPriv
  • RecipientKeyInitPriv

Recipient reads from decrypted header:

  • SenderSessionPub
  • SenderIdentityPub

Recipient verifies that:

  1. RecipientIdentityPub used in header encryption and RecipientIdentityPub used in root key agreement are the same.
  2. SenderUID in header is valid and current.
  3. SenderLastKeychainHash is valid and current (with some grace period).
  4. No public keys are duplicates (no reuse of sender keys, no mirroring of recipient keys. All keys, including header temp key, must differ).

Recipient calculates:

  t1 = DH.ecdh( RecipientKeyInitPriv, SenderIdentityPub )
  t2 = DH.ecdh( RecipientKeyInitPriv, SenderSessionPub )
  t3 = DH.ecdh( RecipientIdentityPriv, SenderSessionPub )

  root_key =  KDF( t1 | t2 | t3 | HASH(previous_root_key) )

Important: 4. must be enforced strictly!

2.3 Session Update

Frequently each party should send a new NextSenderSessionPub. The NextSenderSessionPub of the other party must be sent back in NextRecipientSessionPubSeen. As soon as a party sees that the other side has created a new NextSenderSessionPub and its own NextSenderSessionPub is reflected in NextRecipientSessionPubSeen, the session is refreshed by moving the new keys:

  RecipientPubHash = NextSenderSessionPub
  SenderSessionPub = NextRecipientSessionPubSeen
  SenderSessionCount = SenderSessionCount + SenderMessageCount
  SenderMessageCount = 0

The current sender then updates its root_key and sends the message. The receiving party sees the new keys and triggers a root_key update as well. As soon as a message with a new root_key is received and no gaps (missing previous messages) are detected, all temporary private keys are to be deleted. The root_key must be deleted as soon as the message-key generation has succeeded. This minimizes the keys to be cached/stored to provide maximum perfect forward security/perfect future secrecy.

3. Invalid headers

In case of invalid headers (KeyInit not cached etc):

  • Recipient should try to recover by reusing the SenderSessionPub, setting Status=Error.
  • If receiving a Status==Error and the session cannot be started, reply with a Status=Reset and reset the session locally.
  • If receiving a Status==Reset, reset the session locally and try using the header to pick up the session created by the peer

4. Body encryption keys

The root_key is used to derive the message keys of a future N messages by:

  identity_fix = HASH(SORT(SenderNym, RecipientNym))  // Names/Nyms of both parties, sorted lexically
  chainkey = root_key
  for i=0, i<N, i++:
    messagekey_send[i] = HMAC_HASH(chainkey, "MESSAGE" | HASH(RecipientPub) | identity_fix)
    messagekey_recv[i] = HMAC_HASH(chainkey, "MESSAGE" | HASH(SenderSessionPub) | identity_fix)
    chainkey = HMAC_HASH(chainkey, "CHAIN" )

Messagekeys may never be reused. They should be generated once on session creation/refresh and deleted on use. Recipient may only use the message key corresponding to SenderSessionCount:SenderMessageCount for decrypting the body. The root_key should be deleted as soon as the messagekeys have been calculated and cached.

5. Body encryption

The body must be encrypted adhering to the Ciphersuite setting in the header, including potential signature. A body may be digitally signed. For encryption, the content is split into packets of fixed size, annotated by inner and outer packet-header and then encrypted. Packet size for encrypted packets must be a multiple of the blocksize of the encryption algorithm used and at least 512 bytes.

Outer-Packet-Header (binary):

  Type 1 byte:
         1:  Pre-Header. Version, Ciphersuite, Header-Keys
         2:  Encrypted Header.
         4:  Crypto setup. Nonce/IV
         8:  HMAC. Result of HMAC calculation.
        16:  Symmetric encrypted packet.
  PLen 2 byte:  Length of packet NOT including header
  Packet count 4 byte: Number of packet counted from first packet (count 0).

Inner-Packet-Header (binary):

  Type 1 byte:
        1: Padding (random padding data to fill packet)
        2: Data
        4: Sign (packet will be included in signature)
        8: Signature
  PLen 2 byte:  Length of the packet NOT including header.
  More 1 byte:  If set to 1, at least 1 more packet follows.
  Skip 1 byte:  Skip N bytes following header... additional padding scheme. Zero in most cases.

Signatures are always calculated over the data-packets only, and always excluding the inner-packet-header, excluding padding and skip bytes. Signature is only calculated for packets that have the "Sign" flag set. Packets following a packet with sign-flag but not having the sign-flag itself are forbidden if they also have the data flag set. HMAC calculation is over the complete previous packets, starting with the last crypto setup packet, including headers. Packets are encrypted before HMAC is calculated. Includes the HMAC Packet Header itself.

5.1 Packet: Pre-Header (type 1)

This packet contains:

  • Version: 2 byte (0x00 0x01)
  • Length(Ciphersuite): 2 bytes (0x00 0x1C)
  • Ciphersuite: must be for now: CURVE25519 XSALSA20 POLY1305
  • Length(SenderHeaderPub): 2 byte
  • SenderHeaderPub

See "1. Header Encryption" for details.

5.2 Packet: Crypto Setup (type 4)

First IV then Nonce (if any). The length of both is determined by the algorithm used (block size). The algorithm is defined in the Encrypted Header, field Ciphersuite.

5.3 Symmetric keys (encryption, HMAC)

Keys are derived by the messagekey for this message number. The KDF is used to generate to subkeys used for encryption and hmac respectively:

  crypto_key, hmac_key = KDF(messagekey_send)

The signature key is derived from the UID Message included in the header.

6. Body content

The content of the body adheres the email format, starting with headers, a newline, and the main body. The main body (after headers & newline) should be Mime-encodeded. On decryption of the body the included headers are shifted to the headers of the envelope message (the email that transports a Mute-encrypted message), overwriting existing duplicate headers. On encryption the reverse is done, shifting all headers not necessary for transport to the encrypted body.

7. Example message

  base64
    outer-header: 1. Pre-Header
      Version
      Length(Ciphersuite)
      Ciphersuite
      Length(SenderHeaderPub)
      SenderHeaderPub
    outer-header: 2. Encrypted Header
      Header (keys, counters etc)
    outer-header: 4. crypto setup
      IV
      Nonce
    outer-header: 16. encrypted packet
      encrypted:
        inner-header: 2 Data, 4 Sign
          data
    outer-header: 16. encrypted packet
      encrypted:
        inner-header: 2 Data, 4 Sign
          data
    outer-header: 16. encrypted packet
      encrypted:
        inner-header: 8 signature
          signature
        inner-header: 1 padding
          padding
    outer-header: 8. HMAC
      HMAC

8. Message flow

Message flow