|
| 1 | +//! HID make-credential with related origins enabled. |
| 2 | +//! |
| 3 | +//! The bundled reqwest-backed [`ReqwestRelatedOriginsSource`] fetches |
| 4 | +//! `https://<rp.id>/.well-known/webauthn` when the request's rp.id is not a |
| 5 | +//! registrable suffix of the caller origin. This demo is same-origin |
| 6 | +//! (`example.org`), so the source is wired but the fetch is not triggered. It |
| 7 | +//! fires when rp.id and the origin sit on different registrable domains, with |
| 8 | +//! the RP listing the caller origin in its well-known document. |
| 9 | +
|
| 10 | +use std::error::Error; |
| 11 | +use std::time::Duration; |
| 12 | + |
| 13 | +use libwebauthn::ops::webauthn::{ |
| 14 | + JsonFormat, MakeCredentialRequest, MaxRegistrableLabels, OriginValidation, RelatedOrigins, |
| 15 | + RequestOrigin, RequestSettings, ReqwestRelatedOriginsSource, SystemPublicSuffixList, |
| 16 | + WebAuthnIDLResponse as _, |
| 17 | +}; |
| 18 | +use libwebauthn::transport::hid::list_devices; |
| 19 | +use libwebauthn::transport::{Channel as _, Device}; |
| 20 | +use libwebauthn::webauthn::WebAuthn; |
| 21 | + |
| 22 | +#[path = "../common/mod.rs"] |
| 23 | +mod common; |
| 24 | + |
| 25 | +const TIMEOUT: Duration = Duration::from_secs(10); |
| 26 | + |
| 27 | +#[tokio::main] |
| 28 | +pub async fn main() -> Result<(), Box<dyn Error>> { |
| 29 | + common::setup_logging(); |
| 30 | + |
| 31 | + let devices = list_devices().await.unwrap(); |
| 32 | + println!("Devices found: {:?}", devices); |
| 33 | + |
| 34 | + for mut device in devices { |
| 35 | + println!("Selected HID authenticator: {}", &device); |
| 36 | + let mut channel = device.channel().await?; |
| 37 | + channel.wink(TIMEOUT).await?; |
| 38 | + |
| 39 | + let request_origin: RequestOrigin = |
| 40 | + "https://example.org".try_into().expect("Invalid origin"); |
| 41 | + let psl = SystemPublicSuffixList::auto().expect( |
| 42 | + "PSL not available; install the publicsuffix-list (or publicsuffix-list-dafsa) package, or pass an explicit path", |
| 43 | + ); |
| 44 | + let related_origins = ReqwestRelatedOriginsSource::new()?; |
| 45 | + let settings = RequestSettings { |
| 46 | + origin: OriginValidation::Validate { |
| 47 | + public_suffix_list: &psl, |
| 48 | + related_origins: RelatedOrigins::Enabled { |
| 49 | + source: &related_origins, |
| 50 | + max_labels: MaxRegistrableLabels::default(), |
| 51 | + }, |
| 52 | + }, |
| 53 | + }; |
| 54 | + let request_json = r#" |
| 55 | + { |
| 56 | + "rp": { "id": "example.org", "name": "Example Relying Party" }, |
| 57 | + "user": { |
| 58 | + "id": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2Nzg", |
| 59 | + "name": "alice", |
| 60 | + "displayName": "Alice" |
| 61 | + }, |
| 62 | + "challenge": "MTIzNDU2NzgxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2Nzg", |
| 63 | + "pubKeyCredParams": [{"type": "public-key", "alg": -7}], |
| 64 | + "timeout": 60000, |
| 65 | + "excludeCredentials": [], |
| 66 | + "authenticatorSelection": { "residentKey": "discouraged", "userVerification": "preferred" }, |
| 67 | + "attestation": "none" |
| 68 | + } |
| 69 | + "#; |
| 70 | + let request = MakeCredentialRequest::prepare(&request_origin, request_json, &settings) |
| 71 | + .await |
| 72 | + .expect("Failed to parse request JSON"); |
| 73 | + |
| 74 | + let state_recv = channel.get_ux_update_receiver(); |
| 75 | + tokio::spawn(common::handle_uv_updates(state_recv)); |
| 76 | + |
| 77 | + let response = retry_user_errors!(channel.webauthn_make_credential(&request)).unwrap(); |
| 78 | + let response_json = response |
| 79 | + .to_json_string(&request, JsonFormat::Prettified) |
| 80 | + .expect("Failed to serialize MakeCredential response"); |
| 81 | + println!("WebAuthn MakeCredential response (JSON):\n{response_json}"); |
| 82 | + } |
| 83 | + |
| 84 | + Ok(()) |
| 85 | +} |
0 commit comments