Skip to content

Commit

Permalink
caBLE tunnel server (#291)
Browse files Browse the repository at this point in the history
* backend: add connection TTL and traffic limits
* backend: return HTTP errors when peers are not available
* common: add request / response copy functions
* common: add Display implementation for CablePath
* common: make CablePath struct more consistent
* frontend: check routing_id
* frontend: simplify plumbing
* log things better
* remove many unwraps
* add more logging to library
* cable-tunnel-server: increase TTL and add sample exchange
* backend: implement flags
* Migrated backend to dev version of hyper, and implement TLS.
Works with Chrome and Safari.
* Move some TLS bits out, add debugging
* Migrate frontend to new version of hyper, and implement some basic routing. Move some boilerplate into common. Implement HTTPS frontend.
* frontend: break loops, and remove excess state
* shuffle more state
* add docs
* wip: self_tx mode, probably will remove
* refactor out a bunch of futures stuff to propagate error states better
* document tunnel server better, improve logging and error handling
* Make http dependency optional
* Define all cable-tunnel-server dependencies in the workspace, and share those with other components.
* fix building cable docs
* a bunch of wordsmithing, using tracing spans
* document a bunch of things, instrument the frontend, make debug handler disableable
* add some router tests
* clippy
* fmt
  • Loading branch information
micolous authored Apr 27, 2023
1 parent 93089ea commit dcb6ebd
Show file tree
Hide file tree
Showing 13 changed files with 1,895 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ members = [
"webauthn-rs",
# Authenticator interactions
"webauthn-authenticator-rs",
# caBLE tunnel server
"cable-tunnel-server/backend",
"cable-tunnel-server/common",
"cable-tunnel-server/frontend",
"fido-key-manager",
# Authenticator CLI,
"authenticator-cli",
Expand All @@ -33,6 +37,7 @@ exclude = [

[workspace.dependencies]
base64urlsafedata = { path = "./base64urlsafedata" }
cable-tunnel-server-common = { path = "./cable-tunnel-server/common" }
webauthn-authenticator-rs = { path = "./webauthn-authenticator-rs" }
webauthn-rs = { path = "./webauthn-rs" }
webauthn-rs-core = { path = "./webauthn-rs-core" }
Expand All @@ -44,6 +49,10 @@ clap = { version = "^3.2", features = ["derive", "env"] }
compact_jwt = "0.2.3"
futures = "^0.3.25"
hex = "0.4.3"
http = "^0.2.9"
http-body = "=1.0.0-rc.2"
http-body-util = "=0.1.0-rc.2"
hyper = { version = "=1.0.0-rc.3", default-features = false, features = ["http1"] }
nom = "7.1"
openssl = "^0.10.41"
rand = "0.8"
Expand All @@ -53,8 +62,10 @@ serde_json = "^1.0.79"
tide = "0.16"
thiserror = "^1.0.37"
tokio = { version = "1.22.0", features = ["sync", "test-util", "macros", "rt-multi-thread", "time"] }
tokio-native-tls = "^0.3.1"
tokio-tungstenite = { version = "^0.18.0", features = ["native-tls"] }
tracing = "^0.1.35"
tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
tungstenite = { version = "^0.18.0", default-features = false, features = ["handshake"] }
url = "2"
uuid = "^1.1.2"
113 changes: 113 additions & 0 deletions cable-tunnel-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# webauthn-rs caBLE tunnel server

**Important:** it is only necessary for an *authenticator vendor* to run a caBLE
tunnel service for their devices. Initiators (such as browsers and client
applications) connect to a tunnel service of the *authenticator's* choosing.

**Warning:** this is still a work in progress, and not yet fully implemented.

However, you can run a single-task tunnel service with the `backend` alone:
[see `./backend/README.md` for instructions][0].

[0]: ./backend/README.md

## Background

To facilitate two-way communication between an initiator (browser) and
authenticator (mobile phone), caBLE uses a WebSocket tunnel server. There are
tunnel servers run by Apple (`cable.auth.com`) and Google (`cable.ua5v.com`),
and a facility to procedurally generate new tunnel server domain names
([run `webauthn-authenticator-rs`' `cable_domain` example][1]).

[1]: ../webauthn-authenticator-rs/examples/cable_tunnel.rs

As far as the tunnel server is concerned, what happens is:

1. The authenticator and initator choose a 16 byte tunnel ID.

2. The authenticator connects to a tunnel server of its choosing, using HTTPS.

3. The authenticator makes a WebSocket request to `/cable/new/${TUNNEL_ID}`[^new].

4. The tunnel server responds with a WebSocket handshake, and includes a 3 byte
routing ID in the HTTP response headers to indicate which task is serving
the request.

5. The authenticator transmits the tunnel server ID and routing ID to the
initiator using an encrypted Bluetooth Low Energy advertisement.

6. The initiator decrypts the advertisement, and connects to the tunnel server
using HTTPS.

7. The initiator makes a WebSocket request to
`/cable/connect/${ROUTING_ID}/${TUNNEL_ID}`.

8. The tunnel server responds with a WebSocket handshake.

9. The tunnel server relays binary WebSocket messages between the authenticator
and initiator.

The initiator starts a Noise channel with the authenticator for further
communication such that the tunnel server cannot read their communications, and
then does registration or authentication using the FIDO 2 protocol.

Aside from implementing some basic request filtering, message limits and session
limits, the tunnel server implementations are very simple. The tunnel server
itself does not need to concern itself with the minutae of the Noise protocol -
it only needs to pass binary messages across the tunnel verbatim.

[^new]:
This [follows Google's caBLE URL convention][2]. The URL used to establish a
new channel [is not part of the FIDO 2.2 specification][3].

[2]: https://source.chromium.org/chromium/chromium/src/+/main:device/fido/cable/v2_handshake.cc?q=symbol%3A%5Cbdevice%3A%3Acablev2%3A%3Atunnelserver%3A%3AGetNewTunnelURL%5Cb%20case%3Ayes
[3]: https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#ref-for-client-platform①⓪

## Design

`webauthn-rs`' caBLE tunnel server consists of three parts:

* [backend][]: serving binary which passes messages between the authenticator
and initiator on a known tunnel ID.

* [frontend][]: serving binary which routes requests to a `backend` task based
on the routing ID (for `connect` / initiator requests), or some other load
balancing algorithm (for `new` / authenticator requests).

* [common][]: contains all the shared web server, TLS and caBLE components for
the `backend` and `frontend` binaries.

[backend]: ./backend/
[frontend]: ./frontend/
[common]: ./common/

### Backend

**Source:** [`./backend/`][backend]

It should be possible to run the `backend` without a `frontend` – in this case
the routing ID will be ignored, and all tunnels exist inside of a single serving
task.

### Frontend

**Warning:** The `frontend` is not yet fully implemented, and does not yet do
everything described here. This would be necessary to operate a
high-availability caBLE tunnel service.

**Source:** [`./frontend/`][frontend]

The `frontend` needs to do some basic request processing (for routing) before
handing off the connection to a `backend`:

* For connecting to existing tunnels, the `frontend` needs to connect to
arbitrary `backend` tasks *in any location*.

* For establishing new tunnels, the `frontend` should prefer to route to "local"
`backend` tasks, taking into account backend availability and load balancing.

This will probably need some distributed lock service to allocate the routing
IDs.

While it would be possible to route based on the tunnel ID *alone*, this would
make tunnel create / fetch operations (in the `backend`) global.
29 changes: 29 additions & 0 deletions cable-tunnel-server/backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "cable-tunnel-server-backend"
version = "0.1.0"
authors = ["Michael Farrell <micolous+git@gmail.com>"]
categories = ["authentication"]
description = "webauthn-rs caBLE tunnel server backend"
edition = "2021"
keywords = ["cable", "hybrid", "fido", "webauthn"]
license = "MPL-2.0"
readme = "README.md"
repository = "https://github.com/kanidm/webauthn-rs/"
rust-version = "1.66.0"

[dependencies]
cable-tunnel-server-common.workspace = true

clap.workspace = true
futures.workspace = true
hex.workspace = true
http-body.workspace = true
http-body-util.workspace = true
hyper = { workspace = true, features = ["server"] }
thiserror.workspace = true
tokio.workspace = true
tokio-native-tls.workspace = true
tokio-tungstenite.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tungstenite.workspace = true
108 changes: 108 additions & 0 deletions cable-tunnel-server/backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# webauthn-rs cable-tunnel-server-backend

This binary provides a caBLE tunnel server, which is intended for
*non-production use only*.

The `backend` can run in two configurations:

* a single-task configuration, directly serving requests with no frontend.

In this configuration, caBLE [Routing IDs][background] are ignored, and it is
presumed all incoming requests can be served out of a single running task.

* a multi-task configuration, with many frontend tasks.

In this configuration, the backend presumes it has frontend tasks in front of
it to [handle caBLE Routing IDs][background]. However, the frontend is not yet
fully implemented.

The `backend` is stateless, and is not capable of communicating with other
tasks on its own. Each tunnel exists within one (*and only one*) `backend` task,
and `backend` tasks never process caBLE [Routing IDs][background].

[background]: ../README.md#background

## Building

You can build the `backend` using Cargo:

```sh
cargo build
```

This will output a binary to `./target/debug/cable-tunnel-server-backend`.

You can also run the server via Cargo:

```sh
cargo run -- --help
```

## Configuring the server

The server is configured with command-line flags, which can be seen by running
the server with `--help`.

To run the server at http://127.0.0.1:8080 (for testing with
`webauthn-authenticator-rs` built with the `cable-override-tunnel` feature):

```sh
./cable-tunnel-server-backend \
--bind-address 127.0.0.1:8080 \
--insecure-http-server
```

To run the server with HTTPS and strict `Origin` header checks:

```sh
./cable-tunnel-server-backend \
--bind-address 192.0.2.1:443 \
--tls-public-key /etc/ssl/certs/cable.example.com.pem \
--tls-private-key /etc/ssl/certs/cable.example.com.key \
--origin cable.example.com
```

> **Important:** caBLE has an algorithm to deriving tunnel server domain names –
> you cannot host the service on an arbitrary domain name of your choosing.
>
> Run [`webauthn-authenticator-rs`' `cable_domain` example][cable_domain] to
> derive hostnames at the command line.
[cable_domain]: ../../webauthn-authenticator-rs/examples/cable_domain.rs

## Logging

By default, the server runs at log level `info`. This can be changed with the
`RUST_LOG` environment variable, using the
[log levels available in the `tracing` crate][log-levels].

The server logs the following at each level, plus all the messages in the levels
above it:

* `error`: TLS handshake errors, TCP connection errors, incorrect or unknown
HTTP requests

* `warn`: warnings about using unencrypted HTTP

* `info`: (default) start-up messages, HTTP connection lifetime, HTTP request
logs, WebSocket tunnel lifetime

* `debug`: n/a

* `trace`: adds complete incoming HTTP requests, WebSocket tunnel messages

[log-levels]: https://docs.rs/tracing/*/tracing/struct.Level.html

## Monitoring

The server exports some basic metrics at `/debug`:

* `server_state.strong_count`: the number of strong references to
`Arc<ServerState>`

* `peer_map`: a `HashMap` of all pending tunnels - those where the authenticator
has connected but the initiator has not yet connected.

* `peer_map.capacity`: the capacity of the pending tunnels `HashMap`

* `peer_map.len`: the number of pending tunnels
Loading

0 comments on commit dcb6ebd

Please sign in to comment.