diff --git a/i2p2www/spec/proposals/144-ecies-x25519-aead-ratchet.rst b/i2p2www/spec/proposals/144-ecies-x25519-aead-ratchet.rst index 8fd98ca0..2ebc394f 100644 --- a/i2p2www/spec/proposals/144-ecies-x25519-aead-ratchet.rst +++ b/i2p2www/spec/proposals/144-ecies-x25519-aead-ratchet.rst @@ -5,7 +5,7 @@ ECIES-X25519-AEAD-Ratchet :author: zzz, chisana :created: 2018-11-22 :thread: http://zzz.i2p/topics/2639 - :lastupdated: 2019-09-14 + :lastupdated: 2019-09-18 :status: Open .. contents:: @@ -269,23 +269,68 @@ Crypto type 0 is ElGamal. Crypto types 1-3 are reserved for ECIES-ECDH-AES-SessionTag, see proposal 145. +Noise Protocol Framework +------------------------ + +This proposal provides the requirements based on the Noise Protocol Framework +[NOISE]_ (Revision 34, 2018-07-11). +Noise has similar properties to the Station-To-Station protocol +[STS]_, which is the basis for the [SSU]_ protocol. In Noise parlance, Alice +is the initiator, and Bob is the responder. + +This proposal is based on the Noise protocol Noise_IK_25519_ChaChaPoly_SHA256. +(The actual identifier for the initial key derivation function +is "Noise_IKelg2_25519_ChaChaPoly_SHA256" +to indicate I2P extensions - see KDF 1 section below) +This Noise protocol uses the following primitives: + +- Interactive Handshake Pattern: IK + Alice immediately transmits her static key to Bob (I) + Alice knows Bob's static key already (K) + +- One-Way Handshake Pattern: N + Alice does not transmit her static key to Bob (N) + +- DH Function: X25519 + X25519 DH with a key length of 32 bytes as specified in [RFC-7748]_. + +- Cipher Function: ChaChaPoly + AEAD_CHACHA20_POLY1305 as specified in [RFC-7539]_ section 2.8. + 12 byte nonce, with the first 4 bytes set to zero. + Identical to that in [NTCP2]_. + +- Hash Function: SHA256 + Standard 32-byte hash, already used extensively in I2P. + + +Additions to the Framework +`````````````````````````` + +This proposal defines the following enhancements to +Noise_XK_25519_ChaChaPoly_SHA256. These generally follow the guidelines in +[NOISE]_ section 13. + +1) Cleartext ephemeral keys are encoded with [Elligator2]_.ion using a known + +2) The reply is prefixed with a cleartext tag. + +3) The payload format is defined for messages 1, 2, and the data phase. + Of course, this is not defined in Noise. + + Handshake Patterns ------------------ -Handshakes similar to Noise handshake patterns: https://noiseprotocol.org/noise.html#handshake-patterns +Handshakes are to [Noise]_ handshake patterns. The following letter mapping is used: -- i = one-time ephemeral key - e = one-time ephemeral key - s = static key - p = message payload -One-time and Unbound sessions -````````````````````````````` - -Similar to the Noise N pattern. +One-time and Unbound sessions are similar to the Noise N pattern. .. raw:: html @@ -296,19 +341,15 @@ Similar to the Noise N pattern. {% endhighlight %} -Bound sessions -`````````````` - -Similar to the Noise IK pattern, with an additional ephemeral key in the reply. -TODO replace i with a tag? +Bound sessions are similar to the Noise IK pattern. .. raw:: html {% highlight lang='dataspec' %} <- s ... - e es s ss p -> - <- i si e ee se p + e es p s ss p -> + <- tag e ee se p {% endhighlight %} @@ -663,7 +704,7 @@ Encrypted: + + | New Session Ephemeral Public Key | + 32 bytes + - | | + | Encoded with Elligator2 | + + | | +----+----+----+----+----+----+----+----+ @@ -707,6 +748,9 @@ Encrypted: 1c) New session format (without binding) ---------------------------------------- +If no reply is required, no static key is sent. + + Encrypted: .. raw:: html @@ -716,8 +760,8 @@ Encrypted: | | + + | New Session Ephemeral Public Key | - + + - | | + + 32 bytes + + | Encoded with Elligator2 | + + | | +----+----+----+----+----+----+----+----+ @@ -770,7 +814,7 @@ The payload section must contain an ACK Request block. ------------------------------------------- If only a single message is expected to be sent, -no session setup or ephemeral key is required. +no session setup or static key is required. Encrypted: @@ -782,8 +826,8 @@ Encrypted: | | + + | Ephemeral Public Key | - + + - | | + + 32 bytes + + | Encoded with Elligator2 | + + | | +----+----+----+----+----+----+----+----+ @@ -902,7 +946,7 @@ Typical contents include the following blocks: ================================== ============= ============ Payload Block Type Type Number Block Length ================================== ============= ============ -I2NP Message 3 varies +Garlic Message 3 varies Options 5 9 Next Key 7 37 ACK Request 9 varies @@ -910,10 +954,10 @@ Padding 254 varies ================================== ============= ============ -I2NP Message Contents +Garlic Message Contents ~~~~~~~~~~~~~~~~~~~~~ -The I2NP message sent. +The decrypted Garlic Message as specified in [I2NP]_. Options Contents @@ -972,8 +1016,6 @@ This is the "e" message pattern: Define ck = 32 byte chaining key. Copy the h data to ck. Set chainKey = h - Define rs = Bob's 32-byte static key as published in the RouterInfo - // MixHash(null prologue) h = SHA256(h); @@ -1039,11 +1081,20 @@ This is the "e" message pattern: n = 0 ad = h + End of "es" message pattern. + + This is the "s" message pattern: + // MixHash(ciphertext) // Save for Payload section KDF h = SHA256(h || 64 byte encrypted flags/static key section) - End of "es" message pattern. + // Alice's X25519 static keys + ask = GENERATE_PRIVATE() + apk = DERIVE_PUBLIC(ask) + + End of "s" message pattern. + {% endhighlight %} @@ -1073,11 +1124,13 @@ This is the "ss" message pattern: n = 0 ad = h + End of "ss" message pattern. + // MixHash(ciphertext) // Save for New Session Reply KDF h = SHA256(h || encrypted payload section) - End of "ss" message pattern. + TODO tag = HKDF(...) {% endhighlight %} @@ -1085,6 +1138,10 @@ This is the "ss" message pattern: KDF for Payload Section (without Alice static key) `````````````````````````````````````````````````` +Note that this is a Noise "N" pattern, but we use the same "IK" initializer +as for bound sessions. + + .. raw:: html {% highlight lang='text' %} @@ -1100,35 +1157,21 @@ chainKey = from Flags/Static key section 1g) New Session Reply format ---------------------------- -TODO replace i with a tag? - Encrypted: .. raw:: html {% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ - | | - + + - | New Session Ephemeral Public Key | - + + - | | - + + - | | + | Session Tag 8 bytes | +----+----+----+----+----+----+----+----+ | | - + Ephemeral Key Section + - | ChaCha20 encrypted data | - + 48 bytes + + + Ephemeral Public Key + | | + + 32 bytes + + | Encoded with Elligator2 | + + | | - + + - | | - +----+----+----+----+----+----+----+----+ - | Poly1305 Message Authentication Code | - + (MAC) for above section + - | 16 bytes | +----+----+----+----+----+----+----+----+ | | + Payload Section + @@ -1143,9 +1186,11 @@ Encrypted: | 16 bytes | +----+----+----+----+----+----+----+----+ + Tag :: 8 bytes, cleartext + Public Key :: 32 bytes, little endian, Elligator2, cleartext - Ephemeral Key Section encrypted data :: 48 bytes + Ephemeral Key Section encrypted data :: 32 bytes Payload Section encrypted data :: remaining data minus 16 bytes @@ -1153,50 +1198,10 @@ Encrypted: {% endhighlight %} -Decrypted: - -Ephemeral Key Section Decrypted data -```````````````````````````````````` - -The Ephemeral Key section contains flags and a key. -It is always 48 bytes. - -TODO don't need session ID? - -.. raw:: html - - {% highlight lang='dataspec' %} -+----+----+----+----+----+----+----+----+ - | flags | unused | tsB | - +----+----+----+----+----+----+----+----+ - | Session ID | - +----+----+----+----+----+----+----+----+ - | | - + Ephemeral Key + - | 32 bytes | - + + - | | - + + - | | - +----+----+----+----+----+----+----+----+ - - flags :: 2 bytes - bit order: 15 14 .. 3210 - bit 0: 1 for ephemeral key is to be used - bit 1: 1 for the session ID to be used - bit 2: 1 for New Session Reply (0 for New Session) - bits 15-3: Unused, set to 0 for future compatibility - unused :: 2 bytes, set to 0 for future compatibility - tsB :: 4 bytes, seconds since epoch, big endian, rolls over in 2106 - sessionID :: session ID, 8 bytes. - Uniquely identifies the session request this is a reply for. - key :: the originator's ephemeral key, 32 bytes. - -{% endhighlight %} - -Alice must implement a Bloom filter or other mechanism to prevent replay attacks, -using the date in the ephemeral key section. Specification TBD. - +Notes +````` +The tag is generated in the New Session payload KDF. +This correlates the reply to the session. Payload @@ -1215,83 +1220,52 @@ Optional blocks can have multiple I2NP blocks, but only a single padding block. If present, the padding block must be the final block. -KDF for Ephemeral Key Section Encrypted Contents +KDF for Payload Section Encrypted Contents ```````````````````````````````````````````````` -Same as Static Key Section in the New Session Message. -Needs to be the same, because Alice will not know this is a reply -until the Ephemeral Key Section is decrypted. - .. raw:: html {% highlight lang='text' %} -// Alice's X25519 static keys - // apk is sent in original New Session message - ask = GENERATE_PRIVATE() - apk = DERIVE_PUBLIC(ask) +// Keys from the New Session message + // Alice's X25519 keys + // apk and aepk are sent in original New Session message + // ask = Alice private static key + // apk = Alice public static key + // aesk = Alice ephemeral private key + // aepk = Alice ephemeral public key + // Bob's X25519 static keys + // bsk = Bob private static key + // bpk = Bob public static key - // Bob's X25519 one-time-use ephemeral keys - ibsk = GENERATE_PRIVATE_ELG2() - ibpk = DERIVE_PUBLIC(ibsk) - // ebpk is sent in cleartext in the + // MixHash(tag) + h = SHA256(h || tag) + + This is the "e" message pattern: + + // Bob's X25519 ephemeral keys + besk = GENERATE_PRIVATE_ELG2() + bepk = DERIVE_PUBLIC(besk) + // elg2_bepk is sent in cleartext in the // beginning of the new session message - ebpk = ENCODE_ELG2(ibpk) + elg2_bepk = ENCODE_ELG2(bepk) // As decoded by Bob - ibpk = DECODE_ELG2(ebpk) + ibpk = DECODE_ELG2(elg2_bepk) - // Must be the same as the original New Session message - // Alice doesn't know it's a reply yet - INITIAL_ROOT_KEY = SHA256("144-ECIES-X25519-AEAD-Ratchet") + End of "e" message pattern. - // Noise-like si - sharedSecret = DH(ask, ibpk) = DH(ibsk, apk) + This is the "ee" message pattern: - // MixKey(DH()) - // ChaChaPoly parameters to encrypt/decrypt - keydata = HKDF(INITIAL_ROOT_KEY, sharedSecret, "NewSessionTmpKey", 64) - chainKey = keydata[0:31] - k = keydata[32:64] - n = 0 - ad = SHA-256(ebpk) - - // MixHash(ibpk) - h = SHA256(h || encrypted payload section) - - -{% endhighlight %} - - - -KDF for Payload Section Encrypted Contents -`````````````````````````````````````````` - -.. raw:: html - - {% highlight lang='text' %} -// Bob's X25519 ephemeral keys generated for the Ephemeral Key Section - rbsk = GENERATE_PRIVATE() - // rbpk decrypted by Alice in Ephemeral Key Section - rbpk = DERIVE_PUBLIC(rbsk) - - // Alice's X25519 ephemeral keys from original New Session Message - rask = GENERATE_PRIVATE() - // rapk was decrypted in original New Session Ephemeral Key Section - rapk = DERIVE_PUBLIC(rask) - - // Noise ee // MixKey(sharedSecret) // ChaChaPoly parameters to encrypt/decrypt // chainKey from original New Session Payload Section - sharedSecret = DH(rask, rbpk) = DH(rbsk, rapk) + sharedSecret = DH(aesk, bepk) = DH(besk, bepk) keydata = HKDF(chainKey, sharedSecret, "", 32) chainKey = keydata[0:31] - // Alice's X25519 static keys from original New Session Message - ask = GENERATE_PRIVATE() - // apk was decrypted in original New Session Static Key Section - apk = DERIVE_PUBLIC(ask) + End of "ee" message pattern. + + This is the "se" message pattern: - // Noise se // MixKey(sharedSecret) sharedSecret = DH(ask, rbpk) = DH(rbsk, apk) keydata = HKDF(chainKey, sharedSecret, "", 64) @@ -1300,9 +1274,13 @@ KDF for Payload Section Encrypted Contents n = 0 ad = SHA-256(rbpk) + End of "se" message pattern. + // MixHash() h = SHA256(h || encrypted payload section) + chainKey is used in the ratchet below. + {% endhighlight %} Notes @@ -1964,7 +1942,7 @@ so the max unencrypted data is 65519 bytes. blk :: 1 byte 0-2 reserved - 3 I2NP message (Garlic Message only) + 3 Garlic Message 4 termination 5 options 6 message number and previous message number (ratchet) @@ -1997,7 +1975,7 @@ the following blocks are required, in the following order: Other allowed blocks: -- I2NP message (type 3) +- Garlic message (type 3) - Padding (type 254) In the new session reply message, @@ -2007,7 +1985,7 @@ the following blocks are required: Other allowed blocks: -- I2NP message (type 3) +- Garlic message (type 3) - Padding (type 254) No other blocks are allowed. @@ -2026,11 +2004,11 @@ a single frame, but it is not prohibited. -I2NP Message +Garlic Message ```````````` -An single I2NP message with a modified header. -I2NP messages may not be fragmented across blocks or +A single decrypted Garlic Message as specified in [I2NP]_. +Garlic messages may not be fragmented across blocks or across ChaChaPoly frames. This uses the first 9 bytes from the standard NTCP I2NP header, @@ -2056,11 +2034,11 @@ and remove the one-byte SHA256 checksum. blk :: 3 size :: 2 bytes, big endian, size of type + msg id + exp + message to follow I2NP message body size is (size - 9). - type :: 1 byte, I2NP msg type, see I2NP spec + type :: 1 byte, I2NP msg type (11 for Garlic Message) msg id :: 4 bytes, big endian, I2NP message ID short exp :: 4 bytes, big endian, I2NP message expiration, Unix timestamp, unsigned seconds. Wraps around in 2106 - message :: I2NP message body + message :: Decrypted Garlic Message body {% endhighlight %} @@ -3163,6 +3141,12 @@ References https://www.imperialviolet.org/2013/12/25/elligator.html See also OBFS4 code +.. [I2NP] + {{ spec_url('i2np') }} + +.. [NTCP2] + {{ spec_url('ntcp2') }} + .. [NOISE] http://noiseprotocol.org/noise.html