Skip to content

Commit

Permalink
wip: re-plumbing domain builder
Browse files Browse the repository at this point in the history
  • Loading branch information
micolous committed Apr 11, 2023
1 parent 9e673e9 commit fed3ac5
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 151 deletions.
118 changes: 64 additions & 54 deletions webauthn-authenticator-rs/src/cable/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use tokio_tungstenite::tungstenite::http::{uri::Builder, Uri};
use crate::{
cable::{btle::*, handshake::*, tunnel::get_domain, CableRequestType, Psk},
crypto::{decrypt, encrypt, hkdf_sha_256, public_key_from_private, regenerate},
error::WebauthnCError, pub_if_cable_override_tunnel,
error::WebauthnCError,
};

type BleAdvert = [u8; size_of::<CableEid>() + 4];
Expand Down Expand Up @@ -159,16 +159,6 @@ impl Discovery {
Ok(psk)
}

/// Gets the Websocket connection URI which the platform will use to connect
/// to the authenticator.
pub fn get_connect_uri(&self, eid: &Eid) -> Result<Uri, WebauthnCError> {
let tunnel_id = self.derive_tunnel_id()?;
eid.get_connect_uri(tunnel_id).ok_or_else(|| {
error!("Unknown WebSocket tunnel URL for {:?}", eid);
WebauthnCError::NotSupported
})
}

/// Gets the WebSocket connection URI which the authenticator will use to
/// connect to the initiator.
pub fn get_new_tunnel_uri(&self, domain_id: u16) -> Result<Uri, WebauthnCError> {
Expand All @@ -180,25 +170,22 @@ impl Discovery {
}
}

pub_if_cable_override_tunnel! {
/// Builds a WebSocket connection URI which the authenticator will use
/// to connect to the initiator.
///
/// This method is an internal implementation detail, and can be made
/// public with the `cable_new_tunnel_uri` feature for testing. For all
/// other purposes, use [`get_new_tunnel_uri`][Self::get_new_tunnel_uri]
/// instead.
fn build_new_tunnel_uri(&self, builder: Builder) -> Result<Uri, WebauthnCError> {
// https://source.chromium.org/chromium/chromium/src/+/main:device/fido/cable/v2_handshake.cc;l=170;drc=de9f16dcca1d5057ba55973fa85a5b27423d414f
let tunnel_id = hex::encode_upper(self.derive_tunnel_id()?);
builder
.path_and_query(format!("/cable/new/{}", tunnel_id))
.build()
.map_err(|e| {
error!("cannot build WebSocket tunnel URI: {e}");
WebauthnCError::Internal
})
}
/// Builds a WebSocket connection URI which the authenticator will use
/// to connect to the initiator, using a [Builder] to provide the protocol
/// and scheme.
///
/// This method is an internal implementation detail. Use
/// [`get_new_tunnel_uri`][Self::get_new_tunnel_uri] instead.
pub(super) fn build_new_tunnel_uri(&self, builder: Builder) -> Result<Uri, WebauthnCError> {
// https://source.chromium.org/chromium/chromium/src/+/main:device/fido/cable/v2_handshake.cc;l=170;drc=de9f16dcca1d5057ba55973fa85a5b27423d414f
let tunnel_id = hex::encode_upper(self.derive_tunnel_id()?);
builder
.path_and_query(format!("/cable/new/{}", tunnel_id))
.build()
.map_err(|e| {
error!("cannot build WebSocket tunnel URI: {e}");
WebauthnCError::Internal
})
}
}

