-
Notifications
You must be signed in to change notification settings - Fork 49
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
[PM-7840] Implement the stubbed out Passkey uniffi API #779
Conversation
New Issues
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #779 +/- ##
==========================================
- Coverage 58.55% 56.67% -1.88%
==========================================
Files 178 181 +3
Lines 11405 11915 +510
==========================================
+ Hits 6678 6753 +75
- Misses 4727 5162 +435 ☔ View full report in Codecov by Sentry. |
bb0ea1d
to
0b8bd33
Compare
0b8bd33
to
d2041a3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given it a quick look and added some comments where I found issues
- Add Options to MakeCredentialRequest - Add `is_verification_enabled` to `Fido2UserInterface` - Prepend `Fido2` to interfaces
…and make_credential when running check_user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors looking good! I think we could even expand on this a little bit and allow the platform to return it's own codes, that way iOS and Android can create their own UserInterfaceRequired
errors that they can throw when user verification is requested inside of a flow without UI access
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a good start, plenty of TODOs left but some initial comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed up to authenticator.rs
, going to continue tomorrow!
let picked = this | ||
.authenticator | ||
.user_interface | ||
.pick_credential_for_authentication(creds) | ||
.await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue/question: This should only be called get_assertion
, is that the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah very true, I've updated this to only be called when doing authentication/assertion in 7a4b8b1
let mut picked = this | ||
.authenticator | ||
.user_interface | ||
.pick_credential_for_creation(creds, cred.clone().into()) | ||
.await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: this needs to be updated to use check_user_and_pick_credential_for_creation
and be called from the UserInterface
instead now that we've added support for UIHint
we should have all the information needed to make the call there instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yep! I've updated it to be called from check_user
in 7a4b8b1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the last of my review :)
// To keep compatibility with passkey-rs ClientData, we want extra_client_data to return a type | ||
// that serializes to Object | Unit. If we just used an Option it would serialize to Object | None | ||
// So we create our own wrapper types that serialize to the correct values and make the ClientData | ||
// implementation return that. | ||
#[derive(Clone)] | ||
pub(super) struct OptionalAndroidClientData { | ||
data: Option<AndroidClientData>, | ||
} | ||
|
||
#[derive(Serialize, Clone)] | ||
struct AndroidClientData { | ||
android_package_name: String, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Does the AndroidClientData
struct need to be optional though? The functions that need it will never be used by anything other than Android?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value is optional because we need a generic type that implements serialize and covers both the DefaultWithExtraData
and DefaultWithCustomHash
branches of the enum.
This way, extra_client_data
returns the following:
- For
DefaultWithExtraData
we returnSome(AndroidClientData)
, which gets serialized and then flattened, adding theandroidPackageName
field toClientDataJson
- For
DefaultWithCustomHash
we returnNone
, which gets serialized and then flattened, and leaves theClientDataJson
as-is
We could also change the type to be something like this, which might work as well:
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct SerializableClientData {
#[serde(skip_serializing_if = "Option::is_none")]
android_package_name: Option<String>,
}
# Conflicts: # crates/bitwarden/Cargo.toml # crates/bitwarden/src/platform/client_platform.rs # crates/bitwarden/src/platform/fido2/authenticator.rs # crates/bitwarden/src/platform/fido2/client.rs # crates/bitwarden/src/platform/fido2/traits.rs # crates/bitwarden/src/platform/mod.rs
…sert UV in client
Okay, I've updated the code to use the new API
I've gone though the call flow a few times to make sure nothing is missing or out of order, and wrote this summary of the call order as it should be now: passkey::Authenticator::make_credential()
self.required_uv.insert()
passkey::make_credential()
if exclude_list not empty:
passkey::CredentialStore::find_credentials()
trait bitwarden::Fido2CredentialStore::find_credentials()
if found excluded credential:
passkey::UserValidationMethod::check_user(InformExcludedCredentialFound)
self.required_uv.get()
trait bitwarden::Fido2UserInterface::check_user()
return
passkey::UserValidationMethod::check_user(RequestNewCredential)
self.required_uv.get()
trait bitwarden::Fido2UserInterface::check_user_and_pick_credential_for_creation()
self.selected_credential.insert()
passkey::CredentialStore::save_credential()
self.selected_credential.get()
self.selected_credential.insert()
trait bitwarden::Fido2CredentialStore::save_credential()
self.selected_credential.get()
bitwarden::Client::register()
self.required_uv.insert()
passkey::Client::register()
passkey::make_credential()
# From here, it's the same as inside passkey::make_credential() above
self.selected_credential.get()
passkey::Authenticator::get_assertion()
self.required_uv.insert()
passkey::get_assertion()
passkey::CredentialStore::find_credentials()
trait bitwarden::Fido2CredentialStore::find_credentials()
trait bitwarden::Fido2UserInterface::pick_credential_for_authentication()
self.selected_credential.insert()
if found credential:
passkey::UserValidationMethod::check_user(RequestExistingCredential)
self.required_uv.get()
trait bitwarden::Fido2UserInterface::check_user()
else:
passkey::UserValidationMethod::check_user(InformNoCredentialsFound)
self.required_uv.get()
trait bitwarden::Fido2UserInterface::check_user()
if counter:
passkey::CredentialStore::update_credential()
self.selected_credential.get()
self.selected_credential.insert()
trait bitwarden::Fido2CredentialStore::save_credential()
self.selected_credential.get()
bitwarden::Client::authenticate()
self.required_uv.insert()
passkey::Client::authenticate()
passkey::get_assertion()
# From here, it's the same as inside passkey::get_assertion() above
self.selected_credential.get() |
Would be nice if we could write some test cases, but looks good. |
# Conflicts: # crates/bitwarden-crypto/src/uniffi_support.rs # crates/bitwarden-uniffi/src/uniffi_support.rs # crates/bitwarden/src/platform/fido2/authenticator.rs # crates/bitwarden/src/platform/fido2/client.rs # crates/bitwarden/src/uniffi_support.rs # crates/bitwarden/src/vault/cipher/login.rs
Updated to remove the usages of Sensitive from the types |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Thanks for adding the b64.
decoding, will be nice to have it if we need it!
_ => return Err("Only one credential should match the id".into()), | ||
}; | ||
// Get the previously selected cipher and update the credential | ||
let selected = this.authenticator.get_selected_credential()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion(non-blocking): maybe just add a sanity check here to verify that the id matches?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nice! Added it in f852dbf
Type of change
Objective
This PR adds our fork of
passkey-rs
to bitwarden to implement the previously stubbed out API. With it come some changes:passkey-rs
insteadFido2CredentialNewView
, the only difference being that it doesn't contain the private key. This is used to send to the clients in the cipher select callback. Everywhere else we send back the encrypted field but at this point we don't have a key to encrypt the field yet. We could send a dummy value but that seems error prone.CheckUserOptions
to be a struct instead of an enum. This was a mistake from the previous PR.types.rs
file. Also implemented conversion between these types andpasskey
types usingFrom
/TryFrom
when possible.There are still some open questions:
passkey-rs
force us to returnStatusCode
orCtap2Error
, how do we want to handle these? At the moment I'm just logging the error and returing a constant value. Do we need specific error values for certain errors to be spec compliant?Passkey
toFido2CredentialView
has a few hardcoded values, is that expected?// TODO(Fido2):
comments around to mention some other small questions I have