Skip to content
2 changes: 1 addition & 1 deletion libwebauthn/examples/webauthn_cable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,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
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
10 changes: 4 additions & 6 deletions libwebauthn/src/management/authenticator_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,23 +166,21 @@ impl Ctap2UserVerifiableRequest for Ctap2AuthenticatorConfigRequest {
&mut self,
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 {
Ctap2AuthTokenPermissionRole::AUTHENTICATOR_CONFIGURATION
Expand Down
21 changes: 8 additions & 13 deletions libwebauthn/src/management/bio_enrollment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,27 +298,22 @@ impl Ctap2UserVerifiableRequest for Ctap2BioEnrollmentRequest {
&mut self,
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 {
Ctap2AuthTokenPermissionRole::BIO_ENROLLMENT
Expand Down
15 changes: 8 additions & 7 deletions libwebauthn/src/management/credential_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,22 @@ impl Ctap2UserVerifiableRequest for Ctap2CredentialManagementRequest {
&mut self,
uv_proto: &dyn PinUvAuthProtocol,
uv_auth_token: &[u8],
) {
let mut data = vec![self.subcommand.unwrap() as u8];
) -> Result<(), Error> {
let subcommand = self
.subcommand
.ok_or(Error::Platform(PlatformError::InvalidDeviceResponse))?;
let mut data = vec![subcommand as u8];

// e.g. pinUvAuthParam (0x04): authenticate(pinUvAuthToken, enumerateCredentialsBegin (0x04) || subCommandParams).
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 {
Ctap2AuthTokenPermissionRole::CREDENTIAL_MANAGEMENT
Expand Down
45 changes: 29 additions & 16 deletions libwebauthn/src/ops/u2f.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::proto::ctap2::{
Ctap2AttestationStatement, Ctap2GetAssertionResponse, Ctap2MakeCredentialResponse,
Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialType, FidoU2fAttestationStmt,
};
use crate::webauthn::{CtapError, Error};
use crate::webauthn::{CtapError, Error, PlatformError};

// FIDO U2F operations can be aliased to CTAP1 requests, as they have no other representation.
pub type RegisterRequest = Ctap1RegisterRequest;
Expand Down Expand Up @@ -61,20 +61,30 @@ impl UpgradableResponse<MakeCredentialResponse, MakeCredentialRequest> for Regis
error!(?self.public_key, "Failed to parse public key as SEC-1 v2 encoded point");
return Err(Error::Ctap(CtapError::Other));
};
let x: heapless::Vec<u8, 32> = heapless::Vec::from_slice(
encoded_point
.x()
.expect("Not the identity point")
.as_bytes(),
)
.unwrap();
let y: heapless::Vec<u8, 32> = heapless::Vec::from_slice(
encoded_point
.y()
.expect("Not identity nor compressed")
.as_bytes(),
)
.unwrap();
let x_bytes = encoded_point.x().ok_or_else(|| {
error!("Public key is the identity point");
Error::Platform(PlatformError::CryptoError(
"public key is the identity point".into(),
))
})?;
let y_bytes = encoded_point.y().ok_or_else(|| {
error!("Public key is identity or compressed");
Error::Platform(PlatformError::CryptoError(
"public key is identity or compressed".into(),
))
})?;
let x: heapless::Vec<u8, 32> =
heapless::Vec::from_slice(x_bytes.as_bytes()).map_err(|_| {
Error::Platform(PlatformError::CryptoError(
"x coordinate exceeds 32 bytes".into(),
))
})?;
let y: heapless::Vec<u8, 32> =
heapless::Vec::from_slice(y_bytes.as_bytes()).map_err(|_| {
Error::Platform(PlatformError::CryptoError(
"y coordinate exceeds 32 bytes".into(),
))
})?;
let cose_public_key = cose::PublicKey::P256Key(cose::P256PublicKey {
x: x.into(),
y: y.into(),
Expand Down Expand Up @@ -173,7 +183,10 @@ impl UpgradableResponse<GetAssertionResponse, SignRequest> for SignResponse {
// 1 Flags Initialized with flags' value.
// 4 Signature counter (signCount) Initialized with signCount bytes.
let authenticator_data = AuthenticatorData {
rp_id_hash: request.app_id_hash.clone().try_into().unwrap(),
rp_id_hash: request.app_id_hash.clone().try_into().map_err(|_| {
error!("app_id_hash has invalid length, expected 32 bytes");
Error::Platform(PlatformError::InvalidDeviceResponse)
})?,
flags,
signature_count,
attested_credential: None,
Expand Down
4 changes: 3 additions & 1 deletion libwebauthn/src/ops/webauthn/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,9 @@ impl Ctap2HMACGetSecretOutput {
} else if output.len() == 64 {
let (o1, o2) = output.split_at(32);
res.output1.copy_from_slice(o1);
res.output2 = Some(o2.try_into().unwrap());
let mut output2 = [0u8; 32];
output2.copy_from_slice(o2);
res.output2 = Some(output2);
} else {
error!("Failed to split HMAC Secret outputs. Unexpected output length: {}. Skipping HMAC extension", output.len());
return None;
Expand Down
Loading
Loading