Skip to content

Commit

Permalink
Implement basic extensions infrastructure (#77)
Browse files Browse the repository at this point in the history
A few things going on here:
- Previously, `AuthenticatorData` was just a ByteBuf. I added it as a
struct with de/serialize-functions.
- Input and Output of the extensions vary for given commands, so I made
`AuthenticatorData` generic over that, and specify which
extension-struct it will contain inside `MakeCredential` or
`GetAssertion`. This should be totally invisible to the end-user, who
will just get 'the right thing'. It does, however, make the
de/serialize-code a bit messy.
- `hmac-secret` extension needs more work, but this PR was already
getting quite big, so I figured, this would be better in a follow-up PR.
  • Loading branch information
msirringhaus authored Feb 21, 2025
1 parent 8c5b03f commit 864b1c8
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 112 deletions.
7 changes: 4 additions & 3 deletions libwebauthn/examples/webauthn_cable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
user_verification: UserVerificationRequirement::Preferred,
algorithms: vec![Ctap2CredentialType::default()],
exclude: None,
extensions_cbor: vec![],
extensions: None,
timeout: TIMEOUT,
};

Expand All @@ -93,13 +93,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
.unwrap();
println!("WebAuthn MakeCredential response: {:?}", response);

let credential: Ctap2PublicKeyCredentialDescriptor = (&response).try_into().unwrap();
let credential: Ctap2PublicKeyCredentialDescriptor =
(&response.authenticator_data).try_into().unwrap();
let get_assertion = GetAssertionRequest {
relying_party_id: "example.org".to_owned(),
hash: Vec::from(challenge),
allow: vec![credential],
user_verification: UserVerificationRequirement::Discouraged,
extensions_cbor: None,
extensions: None,
timeout: TIMEOUT,
};

Expand Down
140 changes: 140 additions & 0 deletions libwebauthn/examples/webauthn_extensions_hid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::convert::TryInto;
use std::error::Error;
use std::time::Duration;

use ctap_types::ctap2::credential_management::CredentialProtectionPolicy;
use rand::{thread_rng, Rng};
use tracing_subscriber::{self, EnvFilter};

use libwebauthn::ops::webauthn::{
GetAssertionRequest, GetAssertionRequestExtensions, MakeCredentialRequest,
MakeCredentialsRequestExtensions, UserVerificationRequirement,
};
use libwebauthn::pin::{PinProvider, StdinPromptPinProvider};
use libwebauthn::proto::ctap2::{
Ctap2CredentialType, Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialRpEntity,
Ctap2PublicKeyCredentialUserEntity,
};
use libwebauthn::transport::hid::list_devices;
use libwebauthn::transport::Device;
use libwebauthn::webauthn::{Error as WebAuthnError, WebAuthn};

const TIMEOUT: Duration = Duration::from_secs(10);

fn setup_logging() {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.without_time()
.init();
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn Error>> {
setup_logging();

let devices = list_devices().await.unwrap();
println!("Devices found: {:?}", devices);

let user_id: [u8; 32] = thread_rng().gen();
let challenge: [u8; 32] = thread_rng().gen();

let pin_provider: Box<dyn PinProvider> = Box::new(StdinPromptPinProvider::new());

let extensions = MakeCredentialsRequestExtensions {
cred_protect: Some(CredentialProtectionPolicy::Required),
cred_blob: Some(r"My own little blob".into()),
large_blob_key: None,
min_pin_length: Some(true),
hmac_secret: Some(true),
};

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

let mut channel = device.channel().await?;

// Make Credentials ceremony
let make_credentials_request = MakeCredentialRequest {
origin: "example.org".to_owned(),
hash: Vec::from(challenge),
relying_party: Ctap2PublicKeyCredentialRpEntity::new("example.org", "example.org"),
user: Ctap2PublicKeyCredentialUserEntity::new(&user_id, "mario.rossi", "Mario Rossi"),
require_resident_key: true,
user_verification: UserVerificationRequirement::Preferred,
algorithms: vec![Ctap2CredentialType::default()],
exclude: None,
extensions: Some(extensions.clone()),
timeout: TIMEOUT,
};

let response = loop {
match channel
.webauthn_make_credential(&make_credentials_request, &pin_provider)
.await
{
Ok(response) => break Ok(response),
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));
}
Err(err) => break Err(err),
};
}
.unwrap();
// println!("WebAuthn MakeCredential response: {:?}", response);
println!(
"WebAuthn MakeCredential extensions: {:?}",
response.authenticator_data.extensions
);

let credential: Ctap2PublicKeyCredentialDescriptor =
(&response.authenticator_data).try_into().unwrap();
let get_assertion = GetAssertionRequest {
relying_party_id: "example.org".to_owned(),
hash: Vec::from(challenge),
allow: vec![credential],
user_verification: UserVerificationRequirement::Discouraged,
extensions: Some(GetAssertionRequestExtensions {
cred_blob: Some(true),
}),
timeout: TIMEOUT,
};

let response = loop {
match channel
.webauthn_get_assertion(&get_assertion, &pin_provider)
.await
{
Ok(response) => break Ok(response),
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));
}
Err(err) => break Err(err),
};
}
.unwrap();
// println!("WebAuthn GetAssertion response: {:?}", response);
println!(
"WebAuthn GetAssertion extensions: {:?}",
response.assertions[0].authenticator_data.extensions
);
let blob = if let Some(ext) = &response.assertions[0].authenticator_data.extensions {
ext.cred_blob
.clone()
.map(|x| String::from_utf8_lossy(&x).to_string())
} else {
None
};
println!("Credential blob: {blob:?}");
}

Ok(())
}
7 changes: 4 additions & 3 deletions libwebauthn/examples/webauthn_hid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
user_verification: UserVerificationRequirement::Preferred,
algorithms: vec![Ctap2CredentialType::default()],
exclude: None,
extensions_cbor: vec![],
extensions: None,
timeout: TIMEOUT,
};

Expand All @@ -77,13 +77,14 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
.unwrap();
println!("WebAuthn MakeCredential response: {:?}", response);

let credential: Ctap2PublicKeyCredentialDescriptor = (&response).try_into().unwrap();
let credential: Ctap2PublicKeyCredentialDescriptor =
(&response.authenticator_data).try_into().unwrap();
let get_assertion = GetAssertionRequest {
relying_party_id: "example.org".to_owned(),
hash: Vec::from(challenge),
allow: vec![credential],
user_verification: UserVerificationRequirement::Discouraged,
extensions_cbor: None,
extensions: None,
timeout: TIMEOUT,
};

Expand Down
Loading

0 comments on commit 864b1c8

Please sign in to comment.