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

caBLE tunnel server #291

Merged
merged 33 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1f7fdaa
wip: tunnel server, probably sucks
micolous Mar 15, 2023
4785042
wip: basic message passing, but in the frontend... (:
micolous Mar 16, 2023
20de131
remove some dead code
micolous Mar 17, 2023
982548f
move around more things and delete old stuff
micolous Mar 17, 2023
db739c9
more shuffling, cutting down
micolous Mar 17, 2023
b75b2fd
still wip frontend
micolous Mar 17, 2023
5ee2b6a
WIP: implement many things:
micolous Mar 20, 2023
3da144a
add more logging to library
micolous Mar 20, 2023
bcfd504
fix clippy
micolous Mar 20, 2023
165c25b
cable-tunnel-server: increase TTL and add sample exchange
micolous Mar 20, 2023
ce9c7cc
backend: implement flags
micolous Mar 20, 2023
2b3d8b5
Migrated backend to dev version of hyper, and implement TLS.
micolous Mar 21, 2023
6c84da4
Move some TLS bits out, add debugging
micolous Mar 22, 2023
4b82e94
Migrate frontend to new version of hyper, and implement some basic ro…
micolous Mar 24, 2023
5b48f08
frontend: break loops, and remove excess state
micolous Mar 24, 2023
961af30
shuffle more state
micolous Mar 24, 2023
a0173a0
add docs
micolous Mar 24, 2023
5c4cec5
wip: self_tx mode, probably will remove
micolous Mar 28, 2023
d19ccee
refactor out a bunch of futures stuff to propagate error states better
micolous Mar 28, 2023
e531237
document tunnel server better, improve logging and error handling
micolous Mar 29, 2023
40017cf
Make http dependency optional
micolous Mar 29, 2023
b559ad4
Define all cable-tunnel-server dependencies in the workspace, and sha…
micolous Mar 29, 2023
35c41cf
fix building cable docs
micolous Mar 29, 2023
9375710
a bunch of wordsmithing, using tracing spans
micolous Mar 30, 2023
870e951
document a bunch of things, instrument the frontend, make debug handl…
micolous Mar 31, 2023
3cd30b5
add some router tests
micolous Mar 31, 2023
e167670
cleanup
micolous Mar 31, 2023
6f956ee
fix clippy
micolous Apr 17, 2023
1444aba
fix broken bit
micolous Apr 17, 2023
daf9472
minor wording
micolous Apr 17, 2023
27b7e16
update cargo.toml
micolous Apr 17, 2023
25e6f2d
fix some docs
micolous Apr 18, 2023
7ed1fa3
Merge branch 'master' into cable-tunnel-server
micolous Apr 27, 2023
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
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
micolous marked this conversation as resolved.
Show resolved Hide resolved
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 <[email protected]>"]
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