Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 4 additions & 6 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ jobs:
- name: Update apt cache
run: sudo apt-get update
- name: Install system dependencies
run: sudo apt-get install libudev-dev libdbus-1-dev libsodium-dev
run: sudo apt-get install libudev-dev libdbus-1-dev libsodium-dev libnfc-dev libpcsclite-dev
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build
run: cargo build
run: cargo build --all-targets --all-features
- name: Run tests
run: cargo test --verbose
- name: Install nfc dependencies
run: sudo apt-get install libnfc-dev libpcsclite-dev
- name: Build with nfc-support
run: cargo build --features libnfc,pcsc
8 changes: 4 additions & 4 deletions libwebauthn/examples/bio_enrollment_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
println!("Devices found: {:?}", devices);

for mut device in devices {
println!("Selected HID authenticator: {}", &device);
println!("Selected HID authenticator: {}", device);
let mut channel = device.channel().await?;
channel.wink(TIMEOUT).await?;

Expand Down Expand Up @@ -199,7 +199,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let idx = ask_for_user_input(enrollments.len());
channel
.remove_bio_enrollment(
&enrollments[idx].template_id.as_ref().unwrap(),
enrollments[idx].template_id.as_ref().unwrap(),
TIMEOUT,
)
.await
Expand Down Expand Up @@ -229,7 +229,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let new_name: String = read!("{}\n");
channel
.rename_bio_enrollment(
&enrollments[idx].template_id.as_ref().unwrap(),
enrollments[idx].template_id.as_ref().unwrap(),
&new_name,
TIMEOUT,
)
Expand Down Expand Up @@ -266,7 +266,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
Err(err) => break 'outer Err(err),
};
}
Ok(format!("Success!"))
Ok("Success!".to_string())
}
};
match action {
Expand Down
17 changes: 9 additions & 8 deletions libwebauthn/examples/change_pin_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,37 +72,38 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
println!("Devices found: {:?}", devices);

for mut device in devices {
println!("Selected HID authenticator: {}", &device);
println!("Selected HID authenticator: {}", device);
let mut channel = device.channel().await?;
channel.wink(TIMEOUT).await?;

print!("PIN: Please enter the _new_ PIN: ");
io::stdout().flush().unwrap();
let new_pin: String = read!("{}\n");

if &new_pin == "" {
if new_pin.is_empty() {
println!("PIN: No PIN provided, cancelling operation.");
return Ok(());
}

let state_recv = channel.get_ux_update_receiver();
tokio::spawn(handle_updates(state_recv));

let response = loop {
loop {
match channel.change_pin(new_pin.clone(), TIMEOUT).await {
Ok(response) => break Ok(response),
Ok(response) => {
println!("WebAuthn response: {response:?}");
break;
}
Err(WebAuthnError::Ctap(ctap_error)) => {
if ctap_error.is_retryable_user_error() {
println!("Oops, try again! Error: {}", ctap_error);
continue;
}
break Err(WebAuthnError::Ctap(ctap_error));
panic!("{:?}", WebAuthnError::Ctap(ctap_error));
}
Err(err) => break Err(err),
Err(err) => panic!("{:?}", err),
};
}
.unwrap();
println!("WebAuthn response: {:?}", response);
}

Ok(())
Expand Down
4 changes: 2 additions & 2 deletions libwebauthn/examples/u2f_ble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
// Registration ceremony
println!("Registration request sent (timeout: {:?}).", TIMEOUT);
let register_request =
RegisterRequest::new_u2f_v2(&APP_ID, &challenge, vec![], TIMEOUT, false);
RegisterRequest::new_u2f_v2(APP_ID, challenge, vec![], TIMEOUT, false);

let state_recv = channel.get_ux_update_receiver();
tokio::spawn(handle_updates(state_recv));
Expand All @@ -56,7 +56,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
println!("Signature request sent (timeout: {:?} seconds).", TIMEOUT);
let new_key = response.as_registered_key()?;
let sign_request =
SignRequest::new(&APP_ID, &challenge, &new_key.key_handle, TIMEOUT, true);
SignRequest::new(APP_ID, challenge, &new_key.key_handle, TIMEOUT, true);
let response = channel.u2f_sign(&sign_request).await?;
println!("Response: {:?}", response);
}
Expand Down
4 changes: 2 additions & 2 deletions libwebauthn/examples/u2f_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
// Registration ceremony
println!("Registration request sent (timeout: {:?}).", TIMEOUT);
let register_request =
RegisterRequest::new_u2f_v2(&APP_ID, &challenge, vec![], TIMEOUT, false);
RegisterRequest::new_u2f_v2(APP_ID, challenge, vec![], TIMEOUT, false);

let state_recv = channel.get_ux_update_receiver();
tokio::spawn(handle_updates(state_recv));
Expand All @@ -59,7 +59,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
println!("Signature request sent (timeout: {:?} seconds).", TIMEOUT);
let new_key = response.as_registered_key()?;
let sign_request =
SignRequest::new(&APP_ID, &challenge, &new_key.key_handle, TIMEOUT, true);
SignRequest::new(APP_ID, challenge, &new_key.key_handle, TIMEOUT, true);
let response = channel.u2f_sign(&sign_request).await?;
println!("Response: {:?}", response);
}
Expand Down
8 changes: 7 additions & 1 deletion libwebauthn/examples/webauthn_cable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;
use std::time::Duration;

use libwebauthn::pin::PinRequestReason;
use libwebauthn::transport::cable::is_available;
use libwebauthn::transport::cable::channel::{CableUpdate, CableUxUpdate};
use libwebauthn::transport::cable::known_devices::{
CableKnownDevice, ClientPayloadHint, EphemeralDeviceInfoStore,
Expand Down Expand Up @@ -91,6 +92,11 @@ async fn handle_updates(mut state_recv: Receiver<CableUxUpdate>) {
pub async fn main() -> Result<(), Box<dyn Error>> {
setup_logging();

if !is_available().await {
eprintln!("No Bluetooth adapter found. Cable/Hybrid transport is unavailable.");
return Err("Cable transport not available".into());
}

let device_info_store = Arc::new(EphemeralDeviceInfoStore::default());
let user_id: [u8; 32] = thread_rng().gen();
let challenge: [u8; 32] = thread_rng().gen();
Expand All @@ -100,7 +106,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let mut device: CableQrCodeDevice = CableQrCodeDevice::new_persistent(
QrCodeOperationHint::MakeCredential,
device_info_store.clone(),
);
)?;

println!("Created QR code, awaiting for advertisement.");
let qr_code = QrCode::new(device.qr_code.to_string()).unwrap();
Expand Down
10 changes: 6 additions & 4 deletions libwebauthn/examples/webauthn_nfc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::convert::TryInto;
use std::error::Error;
use std::io::{self, Write};
use std::time::Duration;
Expand Down Expand Up @@ -85,13 +84,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let challenge: [u8; 32] = thread_rng().gen();

if let Some(mut device) = device {
println!("Selected NFC authenticator: {}", &device);
println!("Selected NFC authenticator: {}", device);
let mut channel = device.channel().await?;

// Make Credentials ceremony
let make_credentials_request = MakeCredentialRequest {
challenge: Vec::from(challenge),
origin: "example.org".to_owned(),
hash: Vec::from(challenge),
cross_origin: None,
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
resident_key: Some(ResidentKeyRequirement::Discouraged),
Expand Down Expand Up @@ -128,7 +128,9 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
(&response.authenticator_data).try_into().unwrap();
let get_assertion = GetAssertionRequest {
relying_party_id: "example.org".to_owned(),
hash: Vec::from(challenge),
challenge: Vec::from(challenge),
origin: "example.org".to_owned(),
cross_origin: None,
allow: vec![credential],
user_verification: UserVerificationRequirement::Discouraged,
extensions: None,
Expand Down
5 changes: 2 additions & 3 deletions libwebauthn/examples/webauthn_preflight_hid.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::convert::TryInto;
use std::error::Error;
use std::io::{self, Write};
use std::time::Duration;
Expand Down Expand Up @@ -85,7 +84,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let user_id: [u8; 32] = thread_rng().gen();

for mut device in devices {
println!("Selected HID authenticator: {}", &device);
println!("Selected HID authenticator: {}", device);
let mut channel = device.channel().await?;
channel.wink(TIMEOUT).await?;

Expand Down Expand Up @@ -165,7 +164,7 @@ async fn make_credential_call(
origin: "example.org".to_owned(),
cross_origin: None,
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
user: Ctap2PublicKeyCredentialUserEntity::new(user_id, "mario.rossi", "Mario Rossi"),
resident_key: Some(ResidentKeyRequirement::Discouraged),
user_verification: UserVerificationRequirement::Preferred,
algorithms: vec![Ctap2CredentialType::default()],
Expand Down
25 changes: 19 additions & 6 deletions libwebauthn/src/fido.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,16 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for AuthenticatorData<T> {

let mut cursor = Cursor::new(&data);
let mut rp_id_hash = [0u8; 32];
cursor.read_exact(&mut rp_id_hash).unwrap(); // We checked the length
let flags_raw = cursor.read_u8().unwrap(); // We checked the length
cursor
.read_exact(&mut rp_id_hash)
.map_err(|e| DesError::custom(format!("failed to read rp_id_hash: {e}")))?;
let flags_raw = cursor
.read_u8()
.map_err(|e| DesError::custom(format!("failed to read flags: {e}")))?;
let flags = AuthenticatorDataFlags::from_bits_truncate(flags_raw);
let signature_count = cursor.read_u32::<BigEndian>().unwrap(); // We checked the length
let signature_count = cursor
.read_u32::<BigEndian>()
.map_err(|e| DesError::custom(format!("failed to read signature_count: {e}")))?;

let attested_credential =
if flags.contains(AuthenticatorDataFlags::ATTESTED_CREDENTIALS) {
Expand All @@ -209,13 +215,20 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for AuthenticatorData<T> {
}

let mut aaguid = [0u8; 16];
cursor.read_exact(&mut aaguid).unwrap(); // We checked the length
let credential_id_len = cursor.read_u16::<BigEndian>().unwrap() as usize; // We checked the length
cursor
.read_exact(&mut aaguid)
.map_err(|e| DesError::custom(format!("failed to read aaguid: {e}")))?;
let credential_id_len = cursor
.read_u16::<BigEndian>()
.map_err(|e| DesError::custom(format!("failed to read credential_id_len: {e}")))?
as usize;
if data.len() < 55 + credential_id_len {
return Err(DesError::invalid_length(data.len(), &"55+L"));
}
let mut credential_id = vec![0u8; credential_id_len];
cursor.read_exact(&mut credential_id).unwrap(); // We checked the length
cursor
.read_exact(&mut credential_id)
.map_err(|e| DesError::custom(format!("failed to read credential_id: {e}")))?;

let credential_public_key: PublicKey =
cbor::from_cursor(&mut cursor).map_err(DesError::custom)?;
Expand Down
8 changes: 8 additions & 0 deletions libwebauthn/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
// Deny panic-inducing patterns in production code.
// Tests are allowed to use unwrap/expect/panic for convenience.
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
#![cfg_attr(not(test), deny(clippy::expect_used))]
#![cfg_attr(not(test), deny(clippy::panic))]
#![cfg_attr(not(test), deny(clippy::todo))]
#![cfg_attr(not(test), deny(clippy::unreachable))]

pub mod fido;
pub mod management;
pub mod ops;
Expand Down
14 changes: 6 additions & 8 deletions libwebauthn/src/management/authenticator_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,28 +164,26 @@ impl Ctap2UserVerifiableRequest for Ctap2AuthenticatorConfigRequest {

fn calculate_and_set_uv_auth(
&mut self,
uv_proto: &Box<dyn PinUvAuthProtocol>,
uv_proto: &dyn PinUvAuthProtocol,
uv_auth_token: &[u8],
) {
) -> Result<(), Error> {
// pinUvAuthParam (0x04): the result of calling
// authenticate(pinUvAuthToken, 32×0xff || 0x0d || uint8(subCommand) || subCommandParams).
let mut data = vec![0xff; 32];
data.push(0x0D);
data.push(self.subcommand as u8);
if self.subcommand == Ctap2AuthenticatorConfigCommand::SetMinPINLength {
data.extend(cbor::to_vec(&self.subcommand_params).unwrap());
data.extend(cbor::to_vec(&self.subcommand_params)?);
}
let uv_auth_param = uv_proto.authenticate(uv_auth_token, &data);
let uv_auth_param = uv_proto.authenticate(uv_auth_token, &data)?;
self.protocol = Some(uv_proto.version());
self.uv_auth_param = Some(ByteBuf::from(uv_auth_param));
Ok(())
}

fn client_data_hash(&self) -> &[u8] {
unreachable!()
}

fn permissions(&self) -> Ctap2AuthTokenPermissionRole {
return Ctap2AuthTokenPermissionRole::AUTHENTICATOR_CONFIGURATION;
Ctap2AuthTokenPermissionRole::AUTHENTICATOR_CONFIGURATION
}

fn permissions_rpid(&self) -> Option<&str> {
Expand Down
28 changes: 11 additions & 17 deletions libwebauthn/src/management/bio_enrollment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ where
Ok(Ctap2BioEnrollmentFingerprintSensorInfo {
fingerprint_kind,
max_capture_samples_required_for_enroll: resp
.max_capture_samples_required_for_enroll
.clone(),
.max_capture_samples_required_for_enroll,
max_template_friendly_name: resp.max_template_friendly_name,
})
}
Expand Down Expand Up @@ -297,32 +296,27 @@ impl Ctap2UserVerifiableRequest for Ctap2BioEnrollmentRequest {

fn calculate_and_set_uv_auth(
&mut self,
uv_proto: &Box<dyn PinUvAuthProtocol>,
uv_proto: &dyn PinUvAuthProtocol,
uv_auth_token: &[u8],
) {
) -> Result<(), Error> {
// pinUvAuthParam (0x05): authenticate(pinUvAuthToken, fingerprint (0x01) || enumerateEnrollments (0x04)).
let mut data = match self.subcommand {
None => unreachable!(),
Some(x) => {
let data = vec![Ctap2BioEnrollmentModality::Fingerprint as u8, x as u8];
data
}
};
let subcommand = self
.subcommand
.ok_or(Error::Platform(PlatformError::InvalidDeviceResponse))?;
let mut data = vec![Ctap2BioEnrollmentModality::Fingerprint as u8, subcommand as u8];
// e.g. "Authenticator calls verify(pinUvAuthToken, fingerprint (0x01) || removeEnrollment (0x06) || subCommandParams, pinUvAuthParam)"
if let Some(params) = &self.subcommand_params {
data.extend(cbor::to_vec(&params).unwrap());
data.extend(cbor::to_vec(&params)?);
}
let uv_auth_param = uv_proto.authenticate(uv_auth_token, &data);
let uv_auth_param = uv_proto.authenticate(uv_auth_token, &data)?;
self.protocol = Some(uv_proto.version());
self.uv_auth_param = Some(ByteBuf::from(uv_auth_param));
Ok(())
}

fn client_data_hash(&self) -> &[u8] {
unreachable!()
}

fn permissions(&self) -> Ctap2AuthTokenPermissionRole {
return Ctap2AuthTokenPermissionRole::BIO_ENROLLMENT;
Ctap2AuthTokenPermissionRole::BIO_ENROLLMENT
}

fn permissions_rpid(&self) -> Option<&str> {
Expand Down
Loading