-
Notifications
You must be signed in to change notification settings - Fork 14
[PM-24051] In identity and sync response & decryption options, add MasterPasswordUnlockDataResponse in response model #376
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
base: main
Are you sure you want to change the base?
Conversation
Great job! No new security vulnerabilities introduced in this pull request |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #376 +/- ##
==========================================
+ Coverage 73.95% 74.36% +0.41%
==========================================
Files 252 256 +4
Lines 21574 22126 +552
==========================================
+ Hits 15955 16454 +499
- Misses 5619 5672 +53 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Some initial comments, some of these are required, some are idiomatic (but probably required since the quality bar for SDK seems to be set much higher).
I'd like a review from someone with more rust experience such as @Hinton or @dani-garcia too. I believe specifically the request parsing is something that has not existed in the SDK so far, and there may be a better way to do this that we're unaware of.
pub struct MasterPasswordUnlockData { | ||
pub kdf: Kdf, | ||
pub master_key_wrapped_user_key: EncString, | ||
pub salt: 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.
We have an opaque string type MasterPasswordSalt
in clients
: https://github.com/bitwarden/clients/blob/61cd0c4f51d9fe390c93f61f2f55076de33b57cf/libs/common/src/key-management/master-password/types/master-password.types.ts#L15.
Ideally we would expose this from rust as we do for EncString
export type EncString = Tagged<string, "EncString">; |
This one is optional, if we don't do it here it is tech debt we should record.
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.
From what i understand, in cases EncString
, we have an equivalent type in Rust SDK, that is different during Serialization: EncString
becomed String
type, hence why we export it in TS as string.
This is different here, since we don't have equivalent in our Rust code.
If we want one, then i think either of the two will be ok:
#[cfg_attr(feature = "wasm", tsify::declare(tsify::Tsify))]
pub type MasterPasswordSalt = String;
Or change the salt
field to have different type in TS.
#[cfg_attr(
feature = "wasm",
tsify(type = "MasterPasswordSalt")
)]
pub salt: 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.
I think my main concern here is that we want the tagged type, so that the typesafety stretches to TS.
A bit more verbose than I'd like, but this does the job, and is the same approach I use for the new PinProtectedKeyEnvelope (PasswordProtectedkeyEnvelope):
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct MasterPasswordSalt(
#[cfg_attr(
feature = "wasm",
tsify(type = r#"Tagged<string, "MasterPasswordSalt">"#)
)]
String,
);
But again, we can do that later if we don't want to do in in this PR, I don't want to scope creep it.
@@ -0,0 +1,353 @@ | |||
#![allow(missing_docs)] |
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.
Please remove this and add comments. We require comments on everything that is publicly exposed.
crates/bitwarden-wasm-internal/src/key_management/master_password.rs
Outdated
Show resolved
Hide resolved
|
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 pretty nice now! Some more smaller items that improve usability by teams unfamiliar with the code (docs / error names)
#[allow(missing_docs)] | ||
#[bitwarden_error(flat)] | ||
#[derive(Debug, thiserror::Error)] | ||
pub enum MasterPasswordError { | ||
#[error(transparent)] | ||
Crypto(#[from] CryptoError), | ||
#[error(transparent)] | ||
MissingField(#[from] MissingFieldError), | ||
} |
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.
#[allow(missing_docs)] | |
#[bitwarden_error(flat)] | |
#[derive(Debug, thiserror::Error)] | |
pub enum MasterPasswordError { | |
#[error(transparent)] | |
Crypto(#[from] CryptoError), | |
#[error(transparent)] | |
MissingField(#[from] MissingFieldError), | |
} | |
#[bitwarden_error(flat)] | |
#[derive(Debug, thiserror::Error)] | |
pub enum MasterPasswordError { | |
/// The wrapped encryption key could not be parsed | |
#[error(transparent)] | |
Crypto(#[from] CryptoError), | |
/// The KDF data is malformed | |
#[error(transparent)] | |
MissingField(#[from] MissingFieldError), | |
} |
I would actually suggest making these errors more descriptive. If I understand the rest of the file correctly, missingfield happens exactly when the KDF is broken, and Crypto happens exactly when the encstring parsing fails, so you could just skip wrapping the errors and make it more clear to the caller what actually failed:
#[allow(missing_docs)] | |
#[bitwarden_error(flat)] | |
#[derive(Debug, thiserror::Error)] | |
pub enum MasterPasswordError { | |
#[error(transparent)] | |
Crypto(#[from] CryptoError), | |
#[error(transparent)] | |
MissingField(#[from] MissingFieldError), | |
} | |
#[bitwarden_error(flat)] | |
#[derive(Debug, thiserror::Error)] | |
pub enum MasterPasswordError { | |
/// The wrapped encryption key could not be parsed because the encstring is malformed | |
EncryptionKeyMalformed, | |
/// The KDF data could not be parsed, because it is missing values or has an invalid value | |
KdfMalformed | |
} |
type Error = MasterPasswordError; | ||
|
||
fn try_from(response: MasterPasswordUnlockResponseModel) -> Result<Self, Self::Error> { | ||
let kdf = match response.kdf.kdf_type { |
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.
If you do apply the suggestion from above, then this would require mapping to the different error types
use crate::key_management::master_password::{MasterPasswordError, MasterPasswordUnlockData}; | ||
|
||
/// Error for master user decryption related operations. | ||
#[allow(missing_docs)] |
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.
#[allow(missing_docs)] |
|
||
/// Represents data required to decrypt user's vault. | ||
/// Currently, this is only used for master password unlock. | ||
#[allow(missing_docs)] |
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.
#[allow(missing_docs)] |
}; | ||
use wasm_bindgen::prelude::wasm_bindgen; | ||
|
||
#[wasm_bindgen] |
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.
#[wasm_bindgen] | |
/// This is a stop-gap solution to allow parsing of response models via the SDK while the full decryption functionality is not yet moved over to the SDK | |
#[wasm_bindgen] |
This tightly couples consumers of the SDK to the server generated bindings which is somewhat dangerous. We've previously addressed this by wrapping requests/responses in a manner that more accurately represents the action from the SDK's perspective. There be dragons here and we should consider this a bit. Do we expect to do similar things to other request/responses? If not I think we should simply wrap the logic with our own struct, while it's a few lines of boilerplate it reduces the complexity.
There is no UniFFI support here. |
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: Why are we implementing logic in wasm-internal? This would generally belong to our feature crates.
@@ -90,3 +110,5 @@ mockall = ["dep:mockall"] | |||
bon = ["dep:bon"] | |||
{{/useBonBuilder}} | |||
{{/reqwestTrait}} | |||
tsify = { workspace = true, optional = true, features = ["js"], default-features = false } | |||
wasm-bindgen = { workspace = true, optional = true, features = ["serde-serialize"] } |
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.
We'd need some way to configure this in runtime, since this would now also apply to identity.
pub struct MasterPasswordUnlockResponseModel { | ||
#[serde(rename = "kdf")] | ||
pub kdf: Box<models::MasterPasswordUnlockKdfResponseModel>, | ||
#[serde(rename = "masterKeyEncryptedUserKey")] | ||
pub master_key_encrypted_user_key: Option<String>, | ||
pub master_key_encrypted_user_key: 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.
Platform hasn't made any strides into making fields required as it has compatibility implications. This will throw an error should these fields not be available in the past, present or future. I.e. can we guarantee forever that this endpoint in the past and future has and always will include master_key_encrypted_user_key
and salt
? And if these are missing are we fine with it throwing?
This is related to https://bitwarden.atlassian.net/wiki/spaces/DEV/pages/760971277/Improve+API+bindings.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-24051
📔 Objective
Adds MasterPasswordUnlock and UserDecryption support with response parsing into the identity token response.
Includes Uniffi and WASM support.
Tested response parsing in client's (via WASM).
⏰ Reminders before review
team
🦮 Reviewer guidelines
:+1:
) or similar for great changes:memo:
) or ℹ️ (:information_source:
) for notes or general info:question:
) for questions:thinking:
) or 💭 (:thought_balloon:
) for more open inquiry that's not quite a confirmedissue and could potentially benefit from discussion
:art:
) for suggestions / improvements:x:
) or:warning:
) for more significant problems or concerns needing attention:seedling:
) or ♻️ (:recycle:
) for future improvements or indications of technical debt:pick:
) for minor or nitpick changes