Skip to content

Commit

Permalink
Authenticate is a builder
Browse files Browse the repository at this point in the history
  • Loading branch information
Firstyear committed Apr 27, 2024
1 parent e4c43b9 commit 148db40
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 73 deletions.
8 changes: 6 additions & 2 deletions compat_tester/webauthn-rs-demo/src/actors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,15 @@ impl WebauthnActor {
.ok_or(WebauthnError::CredentialNotFound)?;

self.wan
.generate_challenge_authenticate(vec![cred], uv, extensions, None, None)
.new_challenge_authenticate_builder(vec![cred], uv)
.map(|builder| builder.extensions(extensions))
.and_then(|b| self.wan.generate_challenge_authenticate(b))
}
None => self
.wan
.generate_challenge_authenticate(creds, None, extensions, None, None),
.new_challenge_authenticate_builder(creds, None)
.map(|builder| builder.extensions(extensions))
.and_then(|b| self.wan.generate_challenge_authenticate(b)),
}?;

debug!("complete ChallengeAuthenticate -> {:?}", acr);
Expand Down
14 changes: 6 additions & 8 deletions webauthn-authenticator-rs/examples/authenticate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,17 +280,15 @@ async fn main() {
.connect_provider(CableRequestType::GetAssertion, &ui)
.await;
let (chal, auth_state) = wan
.generate_challenge_authenticate(
vec![cred.clone()],
None,
Some(RequestAuthenticationExtensions {
.new_challenge_authenticate_builder(vec![cred.clone()], None)
.map(|builder| {
builder.extensions(Some(RequestAuthenticationExtensions {
appid: Some("example.app.id".to_string()),
uvm: None,
hmac_get_secret: None,
}),
None,
None,
)
}))
})
.and_then(|b| wan.generate_challenge_authenticate(b))
.unwrap();

let r = u
Expand Down
3 changes: 2 additions & 1 deletion webauthn-authenticator-rs/src/softpasskey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,8 @@ mod tests {
let cred = wan.register_credential(&r, &reg_state, None).unwrap();

let (chal, auth_state) = wan
.generate_challenge_authenticate(vec![cred], None, None, None, None)
.new_challenge_authenticate_builder(vec![cred], None)
.and_then(|b| wan.generate_challenge_authenticate(b))
.unwrap();

let r = wa
Expand Down
6 changes: 4 additions & 2 deletions webauthn-authenticator-rs/src/softtoken.rs
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,8 @@ mod tests {
info!("Credential -> {:?}", cred);

let (chal, auth_state) = wan
.generate_challenge_authenticate(vec![cred], None, None, None, None)
.new_challenge_authenticate_builder(vec![cred], None)
.and_then(|b| wan.generate_challenge_authenticate(b))
.unwrap();

let r = wa
Expand Down Expand Up @@ -1014,7 +1015,8 @@ mod tests {
let mut wa = WebauthnAuthenticator::new(soft_token);

let (chal, auth_state) = wan
.generate_challenge_authenticate(vec![cred], None, None, None, None)
.new_challenge_authenticate_builder(vec![cred], None)
.and_then(|b| wan.generate_challenge_authenticate(b))
.unwrap();

let r = wa
Expand Down
100 changes: 82 additions & 18 deletions webauthn-rs-core/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ pub struct ChallengeRegisterBuilder {
hints: Option<Vec<PublicKeyCredentialHints>>,
}

/// A builder allowing customisation of a client authentication challenge.
#[derive(Debug)]
pub struct ChallengeAuthenticateBuilder {
creds: Vec<Credential>,
policy: UserVerificationPolicy,
extensions: Option<RequestAuthenticationExtensions>,
allow_backup_eligible_upgrade: bool,
hints: Option<Vec<PublicKeyCredentialHints>>,
}

impl ChallengeRegisterBuilder {
/// Set the attestation conveyance preference. Defaults to None.
pub fn attestation(mut self, value: AttestationConveyancePreference) -> Self {
Expand Down Expand Up @@ -147,6 +157,29 @@ impl ChallengeRegisterBuilder {
}
}

impl ChallengeAuthenticateBuilder {
/// Define extensions to be requested in the request. Defaults to None.
pub fn extensions(mut self, value: Option<RequestAuthenticationExtensions>) -> Self {
self.extensions = value;
self
}

/// Allow authenticators to modify their backup eligibility. This can occur
/// where some formerly hardware bound devices become roaming ones. If in
/// double leave this value as default (false, rejects credentials that
/// post-create move from single device to roaming).
pub fn allow_backup_eligible_upgrade(mut self, value: bool) -> Self {
self.allow_backup_eligible_upgrade = value;
self
}

/// Add the set of hints for which public keys may satisfy this request.
pub fn hints(mut self, hints: Option<Vec<PublicKeyCredentialHints>>) -> Self {
self.hints = hints;
self
}
}

impl WebauthnCore {
/// ⚠️ ⚠️ ⚠️ THIS IS UNSAFE. AVOID USING THIS DIRECTLY ⚠️ ⚠️ ⚠️
///
Expand Down Expand Up @@ -905,22 +938,15 @@ impl WebauthnCore {
Ok(data.authenticator_data)
}

/// Authenticate a set of credentials allowing the user verification policy to be set.
/// If no credentials are provided, this will start a discoverable credential authentication.
///
/// Policy defines the require UserVerification policy. This MUST be consistent with the
/// credentials being used in the authentication.
///
/// `allow_backup_eligible_upgrade` allows rejecting credentials whos backup eligibility
/// has changed between registration and authentication.
pub fn generate_challenge_authenticate(
/// Generate a new challenge builder for client authentication. This is the first
/// step in authentication of a credential. This function will return an
/// authentication builder allowing you to customise the parameters that will be
/// sent to the client.
pub fn new_challenge_authenticate_builder(
&self,
creds: Vec<Credential>,
policy: Option<UserVerificationPolicy>,
extensions: Option<RequestAuthenticationExtensions>,
allow_backup_eligible_upgrade: Option<bool>,
hints: Option<Vec<PublicKeyCredentialHints>>,
) -> Result<(RequestChallengeResponse, AuthenticationState), WebauthnError> {
) -> Result<ChallengeAuthenticateBuilder, WebauthnError> {
let policy = if let Some(policy) = policy {
policy
} else {
Expand All @@ -938,8 +964,37 @@ impl WebauthnCore {
policy
};

// Defaults to false.
let allow_backup_eligible_upgrade = allow_backup_eligible_upgrade.unwrap_or_default();
Ok(ChallengeAuthenticateBuilder {
creds,
policy,
extensions: Default::default(),
allow_backup_eligible_upgrade: Default::default(),
hints: Default::default(),
})
}

/// Generate a new challenge for client authentication from the parameters defined by the
/// [ChallengeAuthenticateBuilder].
///
/// This function will return the
/// [RequestChallengeResponse] which is suitable for serde json serialisation
/// to be sent to the client. The client (generally a web browser) will pass this JSON
/// structure to the `navigator.credentials.create()` javascript function for registration.
///
/// It also returns an [AuthenticationState], that you *must*
/// persist. It is strongly advised you associate this AuthenticationState with a
/// private session ID that is unique to this request.
pub fn generate_challenge_authenticate(
&self,
challenge_builder: ChallengeAuthenticateBuilder,
) -> Result<(RequestChallengeResponse, AuthenticationState), WebauthnError> {
let ChallengeAuthenticateBuilder {
creds,
policy,
extensions,
allow_backup_eligible_upgrade,
hints,
} = challenge_builder;

let chal = self.generate_challenge();

Expand Down Expand Up @@ -2841,7 +2896,7 @@ mod tests {
// Ensure we get a bad result.

assert!(
wan.generate_challenge_authenticate(creds.clone(), None, None, None, None)
wan.new_challenge_authenticate_builder(creds.clone(), None)
.unwrap_err()
== WebauthnError::InconsistentUserVerificationPolicy
);
Expand All @@ -2866,7 +2921,11 @@ mod tests {
.unwrap();
}

let r = wan.generate_challenge_authenticate(creds.clone(), None, None, None, None);
let builder = wan
.new_challenge_authenticate_builder(creds.clone(), None)
.expect("Unable to create authenticate builder");

let r = wan.generate_challenge_authenticate(builder);
debug!("{:?}", r);
assert!(r.is_ok());

Expand All @@ -2890,7 +2949,12 @@ mod tests {
.unwrap();
}

let r = wan.generate_challenge_authenticate(creds.clone(), None, None, None, None);
let builder = wan
.new_challenge_authenticate_builder(creds.clone(), None)
.expect("Unable to create authenticate builder");

let r = wan.generate_challenge_authenticate(builder);

debug!("{:?}", r);
assert!(r.is_ok());
}
Expand Down
89 changes: 47 additions & 42 deletions webauthn-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,17 +679,18 @@ impl Webauthn {
let extensions = None;
let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
let policy = Some(UserVerificationPolicy::Required);
let allow_backup_eligible_upgrade = Some(true);
let allow_backup_eligible_upgrade = true;
let hints = None;

self.core
.generate_challenge_authenticate(
creds,
policy,
extensions,
allow_backup_eligible_upgrade,
hints,
)
.new_challenge_authenticate_builder(creds, policy)
.map(|builder| {
builder
.extensions(extensions)
.allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
.hints(hints)
})
.and_then(|b| self.core.generate_challenge_authenticate(b))
.map(|(rcr, ast)| (rcr, PasskeyAuthentication { ast }))
}

Expand Down Expand Up @@ -957,24 +958,25 @@ impl Webauthn {
) -> WebauthnResult<(RequestChallengeResponse, SecurityKeyAuthentication)> {
let extensions = None;
let creds = creds.iter().map(|sk| sk.cred.clone()).collect();
let allow_backup_eligible_upgrade = Some(false);
let allow_backup_eligible_upgrade = false;

let policy = if self.user_presence_only_security_keys {
UserVerificationPolicy::Discouraged_DO_NOT_USE
Some(UserVerificationPolicy::Discouraged_DO_NOT_USE)
} else {
UserVerificationPolicy::Preferred
Some(UserVerificationPolicy::Preferred)
};

let hints = Some(vec![PublicKeyCredentialHints::SecurityKey]);

self.core
.generate_challenge_authenticate(
creds,
Some(policy),
extensions,
allow_backup_eligible_upgrade,
hints,
)
.new_challenge_authenticate_builder(creds, policy)
.map(|builder| {
builder
.extensions(extensions)
.allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
.hints(hints)
})
.and_then(|b| self.core.generate_challenge_authenticate(b))
.map(|(rcr, ast)| (rcr, SecurityKeyAuthentication { ast }))
}

Expand Down Expand Up @@ -1257,21 +1259,22 @@ impl Webauthn {
});

let policy = Some(UserVerificationPolicy::Required);
let allow_backup_eligible_upgrade = Some(false);
let allow_backup_eligible_upgrade = false;

let hints = Some(vec![
PublicKeyCredentialHints::SecurityKey,
PublicKeyCredentialHints::ClientDevice,
]);

self.core
.generate_challenge_authenticate(
creds,
policy,
extensions,
allow_backup_eligible_upgrade,
hints,
)
.new_challenge_authenticate_builder(creds, policy)
.map(|builder| {
builder
.extensions(extensions)
.allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
.hints(hints)
})
.and_then(|b| self.core.generate_challenge_authenticate(b))
.map(|(rcr, ast)| (rcr, AttestedPasskeyAuthentication { ast }))
}

Expand Down Expand Up @@ -1321,17 +1324,18 @@ impl Webauthn {
uvm: Some(true),
hmac_get_secret: None,
});
let allow_backup_eligible_upgrade = Some(false);
let allow_backup_eligible_upgrade = false;
let hints = None;

self.core
.generate_challenge_authenticate(
vec![],
policy,
extensions,
allow_backup_eligible_upgrade,
hints,
)
.new_challenge_authenticate_builder(Vec::with_capacity(0), policy)
.map(|builder| {
builder
.extensions(extensions)
.allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
.hints(hints)
})
.and_then(|b| self.core.generate_challenge_authenticate(b))
.map(|(mut rcr, ast)| {
// Force conditional ui - this is not a generic discoverable credential
// workflow!
Expand Down Expand Up @@ -1499,21 +1503,22 @@ impl Webauthn {
});

let policy = Some(UserVerificationPolicy::Required);
let allow_backup_eligible_upgrade = Some(false);
let allow_backup_eligible_upgrade = false;

let hints = Some(vec![
PublicKeyCredentialHints::SecurityKey,
PublicKeyCredentialHints::ClientDevice,
]);

self.core
.generate_challenge_authenticate(
creds,
policy,
extensions,
allow_backup_eligible_upgrade,
hints,
)
.new_challenge_authenticate_builder(creds, policy)
.map(|builder| {
builder
.extensions(extensions)
.allow_backup_eligible_upgrade(allow_backup_eligible_upgrade)
.hints(hints)
})
.and_then(|b| self.core.generate_challenge_authenticate(b))
.map(|(rcr, ast)| (rcr, AttestedResidentKeyAuthentication { ast }))
}

Expand Down

0 comments on commit 148db40

Please sign in to comment.