Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

noise-libp2p: introduce "handshake seal"; more. #234

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 119 additions & 84 deletions noise/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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.

Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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
raulk marked this conversation as resolved.
Show resolved Hide resolved
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.
raulk marked this conversation as resolved.
Show resolved Hide resolved

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
Copy link

@morrigan morrigan Dec 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more precise which data should be hashed? Also, do you want to specify this to be SHA256?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handshake hash is defined in the Noise spec: https://noiseprotocol.org/noise.html#overview-of-handshake-state-machine. The noise library you use should be computing the hash -- you shouldn't need to do it in the noise-libp2p implementation.

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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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/
Expand Down