Skip to content

Commit 0d9622e

Browse files
authored
Merge branch 'master' into even_more_extensions
2 parents 231edf9 + b017fd5 commit 0d9622e

13 files changed

Lines changed: 263 additions & 157 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ _Looking for the D-Bus API proposal?_ Check out [platform-api][linux-credentials
3535
- 🟢 GetPinUvAuthTokenUsingUvWithPermissions
3636
- [Passkey Authentication][passkeys]
3737
- 🟢 Discoverable credentials (resident keys)
38-
- 🟢 Hybrid transport (caBLE v2): QR-initiated transactions ([#52][#52]: iOS only)
38+
- 🟢 Hybrid transport (caBLE v2): QR-initiated transactions
3939
- 🟠 Hybrid transport (caBLE v2): State-assisted transactions ([#31][#31]: planned)
4040

4141
## Transports
4242

4343
| | USB (HID) | Bluetooth Low Energy (BLE) | NFC | TPM 2.0 (Platform) | Hybrid (caBLEv2) |
4444
| -------------------- | ------------------------- | -------------------------- | --------------------- | --------------------- | ---------------------------------- |
45-
| **FIDO U2F** | 🟢 Supported (via hidapi) | 🟢 Supported (via bluez) | 🟠 Planned ([#5](#5)) | 🟠 Planned ([#4][#4]) | N/A |
46-
| **WebAuthn (FIDO2)** | 🟢 Supported (via hidapi) | 🟢 Supported (via bluez) | 🟠 Planned ([#5](#5)) | 🟠 Planned ([#4][#4]) | 🟠 Partly implemented ([#31][#31]) |
45+
| **FIDO U2F** | 🟢 Supported (via hidapi) | 🟢 Supported (via bluez) | 🟠 Planned ([#5][#5]) | 🟠 Planned ([#4][#4]) | N/A |
46+
| **WebAuthn (FIDO2)** | 🟢 Supported (via hidapi) | 🟢 Supported (via bluez) | 🟠 Planned ([#5][#5]) | 🟠 Planned ([#4][#4]) | 🟠 Partly implemented ([#31][#31]) |
4747

4848
## Example programs
4949

libwebauthn/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ num_enum = "0.7.1"
4141
x509-parser = "0.16.0"
4242
time = "0.3.35"
4343
curve25519-dalek = "4.1.3"
44-
hex = "0.4.2"
44+
hex = "0.4.3"
4545
mockall = "0.11.4"
4646
hidapi = { version = "2.4.1", default-features = false, features = [
4747
"linux-static-hidraw",

libwebauthn/examples/webauthn_cable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use libwebauthn::proto::ctap2::{
2525
use libwebauthn::transport::Device;
2626
use libwebauthn::webauthn::{Error as WebAuthnError, WebAuthn};
2727

28-
const TIMEOUT: Duration = Duration::from_secs(10);
28+
const TIMEOUT: Duration = Duration::from_secs(120);
2929

3030
fn setup_logging() {
3131
tracing_subscriber::fmt()

libwebauthn/src/pin.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,10 @@ where
391391
return Err(Error::Platform(PlatformError::PinTooLong));
392392
}
393393

394-
let uv_proto = select_uv_proto(&get_info_response).await?;
394+
let Some(uv_proto) = select_uv_proto(&get_info_response).await else {
395+
error!("No supported PIN/UV auth protocols found");
396+
return Err(Error::Ctap(CtapError::Other));
397+
};
395398

396399
let current_pin = match get_info_response.options.as_ref().unwrap().get("clientPin") {
397400
// Obtaining the current PIN, if one is set

libwebauthn/src/proto/ctap2/model.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ impl From<&Ctap1Transport> for Ctap2Transport {
140140

141141
#[derive(Debug, Clone, Serialize, Deserialize)]
142142
pub struct Ctap2PublicKeyCredentialDescriptor {
143-
pub r#type: Ctap2PublicKeyCredentialType,
144143
pub id: ByteBuf,
144+
pub r#type: Ctap2PublicKeyCredentialType,
145145

146146
#[serde(skip_serializing_if = "Option::is_none")]
147147
pub transports: Option<Vec<Ctap2Transport>>,
@@ -157,11 +157,11 @@ pub enum Ctap2COSEAlgorithmIdentifier {
157157

158158
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
159159
pub struct Ctap2CredentialType {
160-
#[serde(rename = "type")]
161-
pub public_key_type: Ctap2PublicKeyCredentialType,
162-
163160
#[serde(rename = "alg")]
164161
pub algorithm: Ctap2COSEAlgorithmIdentifier,
162+
163+
#[serde(rename = "type")]
164+
pub public_key_type: Ctap2PublicKeyCredentialType,
165165
}
166166

167167
impl Default for Ctap2CredentialType {
@@ -206,3 +206,40 @@ pub enum Ctap2UserVerificationOperation {
206206
GetPinToken,
207207
None,
208208
}
209+
210+
#[cfg(test)]
211+
mod tests {
212+
use crate::proto::ctap2::Ctap2PublicKeyCredentialDescriptor;
213+
214+
use super::{Ctap2CredentialType, Ctap2COSEAlgorithmIdentifier, Ctap2PublicKeyCredentialType};
215+
use serde_bytes::ByteBuf;
216+
use serde_cbor;
217+
use hex;
218+
219+
#[test]
220+
/// Verify CBOR serialization conforms to CTAP canonical standard, including ordering (see #95)
221+
pub fn credential_type_field_serialization() {
222+
let credential_type = Ctap2CredentialType {
223+
algorithm: Ctap2COSEAlgorithmIdentifier::ES256,
224+
public_key_type: Ctap2PublicKeyCredentialType::PublicKey,
225+
};
226+
let serialized = serde_cbor::to_vec(&credential_type).unwrap();
227+
// Known good, verified by hand with cbor.me playground
228+
let expected = hex::decode("a263616c672664747970656a7075626c69632d6b6579").unwrap();
229+
assert_eq!(serialized, expected);
230+
}
231+
232+
#[test]
233+
/// Verify CBOR serialization conforms to CTAP canonical standard, including ordering (see #95)
234+
pub fn credential_descriptor_serialization() {
235+
let credential_descriptor = Ctap2PublicKeyCredentialDescriptor {
236+
id: ByteBuf::from(vec![0x42]),
237+
r#type: Ctap2PublicKeyCredentialType::PublicKey,
238+
transports: None,
239+
};
240+
let serialized = serde_cbor::to_vec(&credential_descriptor).unwrap();
241+
// Known good, verified by hand with cbor.me playground
242+
let expected = hex::decode("a2626964414264747970656a7075626c69632d6b6579").unwrap();
243+
assert_eq!(serialized, expected);
244+
}
245+
}

libwebauthn/src/proto/ctap2/protocol.rs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ where
101101
async fn ctap2_make_credential(
102102
&mut self,
103103
request: &Ctap2MakeCredentialRequest,
104-
_timeout: Duration,
104+
timeout: Duration,
105105
) -> Result<Ctap2MakeCredentialResponse, Error> {
106106
trace!(?request);
107-
self.cbor_send(&request.into(), TIMEOUT_GET_INFO).await?;
108-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
107+
self.cbor_send(&request.into(), timeout).await?;
108+
let cbor_response = self.cbor_recv(timeout).await?;
109109
match cbor_response.status_code {
110110
CtapError::Ok => (),
111111
error => return Err(Error::Ctap(error)),
@@ -121,11 +121,11 @@ where
121121
async fn ctap2_get_assertion(
122122
&mut self,
123123
request: &Ctap2GetAssertionRequest,
124-
_timeout: Duration,
124+
timeout: Duration,
125125
) -> Result<Ctap2GetAssertionResponse, Error> {
126126
trace!(?request);
127-
self.cbor_send(&request.into(), TIMEOUT_GET_INFO).await?;
128-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
127+
self.cbor_send(&request.into(), timeout).await?;
128+
let cbor_response = self.cbor_recv(timeout).await?;
129129
match cbor_response.status_code {
130130
CtapError::Ok => (),
131131
error => return Err(Error::Ctap(error)),
@@ -140,12 +140,12 @@ where
140140
#[instrument(skip_all)]
141141
async fn ctap2_get_next_assertion(
142142
&mut self,
143-
_timeout: Duration,
143+
timeout: Duration,
144144
) -> Result<Ctap2GetAssertionResponse, Error> {
145145
debug!("CTAP2 GetNextAssertion request");
146146
let cbor_request = CborRequest::new(Ctap2CommandCode::AuthenticatorGetNextAssertion);
147-
self.cbor_send(&cbor_request, TIMEOUT_GET_INFO).await?;
148-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
147+
self.cbor_send(&cbor_request, timeout).await?;
148+
let cbor_response = self.cbor_recv(timeout).await?;
149149
let data = unwrap_field!(cbor_response.data);
150150
let ctap_response = parse_cbor!(Ctap2GetAssertionResponse, &data);
151151
debug!("CTAP2 GetNextAssertion successful");
@@ -154,13 +154,13 @@ where
154154
}
155155

156156
#[instrument(skip_all)]
157-
async fn ctap2_selection(&mut self, _timeout: Duration) -> Result<(), Error> {
157+
async fn ctap2_selection(&mut self, timeout: Duration) -> Result<(), Error> {
158158
debug!("CTAP2 Authenticator Selection request");
159159
let cbor_request = CborRequest::new(Ctap2CommandCode::AuthenticatorSelection);
160160

161161
loop {
162-
self.cbor_send(&cbor_request, TIMEOUT_GET_INFO).await?;
163-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
162+
self.cbor_send(&cbor_request, timeout).await?;
163+
let cbor_response = self.cbor_recv(timeout).await?;
164164
match cbor_response.status_code {
165165
CtapError::Ok => {
166166
return Ok(());
@@ -177,11 +177,11 @@ where
177177
async fn ctap2_client_pin(
178178
&mut self,
179179
request: &Ctap2ClientPinRequest,
180-
_timeou: Duration,
180+
timeout: Duration,
181181
) -> Result<Ctap2ClientPinResponse, Error> {
182182
trace!(?request);
183-
self.cbor_send(&request.into(), TIMEOUT_GET_INFO).await?;
184-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
183+
self.cbor_send(&request.into(), timeout).await?;
184+
let cbor_response = self.cbor_recv(timeout).await?;
185185
match cbor_response.status_code {
186186
CtapError::Ok => (),
187187
error => return Err(Error::Ctap(error)),
@@ -203,11 +203,11 @@ where
203203
async fn ctap2_authenticator_config(
204204
&mut self,
205205
request: &Ctap2AuthenticatorConfigRequest,
206-
_timeout: Duration,
206+
timeout: Duration,
207207
) -> Result<(), Error> {
208208
trace!(?request);
209-
self.cbor_send(&request.into(), TIMEOUT_GET_INFO).await?;
210-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
209+
self.cbor_send(&request.into(), timeout).await?;
210+
let cbor_response = self.cbor_recv(timeout).await?;
211211
match cbor_response.status_code {
212212
CtapError::Ok => {
213213
return Ok(());
@@ -226,11 +226,11 @@ where
226226
async fn ctap2_bio_enrollment(
227227
&mut self,
228228
request: &Ctap2BioEnrollmentRequest,
229-
_timeout: Duration,
229+
timeout: Duration,
230230
) -> Result<Ctap2BioEnrollmentResponse, Error> {
231231
trace!(?request);
232-
self.cbor_send(&request.into(), TIMEOUT_GET_INFO).await?;
233-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
232+
self.cbor_send(&request.into(), timeout).await?;
233+
let cbor_response = self.cbor_recv(timeout).await?;
234234
match cbor_response.status_code {
235235
CtapError::Ok => (),
236236
error => return Err(Error::Ctap(error)),
@@ -252,11 +252,11 @@ where
252252
async fn ctap2_credential_management(
253253
&mut self,
254254
request: &Ctap2CredentialManagementRequest,
255-
_timeout: Duration,
255+
timeout: Duration,
256256
) -> Result<Ctap2CredentialManagementResponse, Error> {
257257
trace!(?request);
258-
self.cbor_send(&request.into(), TIMEOUT_GET_INFO).await?;
259-
let cbor_response = self.cbor_recv(TIMEOUT_GET_INFO).await?;
258+
self.cbor_send(&request.into(), timeout).await?;
259+
let cbor_response = self.cbor_recv(timeout).await?;
260260
match cbor_response.status_code {
261261
CtapError::Ok => (),
262262
error => return Err(Error::Ctap(error)),

libwebauthn/src/transport/ble/btleplug/manager.rs

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ use std::collections::HashMap;
22

33
use btleplug::api::bleuuid::uuid_from_u16;
44
use btleplug::api::{
5-
Central as _, Manager as _, Peripheral as _, PeripheralProperties, ScanFilter,
5+
Central as _, CentralEvent, Manager as _, Peripheral as _, PeripheralProperties, ScanFilter,
66
};
7-
use btleplug::platform::{Adapter, Manager, Peripheral};
8-
use tracing::{debug, info, instrument, Level};
7+
use btleplug::platform::{Adapter, Manager, Peripheral, PeripheralId};
8+
use futures::{Stream, StreamExt};
9+
use tracing::{debug, info, instrument, trace, warn, Level};
910
use uuid::Uuid;
1011

1112
use super::device::FidoEndpoints;
@@ -50,17 +51,64 @@ impl SupportedRevisions {
5051
}
5152
}
5253

54+
async fn on_peripheral_service_data(
55+
adapter: &Adapter,
56+
id: &PeripheralId,
57+
uuids: &[Uuid],
58+
service_data: HashMap<Uuid, Vec<u8>>,
59+
) -> Option<(Peripheral, Vec<u8>)> {
60+
for uuid in uuids {
61+
if let Some(service_data) = service_data.get(uuid) {
62+
trace!(?id, ?service_data, "Found service data");
63+
let Ok(peripheral) = adapter.peripheral(id).await else {
64+
warn!(?id, "Could not get peripheral");
65+
return None;
66+
};
67+
68+
debug!({ ?id, ?service_data }, "Found service data for peripheral");
69+
return Some((peripheral, service_data.to_owned()));
70+
}
71+
}
72+
73+
trace!(
74+
{ ?id, ?service_data },
75+
"Ignoring periperal as it doesn't have service data for desired UUID"
76+
);
77+
None
78+
}
79+
5380
#[instrument(level = Level::DEBUG, skip_all)]
54-
pub async fn start_discovery(uuids: &[Uuid]) -> Result<(), Error> {
81+
/// Starts a discovery for devices advertising service data on any of the provided UUIDs
82+
pub async fn start_discovery_for_service_data(
83+
uuids: &[Uuid],
84+
) -> Result<impl Stream<Item = (Peripheral, Vec<u8>)> + use<'_>, Error> {
5585
let adapter = get_adapter().await?;
56-
let scan_filter = ScanFilter {
57-
services: uuids.to_vec(),
58-
};
86+
let scan_filter = ScanFilter::default();
87+
88+
let events = adapter.events().await.or(Err(Error::Unavailable))?;
5989

6090
adapter
6191
.start_scan(scan_filter)
6292
.await
63-
.or(Err(Error::ConnectionFailed))
93+
.or(Err(Error::ConnectionFailed))?;
94+
95+
let stream = events.filter_map({
96+
move |event| {
97+
let adapter = adapter.clone();
98+
let uuids = uuids.to_vec();
99+
async move {
100+
// trace!(?event);
101+
match event {
102+
CentralEvent::ServiceDataAdvertisement { id, service_data } => {
103+
on_peripheral_service_data(&adapter, &id, &uuids, service_data).await
104+
}
105+
_ => None,
106+
}
107+
}
108+
}
109+
});
110+
111+
Ok(stream)
64112
}
65113

66114
/// TODO(#86): Support multiple adapters.
@@ -84,6 +132,7 @@ async fn discover_properties(
84132
.properties()
85133
.await
86134
.or(Err(Error::ConnectionFailed))?;
135+
trace!({ ?peripheral, ?properties });
87136
if let Some(properties) = properties {
88137
result.push((peripheral, properties));
89138
}
@@ -117,38 +166,20 @@ pub async fn list_fido_devices() -> Result<Vec<FidoDevice>, Error> {
117166
Ok(with_properties)
118167
}
119168

120-
#[instrument(level = Level::DEBUG, skip_all)]
121-
pub async fn list_devices_with_service_data(
122-
service_uuid: Uuid,
123-
) -> Result<HashMap<FidoDevice, Vec<u8>>, Error> {
124-
let adapter = get_adapter().await?;
125-
let peripherals = adapter
126-
.peripherals()
169+
pub async fn get_device(peripheral: Peripheral) -> Result<Option<FidoDevice>, Error> {
170+
let Some(properties) = peripheral
171+
.properties()
127172
.await
128-
.or(Err(Error::ConnectionFailed))?;
129-
for peripheral in &peripherals {
130-
// TODO: parallelize this
131-
peripheral
132-
.discover_services()
133-
.await
134-
.or(Err(Error::ConnectionFailed))?;
135-
}
136-
let with_properties = discover_properties(peripherals).await?;
137-
Ok(with_properties
138-
.into_iter()
139-
.filter_map(
140-
|(peripheral, properties)| match properties.service_data.get(&service_uuid) {
141-
Some(service_data) => {
142-
let device = FidoDevice {
143-
peripheral,
144-
properties: properties.to_owned(),
145-
};
146-
Some((device, service_data.to_owned()))
147-
}
148-
None => None,
149-
},
150-
)
151-
.collect())
173+
.or(Err(Error::ConnectionFailed))?
174+
else {
175+
return Ok(None);
176+
};
177+
178+
let device = FidoDevice {
179+
peripheral,
180+
properties,
181+
};
182+
Ok(Some(device))
152183
}
153184

154185
pub async fn supported_fido_revisions(

libwebauthn/src/transport/ble/btleplug/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ pub use connection::Connection;
88
pub use device::FidoDevice;
99
pub use error::Error;
1010
pub use manager::{
11-
connect, list_devices_with_service_data, list_fido_devices, start_discovery,
12-
supported_fido_revisions,
11+
connect, list_fido_devices, start_discovery_for_service_data, supported_fido_revisions,
1312
};

0 commit comments

Comments
 (0)