There are numerous issues with this design. It does not express clearly the combination
of credentials that are valid in the credential, nor their capabilities and usage. A situation
such as `Password && (TOTP || Webauthn no verification)` OR `Password && Webauthn no verification`
are now ambiguous with this representation. It also does not clearly allow us to see where is the
correct entry/starting point for the authentication session.
An improved design will have Credential become an enum representing the valid authentication methods
pub enum Credential {
Anonymous,
Password(),
GeneratedPassword(),
PasswordMFA(),
PasswordWebauthn(),
Webauthn(),
WebauthnVerified(),
PasswordWebauthnVerified(),
}
This allows a clearer set of logic flows in the credential and it's handling, as well as defined
transforms between credential states and type level guarantees of the consistency of the credential.
Database Credentials
--------------------
Database Credentials are currently stored as:
#[derive(Serialize, Deserialize, Debug)]
pub struct DbCredV1 {
pub password: Option<DbPasswordV1>,
pub webauthn: Option<Vec<DbWebauthnV1>>,
pub totp: Option<DbTotpV1>,
pub claims: Vec<String>,
pub uuid: Uuid,
}
This will be extended with an enum to represent the correct type/policy to deserialise into:
pub struct DbCredV1 {
pub type_: DbCredTypeV1,
}
An in place upgrade will be required to add this type to all existing credentials in the
database.
Protocol
--------
The current design of the step/response is as follows.
pub enum AuthStep {
Init(String),
Creds(Vec<AuthCredential>),
}
pub enum AuthState {
Success(String),
Denied(String),
Continue(Vec<AuthAllowed>),
}
This will be extended to include the selection criteria to choose which method to use and be presented
with. Additionally, only one AuthCredential can be presented by the server at a time, and the server
may respond with many choices for the next step. This allows the server to propose TOTP *OR* Webauthn
but the client must choose which (It can not supply both, creating an AND situation or ambiguity around
the correct way to handle these).
pub enum AuthMech {
Anonymous,
Password,
// This covers PasswordWebauthn as well.
PasswordMFA,
Webauthn,
WebauthnVerified,
PasswordWebauthnVerified,
}
pub enum AuthStep {
Init(String), // server responds with AuthState::Choose|Denied
Begin(AuthMech), // server responds with AuthState::Continue|Success|Denied
Cred(AuthCredential), // server response with AuthState::Continue|Success|Denied
}
pub enum AuthState {
Choose(Vec<AuthMech>),
Continue(Vec<AuthAllowed>),
Success(String),
Denied(String),
}
A key reason to have this "Choose" step is related to an issue in the design and construction of
Webauthn Challenges. For more details see: https://fy.blackhats.net.au/blog/html/2020/11/21/webauthn_userverificationpolicy_curiosities.html
AuthSession
-----------
Once these other changes are made, AuthSession will need to be simplified but it's core state machines
will remain mostly unchanged. This set of changes will likely result in the AuthSession being much
clearer due to the enforcement of credential presentation order instead of the current design that
may allow "all in one" submissions.
Other Benefits
==============
* During an MFA authentication, the Password if incorrect can be re-prompted for a number of times subsequent to the TOTP/Webauthn having been found valid, improving user experience.
* Allows Webauthn Verified credentials to be used with clearer expression of the claims of the device associated to the credential.
* Policy will be simpler to enforce on credentials due to them more clearly stating their design and layouts.