Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

20231027 378 optional conditional UI #379

Merged
Merged
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
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
Loading