Expand Down Expand Up @@ -363,30 +350,28 @@ impl Eid {
/// connect to the authenticator.
///
/// `tunnel_id` is provided by [Discovery::derive_tunnel_id].
fn get_connect_uri(&self, tunnel_id: TunnelId) -> Option<Uri> {
pub fn get_connect_uri(&self, tunnel_id: TunnelId) -> Option<Uri> {
// https://source.chromium.org/chromium/chromium/src/+/main:device/fido/cable/v2_handshake.cc;l=179;drc=de9f16dcca1d5057ba55973fa85a5b27423d414f
self.get_domain_builder()
.and_then(|builder| self.build_connect_uri(builder, tunnel_id))
}

pub_if_cable_override_tunnel! {
/// Builds a WebSocket connection URI which the initiator will use to
/// connect to the authenticator.
///
/// This method is an internal implementation detail, and can be made
/// public with the `cable_new_tunnel_uri` feature for testing. For all
/// other purposes, use [`get_connect_uri`][Self::get_connect_uri]
/// instead.
fn build_connect_uri(&self, builder: Builder, tunnel_id: TunnelId) -> Option<Uri> {
// https://source.chromium.org/chromium/chromium/src/+/main:device/fido/cable/v2_handshake.cc;l=179;drc=de9f16dcca1d5057ba55973fa85a5b27423d414f
let routing_id = hex::encode_upper(self.routing_id);
let tunnel_id = hex::encode_upper(tunnel_id);

builder
.path_and_query(format!("/cable/connect/{}/{}", routing_id, tunnel_id))
.build()
.ok()
}
/// Builds a WebSocket connection URI which the initiator will use to
/// connect to the authenticator.
///
/// This method is an internal implementation detail, and can be made
/// public with the `cable_new_tunnel_uri` feature for testing. For all
/// other purposes, use [`get_connect_uri`][Self::get_connect_uri]
/// instead.
pub(super) fn build_connect_uri(&self, builder: Builder, tunnel_id: TunnelId) -> Option<Uri> {
// https://source.chromium.org/chromium/chromium/src/+/main:device/fido/cable/v2_handshake.cc;l=179;drc=de9f16dcca1d5057ba55973fa85a5b27423d414f
let routing_id = hex::encode_upper(self.routing_id);
let tunnel_id = hex::encode_upper(tunnel_id);

builder
.path_and_query(format!("/cable/connect/{}/{}", routing_id, tunnel_id))
.build()
.ok()
}

// TODO: needed for pairing
Expand Down Expand Up @@ -416,7 +401,7 @@ mod test {
9, 139, 115, 107, 54, 169, 140, 185, 164, 47, //
// Routing ID
9, 10, 11, //
// Tunnel ID
// Tunnel server ID
255, 1,
];
let expected = Eid {
Expand Down Expand Up @@ -467,24 +452,47 @@ mod test {
.unwrap()
.to_string()
);

let builder = Builder::new().scheme("ws").authority("localhost:8080");
assert_eq!(
"ws://localhost:8080/cable/new/3EEF97097986413B059EAA2A30D653D4",
d.build_new_tunnel_uri(builder).unwrap().to_string()
);

let tunnel_id = d.derive_tunnel_id().unwrap();
assert_eq!(
"wss://cable.my4kstlhndi4c.net/cable/connect/090A0B/3EEF97097986413B059EAA2A30D653D4",
d.get_connect_uri(&c).unwrap().to_string()
c.get_connect_uri(tunnel_id.to_owned()).unwrap().to_string()
);

// Changing the tunnel server ID should work too
let mut google_eid = c;
google_eid.tunnel_server_id = 0;
assert_eq!(
"wss://cable.ua5v.com/cable/connect/090A0B/3EEF97097986413B059EAA2A30D653D4",
d.get_connect_uri(&google_eid).unwrap().to_string()
google_eid
.get_connect_uri(tunnel_id.to_owned())
.unwrap()
.to_string()
);

let mut apple_eid = c;
apple_eid.tunnel_server_id = 1;
assert_eq!(
"wss://cable.auth.com/cable/connect/090A0B/3EEF97097986413B059EAA2A30D653D4",
d.get_connect_uri(&apple_eid).unwrap().to_string()
apple_eid
.get_connect_uri(tunnel_id.to_owned())
.unwrap()
.to_string()
);

// Providing a custom builder
let builder = Builder::new().scheme("ws").authority("localhost:8080");
assert_eq!(
"ws://localhost:8080/cable/connect/090A0B/3EEF97097986413B059EAA2A30D653D4",
c.build_connect_uri(builder, tunnel_id.to_owned())
.unwrap()
.to_string()
);

// Changing bits fails
Expand Down Expand Up @@ -552,7 +560,9 @@ mod test {
assert_eq!(expected, r);
assert_eq!(
"wss://cable.ua5v.com/cable/connect/026555/367CBBF5F5085DF4098476AFE4B9B1D2",
discovery.get_connect_uri(&r).unwrap().to_string()
r.get_connect_uri(discovery.derive_tunnel_id().unwrap())
.unwrap()
.to_string()
);
}
}
15 changes: 0 additions & 15 deletions webauthn-authenticator-rs/src/cable/macros.rs

This file was deleted.

128 changes: 125 additions & 3 deletions webauthn-authenticator-rs/src/cable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,19 +290,22 @@
//!
//! [^qr]: Most mobile device camera apps have an integrated QR code scanner.
#[allow(rustdoc::private_intra_doc_links)]
#[cfg(doc)]
use crate::stubs::*;

mod base10;
mod btle;
mod discovery;
mod framing;
mod handshake;
mod macros;
mod noise;
mod tunnel;

use std::collections::BTreeMap;

pub use base10::DecodeError;
pub use btle::Advertiser;
use tokio_tungstenite::tungstenite::http::uri::Builder;

use crate::{
authenticator_hashed::{
Expand Down Expand Up @@ -366,9 +369,43 @@ impl CableRequestType {
/// On some platforms, Bluetooth access requires additional permissions. If this
/// is not available, this returns [WebauthnCError::PermissionDenied]. See
/// [this module's documentation][self] for more information.
#[inline]
pub async fn connect_cable_authenticator<'a, U: UiCallback + 'a>(
request_type: CableRequestType,
ui_callback: &'a U,
) -> Result<CtapAuthenticator<'a, Tunnel, U>, WebauthnCError> {
connect_cable_authenticator_impl(request_type, ui_callback, None).await
}

#[cfg(feature = "cable-override-tunnel")]
/// Connect to an authenticator using caBLE, overriding the WebSocket tunnel
/// protocol and domain.
///
/// ## Warning
///
/// This is intended for testing purposes only, and **is incompatible with other
/// caBLE implementations**. It also allows connecting to tunnel servers over
/// unencrypted HTTP.
///
/// Use [`connect_cable_authenticator()`] instead.
///
/// This is intended to allow a caBLE tunnel server developer to test their code
/// locally, without needing to register an appropriate domain name, set up DNS,
/// or get a TLS certificate.
///
/// This is only available with the `cable-override-tunnel` feature.
pub async fn connect_cable_authenticator_with_tunnel_uri<'a, U: UiCallback + 'a>(
request_type: CableRequestType,
ui_callback: &'a U,
connect_uri: Builder,
) -> Result<CtapAuthenticator<'a, Tunnel, U>, WebauthnCError> {
connect_cable_authenticator_impl(request_type, ui_callback, Some(connect_uri)).await
}

async fn connect_cable_authenticator_impl<'a, U: UiCallback + 'a>(
request_type: CableRequestType,
ui_callback: &'a U,
connect_uri: Option<Builder>,
) -> Result<CtapAuthenticator<'a, Tunnel, U>, WebauthnCError> {
// TODO: it may be better to return a caBLE-specific authenticator object,
// rather than CtapAuthenticator, because the device will close the
Expand All @@ -394,9 +431,18 @@ pub async fn connect_cable_authenticator<'a, U: UiCallback + 'a>(

let psk = disco.derive_psk(&eid)?;

let connect_url = disco.get_connect_uri(&eid)?;
let tunnel_id = disco.derive_tunnel_id()?;
let connect_uri = match connect_uri {
Some(u) => eid.build_connect_uri(u, tunnel_id),
None => eid.get_connect_uri(tunnel_id),
}
.ok_or_else(|| {
error!("unknown WebSocket tunnel URL for {:?}", eid);
WebauthnCError::NotSupported
})?;

let tun = Tunnel::connect_initiator(
&connect_url,
&connect_uri,
psk,
disco.local_identity.as_ref(),
ui_callback,
Expand All @@ -423,14 +469,84 @@ pub async fn connect_cable_authenticator<'a, U: UiCallback + 'a>(
/// example which uses a Bluetooth HCI controller connected to a serial UART.
///
/// * `ui_callback` trait for prompting for user interaction where needed.
#[inline]
pub async fn share_cable_authenticator<'a, U>(
backend: &mut impl AuthenticatorBackendHashedClientData,
info: GetInfoResponse,
url: &str,
tunnel_server_id: u16,
advertiser: &mut impl Advertiser,
ui_callback: &'a U,
close_after_one_command: bool,
) -> Result<(), WebauthnCError>
where
U: UiCallback + 'a,
{
share_cable_authenticator_impl(
backend,
info,
url,
tunnel_server_id,
advertiser,
ui_callback,
close_after_one_command,
None,
)
.await
}

#[cfg(feature = "cable-override-tunnel")]
/// Share an authenticator using caBLE, overriding the WebSocket tunnel URI.
///
/// ## Warning
///
/// This is intended for testing purposes only, and **is incompatible with other
/// caBLE implementations**. It also allows connecting to tunnel servers over
/// unencrypted HTTP.
///
/// Use [`share_cable_authenticator()`] instead.
///
/// This is intended to allow a caBLE tunnel server developer to test their code
/// locally, without needing to register an appropriate domain name, set up DNS,
/// or get a TLS certificate.
///
/// This is only available with the `cable-override-tunnel` feature.
#[inline]
pub async fn share_cable_authenticator_with_tunnel_uri<'a, U>(
backend: &mut impl AuthenticatorBackendHashedClientData,
info: GetInfoResponse,
url: &str,
tunnel_server_id: u16,
advertiser: &mut impl Advertiser,
ui_callback: &'a U,
close_after_one_command: bool,
tunnel_uri: Builder,
) -> Result<(), WebauthnCError>
where
U: UiCallback + 'a,
{
share_cable_authenticator_impl(
backend,
info,
url,
tunnel_server_id,
advertiser,
ui_callback,
close_after_one_command,
Some(tunnel_uri),
)
.await
}

async fn share_cable_authenticator_impl<'a, U>(
backend: &mut impl AuthenticatorBackendHashedClientData,
mut info: GetInfoResponse,
url: &str,
tunnel_server_id: u16,
advertiser: &mut impl Advertiser,
ui_callback: &'a U,
close_after_one_command: bool,
tunnel_uri: Option<Builder>,
) -> Result<(), WebauthnCError>
where
U: UiCallback + 'a,
Expand All @@ -453,7 +569,13 @@ where
let handshake = HandshakeV2::from_qr_url(url)?;
let discovery = handshake.to_discovery()?;

let tunnel_uri = match tunnel_uri {
Some(u) => discovery.build_new_tunnel_uri(u)?,
None => discovery.get_new_tunnel_uri(tunnel_server_id)?,
};

let mut tunnel = Tunnel::connect_authenticator(
&tunnel_uri,
&discovery,
tunnel_server_id,
&handshake.peer_identity,
Expand Down
Loading

0 comments on commit fed3ac5

Please sign in to comment.