Skip to content

Commit

Permalink
Merge pull request #151 from libp2p/tls
Browse files Browse the repository at this point in the history
libp2p TLS 1.3 Handshake
  • Loading branch information
marten-seemann authored Apr 7, 2019
2 parents 63c77f8 + 1a5e6eb commit adce976
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
47 changes: 47 additions & 0 deletions tls/design considerations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Design considerations for the libp2p TLS Handshake

## Requirements

There are two main requirements that prevent us from using the straightforward way to run a TLS handshake (which would be to simply use the host key to create a self-signed certificate).

1. We want to use different key types: RSA, ECDSA, and Ed25519, Secp256k1 (and maybe more in the future?).
2. We want to be able to send the key type along with the key (see https://github.com/libp2p/specs/issues/111).

The first point is problematic in practice, because Go currently only supports RSA and ECDSA certificates. Support for Ed25519 was planned for Go 1.12, but was deferred recently, and the Go team is now evaluating interest in this in order to prioritze their work, so this might or might not happen in Go 1.13. I'm not aware of any plans for Secp256k1 at the moment.
The second requirement implies that we might want add some additional (free-form) information to the handshake, and we need to find a field to stuff that into.

The handshake protocol described here:
* supports arbitrary keys, independent from what the signature algorithms implemented by the TLS library used
* defines a way how future versions of this protocol might be negotiated without requiring any out-of-band information and additional roundtrips


## Design Choices

### TLS 1.3 - What about older versions?

The handshake protocol requires TLS 1.3 support. This means that the handshake between two peers that have never communicated before will typically complete in just a single roundtrip. With older TLS versions, a handshake typically takes two roundtrips. By not specifying support for older TLS versions, we increase performance and simplify the protocol.


### Why we're not using the host key for the certificate

The current proposal uses a self-signed certificate to carry the host's public key in the libp2p Public Key Extension. The key used to generate the self-signed certificate has no relationship with the host key. This key can be generated for every single connection, or can be generated at boot time.

One optimisation that was considered when designing the protocol was to use the libp2p host key to generate the certificate in the case of RSA and ECDSA keys (which we can assume to be supported signature schemes by all peers). That would have allowed us to strip the host key and the signature from the key extension, in order to

1. reduce the size of the certificate and
2. reduce the number of signature verifications the peer has to perform from 2 to 1.

The protocol does not include this optimisation, because

1. assuming that the peer uses an ECDSA key for generating the self-signed certificate, this only saves about ~150 bytes if the host key is an ECDSA key as well, and it even slightly increases the size of the certificate in case of a RSA host key. Furthermore, for ECDSA keys, the size of all handshake messages combined is less than 900 bytes, so having a slightly larger certificate won't require us to send more (TCP / QUIC) packets.
2. For a client, the number of signature verifications shouldn't pose a problem, since it controls the rate of its dials. Only for servers this might be a problem, since a malicious client could force a server to waste resources on signature verification. However, this is not a particularly interesting DoS vector, since the client's certificate is sent in its second flight (after receiving the ServerHello and the server's certificate), so it requires the attacker to actually perform most of the TLS handshake, including encrypting the certificate chain with a key that's tied to that handshake.


### Versioning - How we could roll out a new version of this protocol in the future

An earlier version of this document included a version negotiation mechanism. While it is a desireable property to be able to change things in the future, it also adds a lot of complexity.

To keep things simple, the current proposal does not include a version negotiation mechanism. A future version of this protocol might:

1. Change the format in which the keys are transmitted. A x509 extension has an ID (the Objected Identifier, OID), so we can use a new OID if we want to change the way we encode information. x509 certificates allow use to include multiple extensions, so we can even send the old and the new version during a transition period. In the handshake protocol defined here, peers are required to skip over extensions that they don't understand.
2. For more involved changes, a new version might (ab)use the SNI field in the ClientHello to announce support for new versions. To allow for this to work, the current version requires clients to send anything in the SNI field and server to completely ignore this field, no matter what its contents are.
67 changes: 67 additions & 0 deletions tls/tls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# libp2p TLS Handshake

## Introduction

This document describes how [TLS 1.3](https://tools.ietf.org/html/rfc8446) is used to secure libp2p connections. Endpoints authenticate to their peers by encoding their public key into a x509 certificate extension. The protocol described here allows peers to use arbitrary key types, not constrained to those for which signing of a x509 certificates is specified.


## Handshake Protocol

The libp2p handshake uses TLS 1.3 (and higher). Endpoints MUST NOT negotiate lower TLS versions.

During the handshake, peers authenticate each other’s identity as described in [Peer Authentication](#peer-authentication). Endpoints MUST verify the peer's identity. Specifically, this means that servers MUST require client authentication during the TLS handshake, and MUST abort a connection attempt if the client fails to provide the requested authentication information.


## Peer Authentication

In order to be able use arbitrary key types, peers don’t use their host key to sign the x509 certificate they send during the handshake. Instead, the host key is encoded into the [libp2p Public Key Extension](#libp2p-public-key-extension), which is carried in a self-signed certificate. The key used to generate and sign this certificate SHOULD NOT be related to the host's key. Endpoints MAY generate a new key and certificate for every connection attempt, or they MAY reuse the same key and certificate for multiple connections. Endpoints MUST choose a key that will allow the peer to verify the certificate (i.e. choose a signature algorithm that the peer supports), and SHOULD use a key type which allows for efficient signature computation and which reduces the combined size of the certificate and the signature.

Endpoints MUST NOT send a certificate chain that contains more than one certificate. The certificate MUST have NotBefore and NotAfter fields set such that the certificate is valid at the time it is received by the peer. When receiving the certificate chain, an endpoint MUST check these conditions and abort the connection attempt if the presented certificate is not yet valid or if it is expired.

The certificate MUST contain the [libp2p Public Key Extension](#libp2p-public-key-extension). If this extension is missing, endpoints MUST abort the connection attempt. The certificate MAY contain other extensions, implementations MUST ignore extensions with unknown OIDs.

Note for clients: Since clients complete the TLS handshake immediately after sending the certificate (and the TLS ClientFinished message), the handshake will appear as having succeeded before the server had the chance to verify the certificate. In this state, the client can already send application data. If certificate verification fails on the server side, the server will close the connection without processing any data that the client sent.



### libp2p Public Key Extension

In order to prove ownership of its host key, an endpoint sends two values:
- the public host key
- a signature performed using the private host key

The public host key allows the peer to calculate the peer ID of the peer it is connecting to. Clients MUST verify that the peer ID derived from the certificate matches the peer ID they intended to connect to, and MUST abort the connection if there is a mismatch.

The peer signs the concatenation of the string "libp2p-tls-handshake:" and the public key that it used to generate the certificate carrying the libp2p Public Key Extension using its private host key. This signature provides cryptographic proof that the peer was in possession of the private host key at the time the certificate was signed. Peers MUST verify the signature, and abort the connection attempt if signature verification fails.

The public host key and the signature are ANS.1-encoded into the SignedKey data structure, which is carried in the libp2p Public Key Extension. The libp2p Public Key Extension is a x509 extension with the Object Identier 1.3.6.1.4.1.53594.1.1.

```asn1
SignedKey ::= SEQUENCE {
publicKey BIT STRING,
signature BIT STRING
}
```

The publicKey field of SignedKey contains the public host key of the endpoint, encoded using the following protobuf.

```protobuf
enum KeyType {
RSA = 0;
Ed25519 = 1;
Secp256k1 = 2;
ECDSA = 3;
}
message PublicKey {
required KeyType Type = 1;
required bytes Data = 2;
}
```

TODO: PublicKey.Data looks underspecified. Define precisely how to marshal the key.


## Future Extensibility

Future versions of this handshake protocol MAY use the Server Name Indication in the ClientHello as defined in [RFC 6066, section 3](https://tools.ietf.org/html/rfc6066) to announce their support for other versions. In order to keep this flexibility for future versions, clients that only support the version of the handshake defined in this document MUST NOT send any value in the Server Name Indication. Servers that only this version MUST ignore this field, specifically, they MUST NOT check if it was empty.

0 comments on commit adce976

Please sign in to comment.