Skip to content

Commit e27d33f

Browse files
caBLE progress: Handshake completed
1 parent 41b3595 commit e27d33f

File tree

8 files changed

+199
-64
lines changed

8 files changed

+199
-64
lines changed

Cargo.lock

Lines changed: 23 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libwebauthn/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ hkdf = "0.12"
5656
ctap-types = { path = "../ctap-types", features = ["alloc"] }
5757
solo = { path = "../solo", optional = true }
5858
text_io = "0.1"
59+
tungstenite = { version = "0.20.1" }
5960
tokio-tungstenite = { version = "0.20.1", features = [
6061
"rustls-tls-native-roots",
6162
] }

libwebauthn/src/transport/ble/channel.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl<'a> Channel for BleChannel<'a> {
7676
self.status
7777
}
7878

79-
async fn close(&self) {
79+
async fn close(&mut self) {
8080
let _x = self.device;
8181
todo!()
8282
}
@@ -117,7 +117,7 @@ impl<'a> Channel for BleChannel<'a> {
117117

118118
#[instrument(level = Level::DEBUG, skip_all)]
119119
async fn cbor_send(
120-
&self,
120+
&mut self,
121121
request: &CborRequest,
122122
timeout: std::time::Duration,
123123
) -> Result<(), Error> {
@@ -133,7 +133,7 @@ impl<'a> Channel for BleChannel<'a> {
133133
}
134134

135135
#[instrument(level = Level::DEBUG, skip_all)]
136-
async fn cbor_recv(&self, timeout: std::time::Duration) -> Result<CborResponse, Error> {
136+
async fn cbor_recv(&mut self, timeout: std::time::Duration) -> Result<CborResponse, Error> {
137137
let response_frame = bluez::frame_recv(&self.connection, timeout)
138138
.await
139139
.or(Err(Error::Transport(TransportError::ConnectionFailed)))?;
Lines changed: 147 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
1+
use core::error;
12
use std::fmt::{Display, Formatter};
23
use std::time::Duration;
34

45
use async_trait::async_trait;
5-
use tracing::instrument;
6+
use futures::stream::FusedStream;
7+
use futures::{SinkExt, StreamExt};
8+
use snow::TransportState;
9+
use tokio::net::TcpStream;
10+
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
11+
use tracing::{debug, error, instrument, trace, warn};
12+
use tungstenite::protocol::Message;
613

714
use crate::proto::{
815
ctap1::apdu::{ApduRequest, ApduResponse},
916
ctap2::cbor::{CborRequest, CborResponse},
1017
};
11-
use crate::transport::error::Error;
18+
use crate::transport::error::{Error, TransportError};
1219
use crate::transport::{channel::ChannelStatus, device::SupportedProtocols, Channel};
1320

1421
use super::known_devices::CableKnownDevice;
1522
use super::qr_code_device::CableQrCodeDevice;
1623

24+
const PADDING_GRANULARITY: usize = 32;
25+
const MAX_CBOR_SIZE: usize = 1024 * 1024;
26+
1727
#[derive(Debug)]
1828
pub enum CableChannelDevice<'d> {
19-
QrCode(&'d mut CableQrCodeDevice<'d>),
20-
Known(&'d mut CableKnownDevice<'d>),
29+
QrCode(&'d CableQrCodeDevice<'d>),
30+
Known(&'d CableKnownDevice<'d>),
2131
}
2232

2333
#[derive(Debug)]
2434
pub struct CableChannel<'d> {
25-
// pub ws_stream: ??
35+
pub ws_stream: WebSocketStream<MaybeTlsStream<TcpStream>>,
36+
pub noise_state: TransportState,
2637
pub device: CableChannelDevice<'d>,
2738
}
2839

29-
impl Drop for CableChannel<'_> {
30-
#[instrument(skip_all)]
31-
fn drop(&mut self) {
32-
todo!()
33-
}
34-
}
35-
3640
impl Display for CableChannel<'_> {
3741
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3842
write!(f, "CableChannel")
@@ -42,30 +46,151 @@ impl Display for CableChannel<'_> {
4246
#[async_trait]
4347
impl<'d> Channel for CableChannel<'d> {
4448
async fn supported_protocols(&self) -> Result<SupportedProtocols, Error> {
45-
todo!()
49+
Ok(SupportedProtocols::fido2_only())
4650
}
4751

4852
async fn status(&self) -> ChannelStatus {
49-
todo!()
53+
match self.ws_stream.is_terminated() {
54+
true => ChannelStatus::Closed,
55+
false => ChannelStatus::Ready,
56+
}
5057
}
5158

52-
async fn close(&self) {
53-
todo!()
59+
async fn close(&mut self) {
60+
if let Err(e) = self.ws_stream.close(None).await {
61+
warn!(?e, "Failed to close WebSocket connection");
62+
}
5463
}
5564

5665
async fn apdu_send(&self, request: &ApduRequest, timeout: Duration) -> Result<(), Error> {
57-
todo!()
66+
error!("APDU send not supported in caBLE transport");
67+
Err(Error::Transport(TransportError::TransportUnavailable))
5868
}
5969

6070
async fn apdu_recv(&self, timeout: Duration) -> Result<ApduResponse, Error> {
61-
todo!()
71+
error!("APDU recv not supported in caBLE transport");
72+
Err(Error::Transport(TransportError::TransportUnavailable))
6273
}
6374

64-
async fn cbor_send(&self, request: &CborRequest, timeout: Duration) -> Result<(), Error> {
65-
todo!()
75+
async fn cbor_send(&mut self, request: &CborRequest, timeout: Duration) -> Result<(), Error> {
76+
debug!("Sending CBOR request");
77+
trace!(?request);
78+
79+
let cbor_request = request.raw_long().or(Err(TransportError::InvalidFraming))?;
80+
81+
if cbor_request.len() > MAX_CBOR_SIZE {
82+
error!(
83+
cbor_request_len = cbor_request.len(),
84+
"CBOR request too large"
85+
);
86+
return Err(Error::Transport(TransportError::InvalidFraming));
87+
}
88+
89+
let extra_bytes = PADDING_GRANULARITY - (cbor_request.len() % PADDING_GRANULARITY);
90+
let padded_len = cbor_request.len() + extra_bytes;
91+
92+
let mut padded_cbor_request = cbor_request.clone();
93+
padded_cbor_request.resize(padded_len, 0u8);
94+
padded_cbor_request[padded_len - 1] = extra_bytes as u8;
95+
96+
let mut encrypted_cbor_request = vec![0u8; MAX_CBOR_SIZE];
97+
match self
98+
.noise_state
99+
.write_message(&padded_cbor_request, &mut encrypted_cbor_request)
100+
{
101+
Ok(size) => {
102+
encrypted_cbor_request.resize(size, 0u8);
103+
}
104+
Err(e) => {
105+
error!(?e, "Failed to encrypt CBOR request");
106+
return Err(Error::Transport(TransportError::ConnectionFailed));
107+
}
108+
}
109+
110+
if let Err(e) = self.ws_stream.send(encrypted_cbor_request.into()).await {
111+
error!(?e, "Failed to send CBOR request");
112+
return Err(Error::Transport(TransportError::ConnectionFailed));
113+
}
114+
115+
Ok(())
66116
}
67117

68-
async fn cbor_recv(&self, timeout: Duration) -> Result<CborResponse, Error> {
69-
todo!()
118+
async fn cbor_recv(&mut self, timeout: Duration) -> Result<CborResponse, Error> {
119+
loop {
120+
let message = match self.ws_stream.next().await {
121+
Some(Err(e)) => {
122+
error!(?e, "Failed to read encrypted CBOR message");
123+
return Err(Error::Transport(TransportError::ConnectionFailed));
124+
}
125+
None => {
126+
error!("Connection was closed before encrypted CBOR response was received");
127+
return Err(Error::Transport(TransportError::ConnectionFailed));
128+
}
129+
Some(Ok(message)) => {
130+
debug!("Received WSS message");
131+
trace!(?message);
132+
message
133+
}
134+
};
135+
136+
let encrypted_frame = match message {
137+
Message::Ping(_) | Message::Pong(_) => {
138+
debug!("Received keepalive message");
139+
continue;
140+
}
141+
Message::Close(close_frame) => {
142+
debug!(?close_frame, "Received close frame");
143+
return Err(Error::Transport(TransportError::ConnectionFailed));
144+
}
145+
Message::Binary(encrypted_frame) => {
146+
debug!(
147+
frame_len = encrypted_frame.len(),
148+
"Received encrypted CBOR response"
149+
);
150+
trace!(?encrypted_frame);
151+
encrypted_frame
152+
}
153+
_ => {
154+
error!(?message, "Unexpected message type received");
155+
return Err(Error::Transport(TransportError::ConnectionFailed));
156+
}
157+
};
158+
159+
let mut decrypted_frame = vec![0u8; MAX_CBOR_SIZE];
160+
match self
161+
.noise_state
162+
.read_message(&encrypted_frame, &mut decrypted_frame)
163+
{
164+
Ok(size) => {
165+
debug!(decrypted_frame_len = size, "Decrypted CBOR response");
166+
decrypted_frame.resize(size, 0u8);
167+
trace!(?decrypted_frame);
168+
}
169+
Err(e) => {
170+
error!(?e, "Failed to decrypt CBOR response");
171+
return Err(Error::Transport(TransportError::ConnectionFailed));
172+
}
173+
}
174+
175+
let padding_len = decrypted_frame[decrypted_frame.len() - 1] as usize;
176+
decrypted_frame.truncate(decrypted_frame.len() - (padding_len + 1));
177+
trace!(
178+
?decrypted_frame,
179+
decrypted_frame_len = decrypted_frame.len(),
180+
"Trimmed padding"
181+
);
182+
183+
// TODO: Unwrap CTAP message which may include a CBOR response
184+
// TODO: Async handling of CTAP incoming messages, including CTAP updates
185+
// TODO: Handle the unsolicited GetInfo response upon connection
186+
187+
let cbor_response: CborResponse = (&decrypted_frame)
188+
.try_into()
189+
.or(Err(TransportError::InvalidFraming))?;
190+
191+
debug!("Received CBOR response");
192+
trace!(?cbor_response);
193+
return Ok(cbor_response);
194+
}
70195
}
71196
}

0 commit comments

Comments
 (0)