From 18a009fb2f8533f404456e527dbb025305e00373 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 28 Feb 2019 10:26:26 +0900 Subject: [PATCH 1/7] add TLS spec --- tls/design considerations.md | 47 ++++++++++++++++++++++++ tls/tls.md | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 tls/design considerations.md create mode 100644 tls/tls.md diff --git a/tls/design considerations.md b/tls/design considerations.md new file mode 100644 index 000000000..b6446b46d --- /dev/null +++ b/tls/design considerations.md @@ -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 to 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 perfomance 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 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. diff --git a/tls/tls.md b/tls/tls.md new file mode 100644 index 000000000..37e1ad489 --- /dev/null +++ b/tls/tls.md @@ -0,0 +1,69 @@ +# libp2p TLS Handshake + +## Introduction + +This document describes how [TLS 1.3](https://tools.ietf.org/html/rfc8446) is used to secure libp2p connections. Endpoints authenticates 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 identy. Specifically, this means that servers MUST require clients 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 key corresponding to its host key +- a signature performed using the host key + +The public 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 it there is a mismatch. + +The signature provides cryptographic proof that the peer was in possession of the private key at the time the certificate was signed. Peers MUST verify the signature, and abort the connection attempt if signature verification fails. + +The public 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.XXX.YYY. + +TODO: Nothing will break if we just use an arbitrary value for XXX. However, if we want to do things correctly, [OID](https://en.wikipedia.org/wiki/Object_identifier) PENs should be registered with [IANA](https://pen.iana.org/pen/PenApplication.page). + +```asn1 +SignedKey ::= SEQUENCE { + publicKey BIT STRING, + signature BIT STRING +} +``` + +The publicKey field of SignedKey contains the public 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. From f35dae6ff069dcc3500e30189bb19800f81198c3 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 28 Feb 2019 20:51:35 +0900 Subject: [PATCH 2/7] explain what is signed --- tls/tls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls/tls.md b/tls/tls.md index 37e1ad489..f058e5bb9 100644 --- a/tls/tls.md +++ b/tls/tls.md @@ -32,7 +32,7 @@ In order to prove ownership of its host key, an endpoint sends two values: The public 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 it there is a mismatch. -The signature provides cryptographic proof that the peer was in possession of the private key at the time the certificate was signed. Peers MUST verify the signature, and abort the connection attempt if signature verification fails. +The peer signs its public key using the its host key. This signature provides cryptographic proof that the peer was in possession of the private key at the time the certificate was signed. Peers MUST verify the signature, and abort the connection attempt if signature verification fails. The public 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.XXX.YYY. From 0437cb7f8b0ba4849f560220027980af94fdf183 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 1 Mar 2019 08:28:36 +0900 Subject: [PATCH 3/7] clarify which public key is used --- tls/tls.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tls/tls.md b/tls/tls.md index f058e5bb9..fb35a616c 100644 --- a/tls/tls.md +++ b/tls/tls.md @@ -30,9 +30,9 @@ In order to prove ownership of its host key, an endpoint sends two values: - the public key corresponding to its host key - a signature performed using the host key -The public 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 it there is a mismatch. +The public key corresponding to the 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 it there is a mismatch. -The peer signs its public key using the its host key. This signature provides cryptographic proof that the peer was in possession of the private key at the time the certificate was signed. Peers MUST verify the signature, and abort the connection attempt if signature verification fails. +The peer signs the public key that it used to generate the certificate carrying the libp2p Public Key Extension using the its host key. This signature provides cryptographic proof that the peer was in possession of the private key at the time the certificate was signed. Peers MUST verify the signature, and abort the connection attempt if signature verification fails. The public 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.XXX.YYY. From 565f205b21bfb037ce9a6e39fb17ef9ff322c43f Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 6 Mar 2019 16:45:44 +0900 Subject: [PATCH 4/7] always talk about private / public host key --- tls/tls.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tls/tls.md b/tls/tls.md index fb35a616c..beacc58dc 100644 --- a/tls/tls.md +++ b/tls/tls.md @@ -27,14 +27,14 @@ Note for clients: Since clients complete the TLS handshake immediately after sen ### libp2p Public Key Extension In order to prove ownership of its host key, an endpoint sends two values: -- the public key corresponding to its host key -- a signature performed using the host key +- the public host key +- a signature performed using the private host key -The public key corresponding to the 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 it there is a mismatch. +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 it there is a mismatch. -The peer signs the public key that it used to generate the certificate carrying the libp2p Public Key Extension using the its host key. This signature provides cryptographic proof that the peer was in possession of the private key at the time the certificate was signed. Peers MUST verify the signature, and abort the connection attempt if signature verification fails. +The peer signs the public key that it used to generate the certificate carrying the libp2p Public Key Extension using the 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 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.XXX.YYY. +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.XXX.YYY. TODO: Nothing will break if we just use an arbitrary value for XXX. However, if we want to do things correctly, [OID](https://en.wikipedia.org/wiki/Object_identifier) PENs should be registered with [IANA](https://pen.iana.org/pen/PenApplication.page). @@ -45,7 +45,7 @@ SignedKey ::= SEQUENCE { } ``` -The publicKey field of SignedKey contains the public key of the endpoint, encoded using the following protobuf. +The publicKey field of SignedKey contains the public host key of the endpoint, encoded using the following protobuf. ```protobuf enum KeyType { From 15e57e758874bb85dbf711bfa165e48a26ece20d Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 14 Mar 2019 09:48:23 +0900 Subject: [PATCH 5/7] use the new Protocol Labs PEN for the libp2p public key extension --- tls/tls.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tls/tls.md b/tls/tls.md index beacc58dc..10e83b098 100644 --- a/tls/tls.md +++ b/tls/tls.md @@ -34,9 +34,7 @@ The public host key allows the peer to calculate the peer ID of the peer it is c The peer signs the public key that it used to generate the certificate carrying the libp2p Public Key Extension using the 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.XXX.YYY. - -TODO: Nothing will break if we just use an arbitrary value for XXX. However, if we want to do things correctly, [OID](https://en.wikipedia.org/wiki/Object_identifier) PENs should be registered with [IANA](https://pen.iana.org/pen/PenApplication.page). +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 { From 2f901ae63b57c515d7f6108c9fd8598effd41726 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 14 Mar 2019 11:16:52 +0900 Subject: [PATCH 6/7] sign the public key including a prefix --- tls/tls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls/tls.md b/tls/tls.md index 10e83b098..121fa9ae7 100644 --- a/tls/tls.md +++ b/tls/tls.md @@ -32,7 +32,7 @@ In order to prove ownership of its host key, an endpoint sends two values: 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 it there is a mismatch. -The peer signs the public key that it used to generate the certificate carrying the libp2p Public Key Extension using the 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 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. From 1a5e6eb805fc6c902128288ae1575945bcdc6e2a Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Sat, 23 Mar 2019 06:14:23 +0100 Subject: [PATCH 7/7] apply suggestions from code review Co-Authored-By: marten-seemann --- tls/design considerations.md | 4 ++-- tls/tls.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tls/design considerations.md b/tls/design considerations.md index b6446b46d..667193a31 100644 --- a/tls/design considerations.md +++ b/tls/design considerations.md @@ -19,7 +19,7 @@ The handshake protocol described here: ### TLS 1.3 - What about older versions? -The handshake protocol requires TLS 1.3 support. This means that the handshake between to 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 perfomance and simplify the protocol. +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 @@ -44,4 +44,4 @@ An earlier version of this document included a version negotiation mechanism. Wh 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 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. +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. diff --git a/tls/tls.md b/tls/tls.md index 121fa9ae7..90670568e 100644 --- a/tls/tls.md +++ b/tls/tls.md @@ -2,14 +2,14 @@ ## Introduction -This document describes how [TLS 1.3](https://tools.ietf.org/html/rfc8446) is used to secure libp2p connections. Endpoints authenticates 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. +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 identy. Specifically, this means that servers MUST require clients authentication during the TLS handshake, and MUST abort a connection attempt if the client fails to provide the requested authentication information. +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 @@ -30,7 +30,7 @@ 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 it there is a mismatch. +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.