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

[PM-7840] Implement the stubbed out Passkey uniffi API #779

Merged
merged 41 commits into from
Jun 4, 2024

Conversation

dani-garcia
Copy link
Member

@dani-garcia dani-garcia commented May 13, 2024

Type of change

- [ ] Bug fix
- [x] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other

Objective

This PR adds our fork of passkey-rs to bitwarden to implement the previously stubbed out API. With it come some changes:

  • Swapped most parts of the stubbed implementations to use passkey-rs instead
  • Some API callbacks were changed to take decrypted items, the clients would need these decrypted to show the user anyway, and we can skip a few rounds of decrypting/encrypting this way. Note that the FIDO2 credentials private keys are never exposed decrypted to the clients.
  • Added a separate Fido2CredentialNewView, 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.
  • Changed CheckUserOptions to be a struct instead of an enum. This was a mistake from the previous PR.
  • Moved a lot of types that were previously distributed among a few files into a specific types.rs file. Also implemented conversion between these types and passkey types using From/TryFrom when possible.

There are still some open questions:

  • Some of the callbacks from passkey-rs force us to return StatusCode or Ctap2Error, 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?
  • The conversion from Passkey to Fido2CredentialView has a few hardcoded values, is that expected?
  • I left a few // TODO(Fido2): comments around to mention some other small questions I have

Copy link
Contributor

github-actions bot commented May 13, 2024

Logo
Checkmarx One – Scan Summary & Detailse962bca3-4295-475c-8833-97ce82eb9a02

New Issues

Severity Issue Source File / Package Checkmarx Insight
LOW Use_of_Hardcoded_Password /languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt: [73](https://github.com/bitwarden/sdk/blob/ps/uniffi-passkey-improvements//languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt# L73) Attack Vector

Copy link

codecov bot commented May 13, 2024

Codecov Report

Attention: Patch coverage is 9.39431% with 733 lines in your changes missing coverage. Please review.

Project coverage is 56.67%. Comparing base (db2ca31) to head (f852dbf).

Files Patch % Lines
...ates/bitwarden/src/platform/fido2/authenticator.rs 0.00% 328 Missing ⚠️
crates/bitwarden/src/platform/fido2/mod.rs 29.57% 100 Missing ⚠️
crates/bitwarden/src/vault/cipher/login.rs 2.29% 85 Missing ⚠️
crates/bitwarden/src/platform/fido2/types.rs 31.68% 69 Missing ⚠️
crates/bitwarden/src/platform/fido2/client.rs 0.00% 54 Missing ⚠️
crates/bitwarden-uniffi/src/platform/fido2.rs 0.00% 44 Missing ⚠️
crates/bitwarden/src/vault/cipher/cipher.rs 0.00% 28 Missing ⚠️
crates/bitwarden/src/platform/fido2/crypto.rs 0.00% 24 Missing ⚠️
crates/bitwarden/src/platform/fido2/traits.rs 0.00% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

@dani-garcia dani-garcia force-pushed the ps/uniffi-passkey-improvements branch from bb0ea1d to 0b8bd33 Compare May 13, 2024 13:50
@dani-garcia dani-garcia force-pushed the ps/uniffi-passkey-improvements branch from 0b8bd33 to d2041a3 Compare May 13, 2024 14:25
@dani-garcia dani-garcia requested a review from coroiu May 13, 2024 16:03
Copy link
Contributor

@coroiu coroiu left a 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

crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
@dani-garcia dani-garcia changed the title Implement the stubbed out Passkey uniffi API [PM-7840] Implement the stubbed out Passkey uniffi API May 14, 2024
@dani-garcia dani-garcia marked this pull request as ready for review May 14, 2024 15:11
@dani-garcia dani-garcia requested a review from coroiu May 14, 2024 15:11
Copy link
Contributor

@coroiu coroiu left a 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

crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
Copy link
Member

@Hinton Hinton left a 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.

languages/swift/iOS/App/ContentView.swift Outdated Show resolved Hide resolved
crates/bitwarden/src/vault/cipher/login.rs Show resolved Hide resolved
crates/bitwarden/src/platform/client_platform.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/crypto.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/crypto.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/mod.rs Outdated Show resolved Hide resolved
@dani-garcia dani-garcia requested a review from Hinton May 24, 2024 08:56
Copy link
Contributor

@coroiu coroiu left a 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!

crates/bitwarden-uniffi/src/platform/fido2.rs Show resolved Hide resolved
crates/bitwarden-uniffi/src/platform/fido2.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
Comment on lines 254 to 258
let picked = this
.authenticator
.user_interface
.pick_credential_for_authentication(creds)
.await?;
Copy link
Contributor

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?

Copy link
Member Author

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

Comment on lines 301 to 305
let mut picked = this
.authenticator
.user_interface
.pick_credential_for_creation(creds, cred.clone().into())
.await?;
Copy link
Contributor

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

Copy link
Member Author

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

crates/bitwarden/src/platform/fido2/authenticator.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@coroiu coroiu left a 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 :)

crates/bitwarden/src/platform/fido2/types.rs Outdated Show resolved Hide resolved
crates/bitwarden/src/platform/fido2/types.rs Outdated Show resolved Hide resolved
Comment on lines 150 to 162
// 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,
}
Copy link
Contributor

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?

Copy link
Member Author

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 return Some(AndroidClientData), which gets serialized and then flattened, adding the androidPackageName field to ClientDataJson
  • For DefaultWithCustomHash we return None, which gets serialized and then flattened, and leaves the ClientDataJson 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>,
}

crates/bitwarden/src/platform/fido2/types.rs Outdated Show resolved Hide resolved
# 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
@dani-garcia
Copy link
Member Author

dani-garcia commented May 30, 2024

Okay, I've updated the code to use the new API check_user_and_pick_credential_for_creation and made sure to remove most UI logic from the Storage implementation:

  • Now save_credential and update_credential only get the selected_credential from the mutex, modify it as needed and call the clients save_credential, which simplifies them quite a bit.

  • On check_user we either call the client's check_user or check_user_and_pick_credential_for_creation, depending on the provided hint.

  • On find_credentials, we only call pick_credential_for_authentication when doing authentication.

  • On Client we now store the request UV values, which I forgot to do.

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()

@Hinton Hinton requested a review from coroiu May 31, 2024 12:38
Hinton
Hinton previously approved these changes May 31, 2024
@Hinton
Copy link
Member

Hinton commented May 31, 2024

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
@dani-garcia
Copy link
Member Author

Updated to remove the usages of Sensitive from the types

coroiu
coroiu previously approved these changes Jun 4, 2024
Copy link
Contributor

@coroiu coroiu left a 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()?;
Copy link
Contributor

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?

Copy link
Member Author

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

@dani-garcia dani-garcia merged commit ea93ac5 into main Jun 4, 2024
104 checks passed
@dani-garcia dani-garcia deleted the ps/uniffi-passkey-improvements branch June 4, 2024 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants