From 17c7ccce323a10318e26f77d9d6ce2a100862a5b Mon Sep 17 00:00:00 2001 From: achingbrain Date: Tue, 13 Aug 2024 12:24:13 +0100 Subject: [PATCH 1/8] docs: add WebSockets spec Specifies the WebSockets transport with notes on certificates, multiplexing, etc. Fixes #594 --- websockets/README.md | 69 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 websockets/README.md diff --git a/websockets/README.md b/websockets/README.md new file mode 100644 index 000000000..9686559ca --- /dev/null +++ b/websockets/README.md @@ -0,0 +1,69 @@ +# libp2p WebSockets + +| Lifecycle Stage | Maturity | Status | Latest Revision | +|-----------------|--------------------------|--------|-----------------| +| 2A | Candidate Recommendation | Active | r0, 2022-10-12 | + +Authors: [@achingbrain] + +Interest Group: [@MarcoPolo] + +[@achingbrain]: https://github.com/achingbrain +[@MarcoPolo]: https://github.com/MarcoPolo + +See the [lifecycle document](../00-framework-01-spec-lifecycle.md) for context about maturity level +and spec status. + +## Introduction + +[WebSockets](https://websockets.spec.whatwg.org/) are a way for web applications to maintain bidirectional communications with server-side processes. + +All major browsers have shipped WebSocket support and the implementations are both robust and well understood. + +A WebSocket request starts as a regular HTTP request, which is renegotiated as a WebSocket connection using the [HTTP protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism). + +## Drawbacks + +WebSockets suffer from [head of line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking) and provide no mechanism for stream multiplexing, encryption or authentication so additional features must be added by the developer or by libp2p. + +In practice they only run over TCP so are less effective with [DCuTR Holepunching](../relay/DCUtR.md). + +## Certificates + +With [some exceptions](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure) browsers will prevent making connections to unencrypted WebSockets when the request is made from a [Secure Context](https://www.w3.org/TR/secure-contexts/). + +Given that libp2p makes extensive use of the [SubtleCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto), and that API is only available in Secure Contexts, it's safe to assume that any incoming libp2p connections initiated over WebSockets originate from a Secure Context. + +Consequently server-side processes listening for incoming libp2p connections via WebSockets must use TLS certificates that can be verified by the connecting user agent. + +These must be obtained externally and configured in the same way as you would for an HTTP server. + +The only exception to this is if both server and client are operating exclusively on loopback or localhost addresses such as in a testing or offline environment. Such addresses should not be shared outside of these environments. + +## Stream Multiplexing + +WebSockets have no built in stream multiplexing. Server-side processes listening for incoming libp2p connections via WebSockets should support [multi-stream select](https://github.com/multiformats/multistream-select) and negotiate an appropriate stream multiplexer such as [yamux](../yamux/README.md). + +## Authentication + +WebSockets have no built in authentication mechanism. Server-side processes listening for incoming libp2p connections via WebSockets should support [multi-stream select](https://github.com/multiformats/multistream-select) and negotiate an appropriate authentication mechanism such as [noise](../noise/README.md). + +## Encryption + +At the time of writing, the negotiated authentication mechanism should also be used to encrypt all traffic sent over the WebSocket even if TLS certificates are also used at the transport layer. + +A mechanism to avoid this but also maintain backwards compatibility with existing server-side processes will be specified in a future revision to this spec. + +## Addressing + +A WebSocket address contains `/ws`, `/tls/ws` or `/wss` and runs over TCP. If a TCP port is omitted, a secure WebSocket (e.g. `/tls/ws` or `/wss` is assumed to run on TCP port 443), an insecure WebSocket is assumed to run on TCP port 80 similar to HTTP addresses. + +Examples: + +* `/ip4/192.0.2.0/tcp/1234/ws` (an insecure address with a TCP port) +* `/ip4/192.0.2.0/tcp/1234/tls/ws` (a secure address with a TCP port) +* `/ip4/192.0.2.0/ws` (an insecure address that defaults to TCP port 80) +* `/ip4/192.0.2.0/tls/ws` (a secure address that defaults to TCP port 443) +* `/ip4/192.0.2.0/wss` (`/tls` may be omitted when using `/wss`) +* `/dns/example.com/wss` (a DNS address) +* `/dns/example.com/wss/http-path/path%2Fto%2Fendpoint` (an address with a path) From b680589d83ceaf76fc37323ffcbaf8e1d4fbb78f Mon Sep 17 00:00:00 2001 From: achingbrain Date: Tue, 13 Aug 2024 14:03:29 +0100 Subject: [PATCH 2/8] docs: fix WebSockets double encryption Rewords the Encryption section to allow for opt-in single encryption signaled via a noise extension. --- noise/README.md | 1 + websockets/README.md | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/noise/README.md b/noise/README.md index 6277f97a0..2f516febd 100644 --- a/noise/README.md +++ b/noise/README.md @@ -221,6 +221,7 @@ syntax = "proto2"; message NoiseExtensions { repeated bytes webtransport_certhashes = 1; repeated string stream_muxers = 2; + optional bool handshake_only = 3; } message NoiseHandshakePayload { diff --git a/websockets/README.md b/websockets/README.md index 9686559ca..052526f1f 100644 --- a/websockets/README.md +++ b/websockets/README.md @@ -50,9 +50,27 @@ WebSockets have no built in authentication mechanism. Server-side processes list ## Encryption -At the time of writing, the negotiated authentication mechanism should also be used to encrypt all traffic sent over the WebSocket even if TLS certificates are also used at the transport layer. +Server-side processes listening on WebSocket addresses should use TLS certificates to secure transmitted data at the transport level. -A mechanism to avoid this but also maintain backwards compatibility with existing server-side processes will be specified in a future revision to this spec. +This does not provide any assurance that the remote peer possesses the private key that corresponds to their public key, so an additional handshake is necessary. + +During connection establishment over WebSockets, before the connection is made available to the rest of the application, if all of the following criteria are met: + +1. `noise` is negotiated as the connection encryption protocol +2. An initial handshake is performed with the `handshake_only` boolean extension set to true +3. The transport layer is secured by TLS + +Then all subsequent data is sent without encrypting it at the libp2p level, instead relying on TLS encryption at the transport layer. + +If any of the above is not true, all data is encrypted with the negotiated connection encryption method before sending. + +This prevents double-encryption but only when both ends opt-in to ensure backwards compatibility with existing deployments. + +### MITM mitigation + +The TLS certificate used should be signed by a trusted certificate authority, and the host name should correspond to the common name contained within the certificate. + +This requires trusting the certificate authority to issue correct certificates, but is necessary due to limitations of certain user agents, namely web browsers which do not allow use of self-signed certificates that could be otherwise be verified via preshared certificate fingerprints. ## Addressing From 3c3a3a927843f9e9c9184547752bf5983d6144c3 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 2 Sep 2024 14:39:33 +0100 Subject: [PATCH 3/8] docs: add common name to handshake and mention security --- noise/README.md | 1 + websockets/README.md | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/noise/README.md b/noise/README.md index 2f516febd..8e92aa9a7 100644 --- a/noise/README.md +++ b/noise/README.md @@ -222,6 +222,7 @@ message NoiseExtensions { repeated bytes webtransport_certhashes = 1; repeated string stream_muxers = 2; optional bool handshake_only = 3; + optional string tls_common_name = 4; } message NoiseHandshakePayload { diff --git a/websockets/README.md b/websockets/README.md index 052526f1f..957d15f0c 100644 --- a/websockets/README.md +++ b/websockets/README.md @@ -57,8 +57,10 @@ This does not provide any assurance that the remote peer possesses the private k During connection establishment over WebSockets, before the connection is made available to the rest of the application, if all of the following criteria are met: 1. `noise` is negotiated as the connection encryption protocol -2. An initial handshake is performed with the `handshake_only` boolean extension set to true -3. The transport layer is secured by TLS +1. An initial handshake is performed with: + 1. the `handshake_only` boolean extension set to true + 1. a `tls_common_name` extension value that matches the domain name being connected to +1. The transport layer is secured by TLS Then all subsequent data is sent without encrypting it at the libp2p level, instead relying on TLS encryption at the transport layer. @@ -66,12 +68,20 @@ If any of the above is not true, all data is encrypted with the negotiated conne This prevents double-encryption but only when both ends opt-in to ensure backwards compatibility with existing deployments. +Note that by opting-in to single encryption, the dialer is also opting-in to trusting the [CA](https://en.wikipedia.org/wiki/Certificate_authority) system. + ### MITM mitigation -The TLS certificate used should be signed by a trusted certificate authority, and the host name should correspond to the common name contained within the certificate. +The TLS certificate used should be signed by a trusted certificate authority, the host name should correspond to the common name contained within the certificate, and the domain being connected to should match the common name sent as part of the noise handshake. This requires trusting the certificate authority to issue correct certificates, but is necessary due to limitations of certain user agents, namely web browsers which do not allow use of self-signed certificates that could be otherwise be verified via preshared certificate fingerprints. +### Security Considerations + +Protection against man-in-the-middle (MITM) type attacks is through Web [PKI](https://en.wikipedia.org/wiki/Public_key_infrastructure). If the client is in an environment where Web PKI can not be fully trusted (e.g. an enterprise network with a custom enterprise root CA installed on the client), then this authentication scheme can not protect the client from a MITM attack. + +This authentication scheme is also not secure in cases where you do not own your domain name or the certificate. If someone else can get a valid certificate for your domain, you may be vulnerable to a MITM attack. + ## Addressing A WebSocket address contains `/ws`, `/tls/ws` or `/wss` and runs over TCP. If a TCP port is omitted, a secure WebSocket (e.g. `/tls/ws` or `/wss` is assumed to run on TCP port 443), an insecure WebSocket is assumed to run on TCP port 80 similar to HTTP addresses. From d9402f6b479bf13b5362507e1cc1d840148e98e8 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 2 Sep 2024 14:41:24 +0100 Subject: [PATCH 4/8] chore: language --- websockets/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/websockets/README.md b/websockets/README.md index 957d15f0c..29ab579f0 100644 --- a/websockets/README.md +++ b/websockets/README.md @@ -68,7 +68,7 @@ If any of the above is not true, all data is encrypted with the negotiated conne This prevents double-encryption but only when both ends opt-in to ensure backwards compatibility with existing deployments. -Note that by opting-in to single encryption, the dialer is also opting-in to trusting the [CA](https://en.wikipedia.org/wiki/Certificate_authority) system. +Note that by opting-in to single encryption, peers are also opting-in to trusting the [CA](https://en.wikipedia.org/wiki/Certificate_authority) system. ### MITM mitigation From 08a0665dbed7d5f1651eec59f34b7eaf6b0ef4e5 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 4 Sep 2024 12:07:46 +0100 Subject: [PATCH 5/8] chore: add reference to rfc 5705 --- websockets/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/websockets/README.md b/websockets/README.md index 29ab579f0..0ad9c5d3a 100644 --- a/websockets/README.md +++ b/websockets/README.md @@ -82,6 +82,8 @@ Protection against man-in-the-middle (MITM) type attacks is through Web [PKI](ht This authentication scheme is also not secure in cases where you do not own your domain name or the certificate. If someone else can get a valid certificate for your domain, you may be vulnerable to a MITM attack. +Another solution would be to use Keying Material Exporters [RFC 5705](https://www.rfc-editor.org/info/rfc5705) which would remove the need to add data to the noise handshake, however whether this would be exposed as part of browser APIs is unclear at this point. + ## Addressing A WebSocket address contains `/ws`, `/tls/ws` or `/wss` and runs over TCP. If a TCP port is omitted, a secure WebSocket (e.g. `/tls/ws` or `/wss` is assumed to run on TCP port 443), an insecure WebSocket is assumed to run on TCP port 80 similar to HTTP addresses. From f7837dc40089c3d93209214a4da07a471fc39b12 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 4 Sep 2024 12:10:43 +0100 Subject: [PATCH 6/8] chore: add section about completing the handshake --- websockets/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/websockets/README.md b/websockets/README.md index 0ad9c5d3a..1435ecfad 100644 --- a/websockets/README.md +++ b/websockets/README.md @@ -64,6 +64,8 @@ During connection establishment over WebSockets, before the connection is made a Then all subsequent data is sent without encrypting it at the libp2p level, instead relying on TLS encryption at the transport layer. +If this is the case, the server MUST complete the handshake before reading or sending any application data, and MUST abort the connection if the handshake fails. + If any of the above is not true, all data is encrypted with the negotiated connection encryption method before sending. This prevents double-encryption but only when both ends opt-in to ensure backwards compatibility with existing deployments. From cc8b242eb589bf860f3b052eb3f0bc8bad3933a7 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 23 Oct 2024 11:57:02 +0100 Subject: [PATCH 7/8] chore: update noise revision --- noise/README.md | 49 +++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/noise/README.md b/noise/README.md index 8e92aa9a7..22597b0da 100644 --- a/noise/README.md +++ b/noise/README.md @@ -5,7 +5,7 @@ | Lifecycle Stage | Maturity | Status | Latest Revision | |-----------------|----------------|--------|-----------------| -| 3A | Recommendation | Active | r5, 2022-12-07 | +| 3A | Recommendation | Active | r6, 2024-10-23 | Authors: [@yusefnapora] @@ -40,27 +40,32 @@ and spec status. -- [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 Pattern](#handshake-pattern) - - [XX](#xx) -- [Cryptographic Primitives](#cryptographic-primitives) -- [Noise Protocol Name](#noise-protocol-name) -- [Wire Format](#wire-format) -- [Encryption and I/O](#encryption-and-io) -- [Design Considerations](#design-considerations) - - [No Negotiation of Noise Protocols](#no-negotiation-of-noise-protocols) - - [Why the XX handshake pattern?](#why-the-xx-handshake-pattern) - - [Why ChaChaPoly?](#why-chachapoly) - - [Distinct Noise and Identity Keys](#distinct-noise-and-identity-keys) - - [Why Not Noise Signatures?](#why-not-noise-signatures) -- [Changelog](#changelog) - - [r1 - 2020-01-20](#r1---2020-01-20) - - [r2 - 2020-03-30](#r2---2020-03-30) +- [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 Handshake Payload](#the-libp2p-handshake-payload) + - [Handshake Pattern](#handshake-pattern) + - [XX](#xx) + - [Noise Extensions](#noise-extensions) + - [Cryptographic Primitives](#cryptographic-primitives) + - [Noise Protocol Name](#noise-protocol-name) + - [Wire Format](#wire-format) + - [Encryption and I/O](#encryption-and-io) + - [Design Considerations](#design-considerations) + - [No Negotiation of Noise Protocols](#no-negotiation-of-noise-protocols) + - [Why the XX handshake pattern?](#why-the-xx-handshake-pattern) + - [Why ChaChaPoly?](#why-chachapoly) + - [Distinct Noise and Identity Keys](#distinct-noise-and-identity-keys) + - [Why Not Noise Signatures?](#why-not-noise-signatures) + - [Changelog](#changelog) + - [r1 - 2020-01-20](#r1---2020-01-20) + - [r2 - 2020-03-30](#r2---2020-03-30) + - [r3 - 2022-09-20](#r3---2022-09-20) + - [r4 - 2022-09-22](#r4---2022-09-22) From 7c2f77f2927f485b4a336fef680e36b241e8e92b Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 23 Oct 2024 11:58:14 +0100 Subject: [PATCH 8/8] chore: undo toc edit --- noise/README.md | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/noise/README.md b/noise/README.md index 22597b0da..ff70029d6 100644 --- a/noise/README.md +++ b/noise/README.md @@ -40,32 +40,27 @@ and spec status. -- [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 Handshake Payload](#the-libp2p-handshake-payload) - - [Handshake Pattern](#handshake-pattern) - - [XX](#xx) - - [Noise Extensions](#noise-extensions) - - [Cryptographic Primitives](#cryptographic-primitives) - - [Noise Protocol Name](#noise-protocol-name) - - [Wire Format](#wire-format) - - [Encryption and I/O](#encryption-and-io) - - [Design Considerations](#design-considerations) - - [No Negotiation of Noise Protocols](#no-negotiation-of-noise-protocols) - - [Why the XX handshake pattern?](#why-the-xx-handshake-pattern) - - [Why ChaChaPoly?](#why-chachapoly) - - [Distinct Noise and Identity Keys](#distinct-noise-and-identity-keys) - - [Why Not Noise Signatures?](#why-not-noise-signatures) - - [Changelog](#changelog) - - [r1 - 2020-01-20](#r1---2020-01-20) - - [r2 - 2020-03-30](#r2---2020-03-30) - - [r3 - 2022-09-20](#r3---2022-09-20) - - [r4 - 2022-09-22](#r4---2022-09-22) +- [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 Pattern](#handshake-pattern) + - [XX](#xx) +- [Cryptographic Primitives](#cryptographic-primitives) +- [Noise Protocol Name](#noise-protocol-name) +- [Wire Format](#wire-format) +- [Encryption and I/O](#encryption-and-io) +- [Design Considerations](#design-considerations) + - [No Negotiation of Noise Protocols](#no-negotiation-of-noise-protocols) + - [Why the XX handshake pattern?](#why-the-xx-handshake-pattern) + - [Why ChaChaPoly?](#why-chachapoly) + - [Distinct Noise and Identity Keys](#distinct-noise-and-identity-keys) + - [Why Not Noise Signatures?](#why-not-noise-signatures) +- [Changelog](#changelog) + - [r1 - 2020-01-20](#r1---2020-01-20) + - [r2 - 2020-03-30](#r2---2020-03-30)