-
Notifications
You must be signed in to change notification settings - Fork 9
[PM-20493] Add key wrapping / wrapped key facade for encstring & expose keywrap to purecrypto #221
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
…ic keyencryptable trait impl
@@ -43,3 +59,11 @@ impl UniffiCustomTypeConverter for UnsignedSharedKey { | |||
obj.to_string() | |||
} | |||
} | |||
|
|||
// Uniffi doesn't emit unused types, this is a dummy record to ensure that the custom type | |||
// converters are emitted |
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.
Can be removed when #221 is merged.
|
## 🎟️ Tracking https://bitwarden.atlassian.net/browse/PM-21377 ## 📔 Objective Add remaining missing functions needed to port encrypt service to sdk. Ref: https://github.com/bitwarden/clients/blob/87a1f4e8ac928edfb05a283372550d0792c71827/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts#L105-L188 Note: The way that this uses the key context is not ideal, but this should not be handled in this PR. This PR is to make the public interface available to unblock clients changes. Follow-up work (such as #221) can expose better apis from bitwarden-crypto and move PureCrypto to it. ## ⏰ Reminders before review - Contributor guidelines followed - All formatters and local linters executed and passed - Written new unit and / or integration tests where applicable - Protected functional changes with optionality (feature flags) - Used internationalization (i18n) for all UI strings - CI builds passed - Communicated to DevOps any deployment requirements - Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team ## 🦮 Reviewer guidelines <!-- Suggested interactions but feel free to use (or not) as you desire! --> - 👍 (`:+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 confirmed issue 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
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 pretty good to me, just some minor details
/// key_to_wrap. | ||
/// | ||
/// Wrapped keys such as cipher keys, or attachment keys, are used to create a layer of indirection, | ||
/// so that keys can be shared mor granularly, and so that data can be rotated more easily. |
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.
/// so that keys can be shared mor granularly, and so that data can be rotated more easily. | |
/// so that keys can be shared more granularly, and so that data can be rotated more easily. |
impl std::fmt::Debug for WrappedSymmetricKey { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
f.debug_struct("WrappedSymmetricKey") | ||
.field("inner_enc_string", &self.as_inner()) | ||
.finish() | ||
} | ||
} |
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.
In this case we can probably just tag WrappedSymmetricKey
with #[derive(Debug)]
directly instead of implementing it by hand.
/// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it | ||
/// will be overwritten | ||
/// * `encrypted_key` - The key to decrypt | ||
/// * `key_to_unwrap` - The key to decrypt |
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.
There's still a reference to decryption instead of unwrapping (The key to decrypt
-> The key to unwrap
). To avoid the repetition with the comment, we could also rename the parameter to more closely resemble it's type too:
/// * `key_to_unwrap` - The key to decrypt | |
/// * `wrapped_key` - The key to unwrap |
make_user_key(rand::thread_rng(), self) | ||
} | ||
|
||
/// Encrypt the users user key | ||
pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result<EncString> { | ||
pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result<WrappedSymmetricKey> { |
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: Will we want to rename these to also use the wrap/unwrap naming?
@@ -70,12 +70,12 @@ mod tests { | |||
|
|||
let master_key = MasterKey::derive(password, email, &kdf).unwrap(); | |||
|
|||
let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE="; | |||
let user_key: EncString = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); |
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 implement FromStr
for WrappedSymmetricKey
as well, so if you remove the .into()
below you can skip the type here and rust will infer it correctly as a WrappedSymmetricKey
:
let user_key: EncString = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); | |
let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); |
This also probably applies to the other test cases that were updated in this PR
@@ -110,10 +110,13 @@ impl AuthClient { | |||
password: String, | |||
encrypted_user_key: String, | |||
) -> Result<String> { | |||
let encrypted_user_key = encrypted_user_key | |||
.parse::<EncString>() |
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 can also parse into WrappedSymmetricKey
here and remove the .into()
.parse::<EncString>() | |
.parse::<WrappedSymmetricKey>() |
wrapped_key: String, | ||
wrapping_key: Vec<u8>, | ||
) -> Result<Vec<u8>, CryptoError> { | ||
let wrapped_key: WrappedSymmetricKey = EncString::from_str(&wrapped_key)?.into(); |
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.
let wrapped_key: WrappedSymmetricKey = EncString::from_str(&wrapped_key)?.into(); | |
let wrapped_key = WrappedSymmetricKey::from_str(&wrapped_key)?; |
@@ -453,7 +454,7 @@ impl CipherView { | |||
ctx: &mut KeyStoreContext<KeyIds>, | |||
key: SymmetricKeyId, | |||
) -> Result<(), CryptoError> { | |||
let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key)?; | |||
let old_ciphers_key = Cipher::decrypt_cipher_key(ctx, key, &self.key.take())?; |
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.
Now that the keys are wrapped, the take here can be removed.
*attachment_key = dec_attachment_key.encrypt(ctx, new_key)?; | ||
let tmp_attachment_key_id = SymmetricKeyId::Local("attachment_key"); | ||
ctx.unwrap_symmetric_key(old_key, tmp_attachment_key_id, attachment_key)?; | ||
*attachment_key = ctx.wrap_symmetric_key(new_key, tmp_attachment_key_id)?; |
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'm wondering if we should offer a function in the context to do the unwrap/wrap for you, as we're already using it a couple of times here.
@@ -489,8 +490,9 @@ impl CipherView { | |||
if let Some(attachments) = &mut self.attachments { | |||
for attachment in attachments { | |||
if let Some(attachment_key) = &mut attachment.key { | |||
let dec_attachment_key: Vec<u8> = attachment_key.decrypt(ctx, old_key)?; | |||
*attachment_key = dec_attachment_key.encrypt(ctx, new_key)?; | |||
let tmp_attachment_key_id = SymmetricKeyId::Local("attachment_key"); |
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.
Usually we declare these ids as const
, we should probably do the same for consistency.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-20493
📔 Objective
Refactors how wrapped keys are represented in the SDK. We now add a facade around them, that only exposes the unwrap method. Additionally, this exposes key-wrapping to wasm via PureCrypto, in order to act as a backwards compatibility layer for all clients code that has not been migrated to use the SDK entirely yet.
Note: This adds a new module - "safe" to the bitwarden-crypto crate. Any of the methods or structs exposed here should be used first and foremost before anything else within the crypto crate.
🦖 System evolution - "Safe module"
The safe module is meant to serve as a repository for developers to draw from first and foremost. We may want to re-consider moving all other unsafer methods (RSA encrypt, direct encrypt of data, pin-key, masterkey) into a module - hazmat - later on and moving safe to the top level, once the safe crate contains sufficient good abstractions.
The abstractions contained here should be simple to understand, easy to reason about, easy to use in combination and very hard to mis-use.
For instance, master-password unlock, and pin unlock could be represented as:
which completely eliminates the need for developers to care about synchronizing kdf settings and salts, or even caring about things like a masterkey existing.
⏰ 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