Skip to content

Commit

Permalink
20231027 378 optional conditional UI (#379)
Browse files Browse the repository at this point in the history
  • Loading branch information
Firstyear authored Nov 11, 2023
1 parent 0ff6b52 commit bd3c90b
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 28 deletions.
3 changes: 0 additions & 3 deletions compat_tester/webauthn-rs-demo-wasm/src/condui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,6 @@ impl Component for ConduiTest {
ConduiTestState::Main(ChallengeState::Waiting),
AppMsg::BeginLoginChallenge(mut ccr),
) => {
// Setup conditional mediation.
ccr.mediation = Some(Mediation::Conditional);

// No state change, we are just triggering a callback.
ConduiTestState::Main(ChallengeState::Presented(ccr))
}
Expand Down
2 changes: 1 addition & 1 deletion compat_tester/webauthn-rs-demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ license = "MPL-2.0"
[dependencies]
webauthn-rs-demo-shared = { path = "../webauthn-rs-demo-shared", features = ["core"] }
webauthn-rs-core.workspace = true
webauthn-rs = { workspace = true, features = ["resident-key-support", "preview-features", "danger-allow-state-serialisation"] }
webauthn-rs = { workspace = true, features = ["conditional-ui", "attestation", "resident-key-support", "danger-allow-state-serialisation"] }
webauthn-rs-device-catalog.workspace = true

tide.workspace = true
Expand Down
6 changes: 3 additions & 3 deletions compat_tester/webauthn-rs-demo/pkg/webauthn_rs_demo_wasm.js
Original file line number Diff line number Diff line change
Expand Up @@ -833,15 +833,15 @@ function __wbg_get_imports() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper1202 = function(arg0, arg1, arg2) {
imports.wbg.__wbindgen_closure_wrapper1203 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 450, __wbg_adapter_34);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper1722 = function(arg0, arg1, arg2) {
imports.wbg.__wbindgen_closure_wrapper1723 = function(arg0, arg1, arg2) {
const ret = makeClosure(arg0, arg1, 639, __wbg_adapter_37);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper1865 = function(arg0, arg1, arg2) {
imports.wbg.__wbindgen_closure_wrapper1866 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 661, __wbg_adapter_40);
return addHeapObject(ret);
};
Expand Down
Binary file modified compat_tester/webauthn-rs-demo/pkg/webauthn_rs_demo_wasm_bg.wasm
Binary file not shown.
4 changes: 3 additions & 1 deletion webauthn-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ features = ["danger-allow-state-serialisation", "danger-user-presence-only-secur
rustdoc-args = ["--cfg", "docsrs"]

[features]
preview-features = ["conditional-ui", "attestation"]
resident-key-support = []
preview-features = []
conditional-ui = []
attestation = []
workaround-google-passkey-specific-issues = []
danger-allow-state-serialisation = []
danger-credential-internals = []
Expand Down
38 changes: 26 additions & 12 deletions webauthn-rs/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl PartialEq for Passkey {
feature = "danger-allow-state-serialisation",
derive(Serialize, Deserialize)
)]
#[cfg(feature = "preview-features")]
#[cfg(feature = "attestation")]
pub struct AttestedPasskeyRegistration {
pub(crate) rs: RegistrationState,
pub(crate) ca_list: AttestationCaList,
Expand All @@ -171,7 +171,7 @@ pub struct AttestedPasskeyRegistration {
feature = "danger-allow-state-serialisation",
derive(Serialize, Deserialize)
)]
#[cfg(feature = "preview-features")]
#[cfg(feature = "attestation")]
pub struct AttestedPasskeyAuthentication {
pub(crate) ast: AuthenticationState,
}
Expand All @@ -182,12 +182,12 @@ pub struct AttestedPasskeyAuthentication {
///
/// These can be safely serialised and deserialised from a database for use.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg(feature = "preview-features")]
#[cfg(feature = "attestation")]
pub struct AttestedPasskey {
pub(crate) cred: Credential,
}

#[cfg(feature = "preview-features")]
#[cfg(feature = "attestation")]
impl AttestedPasskey {
/// Retrieve a reference to this AttestedPasskey Key's credential ID.
pub fn cred_id(&self) -> &CredentialID {
Expand Down Expand Up @@ -236,23 +236,23 @@ impl AttestedPasskey {
}
}

#[cfg(feature = "preview-features")]
#[cfg(feature = "attestation")]
impl PartialEq for AttestedPasskey {
fn eq(&self, other: &Self) -> bool {
self.cred.cred_id == other.cred.cred_id
}
}

#[cfg(all(feature = "danger-credential-internals", feature = "preview-features"))]
#[cfg(all(feature = "danger-credential-internals", feature = "attestation"))]
impl From<AttestedPasskey> for Credential {
fn from(pk: AttestedPasskey) -> Self {
pk.cred
}
}

#[cfg(all(feature = "danger-credential-internals", feature = "preview-features"))]
#[cfg(all(feature = "danger-credential-internals", feature = "attestation"))]
impl From<Credential> for AttestedPasskey {
/// Convert a generic webauthn credential into a Passkey
/// Convert a generic webauthn credential into an [AttestedPasskey]
fn from(cred: Credential) -> Self {
AttestedPasskey { cred }
}
Expand Down Expand Up @@ -523,7 +523,7 @@ impl From<Credential> for AttestedResidentKey {
feature = "danger-allow-state-serialisation",
derive(Serialize, Deserialize)
)]
#[cfg(feature = "preview-features")]
#[cfg(feature = "conditional-ui")]
pub struct DiscoverableAuthentication {
pub(crate) ast: AuthenticationState,
}
Expand All @@ -534,12 +534,12 @@ pub struct DiscoverableAuthentication {
/// Generally this is used as part of conditional ui which allows autofill of discovered
/// credentials in username fields.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg(feature = "preview-features")]
#[cfg(feature = "conditional-ui")]
pub struct DiscoverableKey {
pub(crate) cred: Credential,
}

#[cfg(feature = "preview-features")]
#[cfg(all(feature = "conditional-ui", feature = "resident-key-support"))]
impl From<&AttestedResidentKey> for DiscoverableKey {
fn from(k: &AttestedResidentKey) -> Self {
DiscoverableKey {
Expand All @@ -548,11 +548,25 @@ impl From<&AttestedResidentKey> for DiscoverableKey {
}
}

#[cfg(feature = "preview-features")]
#[cfg(all(feature = "conditional-ui", feature = "resident-key-support"))]
impl From<AttestedResidentKey> for DiscoverableKey {
fn from(k: AttestedResidentKey) -> Self {
DiscoverableKey { cred: k.cred }
}
}

#[cfg(feature = "conditional-ui")]
impl From<&Passkey> for DiscoverableKey {
fn from(k: &Passkey) -> Self {
DiscoverableKey {
cred: k.cred.clone(),
}
}
}

#[cfg(feature = "conditional-ui")]
impl From<Passkey> for DiscoverableKey {
fn from(k: Passkey) -> Self {
DiscoverableKey { cred: k.cred }
}
}
69 changes: 61 additions & 8 deletions webauthn-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@
//! Manager' key flow, you can enable the feature `workaround-google-passkey-specific-issues`. This
//! flow can only be used on Android devices with GMS Core, and you must have a way to detect this
//! ahead of time.
//!
//! ## Conditional UI / Username Autocompletion
//!
//! Some passkey devices will create a resident key opportunistically during registration. These
//! keys in some cases allow the device to autocomplete the username *and* authenticate in a
//! single step.
//!
//! Not all devices support this, nor do all browsers. As a result you must always support the
//! full passkey flow, and conditional-ui is only opportunistically itself.
//!
//! User testing has shown that these conditional UI flows in most browsers are hard to activate
//! and may be confusing to users, as they attempt to force users to use caBLE/hybrid. We don't
//! recommend conditional UI as a result.
//!
//! If you still wish to experiment with conditional UI, then enabling the feature `conditional-ui`
//! will expose the needed methods to create conditional-ui mediated challenges and expose the
//! functions to extract the users uuid from the authentication request.

#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
Expand Down Expand Up @@ -361,7 +378,7 @@ impl<'a> WebauthnBuilder<'a> {
/// __I want to replace passwords with strong multi-factor cryptographic authentication, limited to
/// a known set of controlled and trusted authenticator types__
///
/// This type requires `preview-features` enabled as the current form of the Attestation CA List
/// This type requires `attestation` enabled as the current form of the Attestation CA List
/// may change in the future.
///
/// > You should use [`start_attested_passkey_registration`](Webauthn::start_attested_passkey_registration)
Expand Down Expand Up @@ -926,7 +943,7 @@ impl Webauthn {
}
}

#[cfg(feature = "preview-features")]
#[cfg(feature = "attestation")]
impl Webauthn {
/// Initiate the registration of a new attested_passkey key for a user. A attested_passkey key is a
/// cryptographic authenticator that is a self-contained multifactor authenticator. This means
Expand Down Expand Up @@ -1199,17 +1216,22 @@ impl Webauthn {
/// valid per the above check. If you wish
/// you *may* use the content of the [AuthenticationResult] for extended validations (such as the
/// user verification flag).
///
/// In *some* cases, you *may* be able to identify the user by examinin
pub fn finish_attested_passkey_authentication(
&self,
reg: &PublicKeyCredential,
state: &AttestedPasskeyAuthentication,
) -> WebauthnResult<AuthenticationResult> {
self.core.authenticate_credential(reg, &state.ast)
}
}

/// WIP DO NOT USE
#[cfg(feature = "conditional-ui")]
impl Webauthn {
/// This function will initiate a conditional ui authentication for discoverable
/// credentials.
///
/// Since this relies on the client to "discover" what credential and user id to
/// use, there are no options required to start this.
pub fn start_discoverable_authentication(
&self,
) -> WebauthnResult<(RequestChallengeResponse, DiscoverableAuthentication)> {
Expand All @@ -1222,10 +1244,20 @@ impl Webauthn {

self.core
.generate_challenge_authenticate_discoverable(policy, extensions)
.map(|(rcr, ast)| (rcr, DiscoverableAuthentication { ast }))
.map(|(mut rcr, ast)| {
// Force conditional ui - this is not a generic discoverable credential
// workflow!
rcr.mediation = Some(Mediation::Conditional);
(rcr, DiscoverableAuthentication { ast })
})
}

/// WIP DO NOT USE
/// Pre-process the clients response from a conditional ui authentication attempt. This
/// will extract the users Uuid and the Credential ID that was used by the user
/// in their authentication.
///
/// You must use this information to locate the relavent credential that was used
/// to allow you to finish the authentication.
pub fn identify_discoverable_authentication<'a>(
&'_ self,
reg: &'a PublicKeyCredential,
Expand All @@ -1237,7 +1269,28 @@ impl Webauthn {
.ok_or(WebauthnError::InvalidUserUniqueId)
}

/// WIP DO NOT USE
/// Given the `PublicKeyCredential` returned by the user agent (e.g. a browser), the
/// stored [DiscoverableAuthentication] and the users [DiscoverableKey],
/// complete the authentication of the user. This asserts that user verification must have been correctly
/// performed allowing you to trust this as a MFA interfaction.
///
/// # Errors
/// If any part of the registration is incorrect or invalid, an error will be returned. See [WebauthnError].
///
/// # Returns
/// On success, [AuthenticationResult] is returned which contains some details of the Authentication
/// process.
///
/// As per <https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion> 21:
///
/// If the Credential Counter is greater than 0 you MUST assert that the counter is greater than
/// the stored counter. If the counter is equal or less than this MAY indicate a cloned credential
/// and you SHOULD invalidate and reject that credential as a result.
///
/// From this [AuthenticationResult] you *should* update the Credential's Counter value if it is
/// valid per the above check. If you wish
/// you *may* use the content of the [AuthenticationResult] for extended validations (such as the
/// user verification flag).
pub fn finish_discoverable_authentication(
&self,
reg: &PublicKeyCredential,
Expand Down

0 comments on commit bd3c90b

Please sign in to comment.