diff --git a/noise/README.md b/noise/README.md index cbf389e61..0d7d7ce58 100644 --- a/noise/README.md +++ b/noise/README.md @@ -4,7 +4,7 @@ | Lifecycle Stage | Maturity | Status | Latest Revision | |-----------------|---------------|--------|-----------------| -| 1A | Working Draft | Active | r0, 2019-08-05 | +| 1A | Working Draft | Active | r1, 2019-12-04 | Authors: [@yusefnapora] @@ -23,6 +23,7 @@ Interest Group: [@raulk], [@tomaka], [@romanb], [@shahankhatch], [@Mikerah], [@d [@morrigan]: https://github.com/morrigan [@araskachoi]: https://github.com/araskachoi + See the [lifecycle document][lifecycle-spec] for context about maturity level and spec status. @@ -32,35 +33,36 @@ and spec status. ## Table of Contents - [noise-libp2p - Secure Channel Handshake](#noise-libp2p---secure-channel-handshake) - - [Table of Contents](#table-of-contents) - - [Overview](#overview) - - [Negotiation](#negotiation) - - [The Noise Handshake](#the-noise-handshake) - - [Static Key Authentication](#static-key-authentication) - - [libp2p Data in Handshake Messages](#libp2p-data-in-handshake-messages) - - [The libp2p Signed Handshake Payload](#the-libp2p-signed-handshake-payload) - - [Supported Handshake Patterns](#supported-handshake-patterns) - - [XX](#xx) - - [Optimistic 0-RTT with Noise Pipes](#optimistic-0-rtt-with-noise-pipes) - - [IK](#ik) - - [XXfallback](#xxfallback) - - [Noise Pipes Message Flow](#noise-pipes-message-flow) - - [Cryptographic Primitives](#cryptographic-primitives) - - [Valid Noise Protocol Names](#valid-noise-protocol-names) - - [Wire Format](#wire-format) - - [Encrypted Payloads](#encrypted-payloads) - - [Encryption and I/O](#encryption-and-io) - - [libp2p Interfaces and API](#libp2p-interfaces-and-api) - - [Initialization](#initialization) - - [Secure Transport Interface](#secure-transport-interface) - - [NoiseConnection](#noiseconnection) - - [SecureOutbound](#secureoutbound) - - [SecureInbound](#secureinbound) - - [Design Considerations](#design-considerations) - - [No Negotiation of Noise Protocols](#no-negotiation-of-noise-protocols) - - [Why ChaChaPoly?](#why-chachapoly) - - [Distinct Noise and Identity Keys](#distinct-noise-and-identity-keys) - - [Why Not Noise Signatures?](#why-not-noise-signatures) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Negotiation](#negotiation) + - [The Noise Handshake](#the-noise-handshake) + - [Static Key Authentication](#static-key-authentication) + - [libp2p Data in Handshake Messages](#libp2p-data-in-handshake-messages) + - [The libp2p Handshake Payload](#the-libp2p-handshake-payload) + - [Handshake sealing](#handshake-sealing) + - [Supported Handshake Patterns](#supported-handshake-patterns) + - [XX](#xx) + - [Optimistic 0-RTT with Noise Pipes](#optimistic-0-rtt-with-noise-pipes) + - [IK](#ik) + - [XXfallback](#xxfallback) + - [Noise Pipes Message Flow](#noise-pipes-message-flow) + - [Cryptographic Primitives](#cryptographic-primitives) + - [Valid Noise Protocol Names](#valid-noise-protocol-names) + - [Wire Format](#wire-format) + - [Encrypted Payloads](#encrypted-payloads) + - [Encryption and I/O](#encryption-and-io) + - [libp2p Interfaces and API](#libp2p-interfaces-and-api) + - [Initialization](#initialization) + - [Secure Transport Interface](#secure-transport-interface) + - [NoiseConnection](#noiseconnection) + - [SecureOutbound](#secureoutbound) + - [SecureInbound](#secureinbound) + - [Design Considerations](#design-considerations) + - [No Negotiation of Noise Protocols](#no-negotiation-of-noise-protocols) + - [Why ChaChaPoly?](#why-chachapoly) + - [Distinct Noise and Identity Keys](#distinct-noise-and-identity-keys) + - [Why Not Noise Signatures?](#why-not-noise-signatures) ## Overview @@ -71,9 +73,8 @@ with verifiable security properties. This document specifies noise-libp2p, a libp2p channel security handshake built using the Noise Protocol Framework. As a framework for building protocols rather than a protocol itself, Noise presents a large decision space with many -tradeoffs. The [Design Considerations -section](#design-considerations) goes into detail about the -choices made when designing the protocol. +tradeoffs. The [Design Considerations section](#design-considerations) goes into +detail about the choices made when designing the protocol. Secure channels in libp2p are established with the help of a transport upgrader, a component that layers security and stream multiplexing over "raw" connections @@ -98,9 +99,9 @@ traffic. The [Noise Handshake section](#the-noise-handshake) describes the libp2p-specific data is exchanged during the handshake](#libp2p-data-in-handshake-messages). -During the handshake, the static -DH key used for Noise is authenticated using the libp2p identity keypair, as -described in the [Static Key Authentication section](#static-key-authentication). +During the handshake, the static DH key used for Noise is authenticated using +the libp2p identity keypair, as described in the [Static Key Authentication +section](#static-key-authentication). Following a successful handshake, peers use the resulting encryption keys to send ciphertexts back and forth. The format for transport messages and the wire @@ -177,7 +178,7 @@ exposure. To authenticate the static Noise key used in a handshake, noise-libp2p includes a signature of the static Noise public key in a [handshake -payload](#the-libp2p-signed-handshake-payload). This signature is produced with +payload](#the-libp2p-handshake-payload). This signature is produced with the private libp2p identity key, which proves that the sender was in possession of the private identity key at the time the payload was generated. @@ -202,61 +203,96 @@ libp2p components after the handshake is complete and the payload signature has been validated. If the handshake fails for any reason, the early data payload MUST be discarded immediately. -Any early data provided to noise-libp2p MUST be included in the [signed -handshake payload](#the-libp2p-signed-handshake-payload) as a byte string +Any early data provided to noise-libp2p MUST be included in a [handshake +payload](#the-libp2p-handshake-payload) as a byte string without alteration by the noise-libp2p implementation, and a valid signature of the early data MUST be included as described below. -#### The libp2p Signed Handshake Payload +#### The libp2p Handshake Payload + +The Noise Protocol Framework caters for sending early data alongside handshake +messages. We leverage this construct to transmit: + +1. the libp2p identity key along with a signature, to authenticate each party to + the other. +2. arbitrary data private to the libp2p stack. This facility is not exposed to + userland. Examples of usage include streamlining muxer selection. + +These payloads MUST be inserted into the first message of the handshake pattern +**that guarantees secrecy**. + +* In XX-initiated handshakes, the initiator will send its payload in message 3 + (closing message), whereas the responder will send theirs in message 2 (their + only message). +* In IK-initiated handshakes, the initiator will optimistically send its payload + in message 1 (as it satisfies the guarantee). Next, this case bifurcates: + * If the responder continues the IK handshake, it will send its payload in + message 2. The handshake ends. + * If the responder fall backs to `XXfallback`, it will have failed to + decrypt the payload in message 1. A retransmission from the initiator with + the fresh cryptographic material is necessary. This is performed in + message 3. -libp2p-specific data, including the signature used for static key -authentication, is transmitted in Noise handshake message payloads. When -decrypted, the message payload has the structure described in [Encrypted +When decrypted, the payload has the structure described in [Encrypted Payloads](#encrypted-payloads), consisting of a length-prefixed `body` field -followed by optional padding. The `body` of the payload contains a serialized -[protobuf][protobuf] message with the following schema: +followed by optional padding. + +The `body` of the payload contains a serialized [protobuf][protobuf] +`NoiseHandshakePayload` message with the following schema: ``` protobuf message NoiseHandshakePayload { - bytes libp2p_key = 1; - bytes noise_static_key_signature = 2; - bytes libp2p_data = 3; - bytes libp2p_data_signature = 4; + bytes identity_key = 1; + bytes identity_sig = 2; + bytes data = 3; } ``` -The `libp2p_key` field contains a serialized `PublicKey` message as defined in -the [peer id spec][peer-id-spec]. +The `identity_key` field contains a serialized `PublicKey` message as defined +in the [peer id spec][peer-id-spec]. -The `noise_static_key_signature` field is produced using the libp2p identity -private key according to the [signing rules in the peer id +The `identity_sig` field is produced using the libp2p identity private key +according to the [signing rules in the peer id spec][peer-id-spec-signing-rules]. The data to be signed is the UTF-8 string `noise-libp2p-static-key:`, followed by the Noise static public key, encoded according to the rules defined in [section 5 of RFC 7748][rfc-7748-sec-5]. -The `libp2p_data` field contains the "early data" provided to the Noise module -when initiating the handshake, if any. The structure of this data is opaque to -noise-libp2p and is expected to be defined in a future iteration of the -connection establishment spec. - -If `libp2p_data` is non-empty, the `libp2p_data_signature` field MUST contain a -signature produced with the libp2p identity key. The data to be signed is the -UTF-8 string `noise-libp2p-early-data:` followed by the contents of the -`libp2p_data` field. +The `data` field contains the "early data" provided to the Noise module when +initiating the handshake, if any. The structure of this data is opaque to +noise-libp2p and is defined in the connection establishment specs. Upon receiving the handshake payload, peers MUST decode the public key from the -`libp2p_key` field into a usable form. The key MUST be used to validate the -`noise_static_key_signature` field against the static Noise key received in the -handshake. If the signature is invalid, the connection MUST be terminated -immediately. - -If the `libp2p_data` field is non-empty, the `libp2p_data_signature` MUST be -validated against the supplied `libp2p_data`. If the signature is invalid, the -connection MUST be terminated immediately. - -If a noise-libp2p implementation does not expose an API for early data, they -MUST still validate the signature upon receiving a non-empty `libp2p_data` -field and abort the connection if it is invalid. +`identity_key` field into a usable form. The key MUST then be used to validate +the `identity_sig` field against the static Noise key received in the handshake. +If the signature is invalid, the connection MUST be terminated immediately. + +#### Handshake sealing + +In the above scheme, the `data` field is not guarded against tampering. +Consequently, a MITM agent could alter the data in transit, or even record the +handshake to replay it at a later time with mutated or transplanted data. + +An option to counteract such attacks is to sign the data too. However, for +efficiency and enhanced security purposes, we choose to adopt the [channel +binding][npf-channel-binding] technique defined in the Noise Protocol Framework. + +The Noise Protocol state machine tracks the handshake hash throughout the entire +handshake exchange. This is a rolling digest of all messages witnessed by each +peer, and it's exposed by the state machine via an accessor. + +Once the Noise handshake has concluded, and the shared secret has been derived, +each party performs the following actions: + +1. Obtains the `HandshakeHash` from the Noise state machine. +2. Signs the `HandshakeHash` with its libp2p identity key. +3. Writes the result on the encrypted channel, prefixed with a Protobuf varint + length prefix. We call this payload the "handshake seal". +4. Awaits to receive the handshake seal from its peer. +5. Verifies the peer's seal signature against the peer's libp2p public key, and + its local value of `HandshakeHash`. +6. If the signature verification passes, the Noise state machine is destroyed + and the encrypted channel is handed over to the libp2p stack. If the + verification fails, the party terminates the network connection immediately. ### Supported Handshake Patterns @@ -297,8 +333,8 @@ to the other party. The first handshake message contains the initiator's ephemeral public key, which allows subsequent key exchanges and message payloads to be encrypted. -The second and third handshake messages include a [signed handshake -payload](#the-libp2p-signed-handshake-payload), which contains a signature +The second and third handshake messages include a +[payload](#the-libp2p-handshake-payload), which contains a signature authenticating the sender's static Noise key as described in the [Static Key Authentication section](#static-key-authentication) and may include other internal libp2p data. @@ -362,9 +398,8 @@ key has changed, they may initiate an [`XXfallback`](#xxfallback) handshake, using the ephemeral public key from the failed `IK` handshake message as pre-message knowledge. -Each handshake message will include a [libp2p signed handshake -payload](#the-libp2p-signed-handshake-payload) that identifies the sender and -authenticates the static Noise key. +Each handshake message will include a [payload](#the-libp2p-handshake-payload) +that identifies the sender and authenticates the static Noise key. #### XXfallback @@ -393,9 +428,8 @@ key is obtained from her initial `IK` message, moving it to the pre-message section of the handshake pattern. Essentially, the failed `IK` message serves the same role as the first handshake message in the standard `XX` pattern. -Each handshake message will include a [libp2p signed handshake -payload](#the-libp2p-signed-handshake-payload) that identifies the sender and -authenticates the static Noise key. +Each handshake message will include a [payload](#the-libp2p-handshake-payload) +that identifies the sender and authenticates the static Noise key. #### Noise Pipes Message Flow @@ -482,7 +516,8 @@ protocol name depends on the handshake pattern in use. The `Noise_XX_25519_ChaChaPoly_SHA256` protocol MUST be supported by all implementations. -Implementations that support Noise Pipes will also support the following Noise protocols: +Implementations that support Noise Pipes will also support the following Noise +protocols: - `Noise_IK_25519_ChaChaPoly_SHA256` - `Noise_XXfallback_25519_ChaChaPoly_SHA256` @@ -754,7 +789,7 @@ unsupported types like RSA. [npf-handshake-indistinguishability]: https://noiseprotocol.org/noise.html#handshake-indistinguishability [npf-handshake-state]: https://noiseprotocol.org/noise.html#the-handshakestate-object [npf-cipher-state]: https://noiseprotocol.org/noise.html#the-cipherstate-object - +[npf-channel-binding]: https://noiseprotocol.org/noise.html#channel-binding [rfc-7748-sec-5]: https://tools.ietf.org/html/rfc7748#section-5 [protobuf]: https://developers.google.com/protocol-buffers/