Skip to content

[PM-18102] Use opaque autogenerated local key ids #274

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

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions crates/bitwarden-core/src/key_management/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

use crate::{
client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod},
key_management::{AsymmetricKeyId, SigningKeyId, SymmetricKeyId},
key_management::{AsymmetricKeyId, SymmetricKeyId},
Client, NotAuthenticatedError, VaultLockedError, WrongPasswordError,
};

Expand Down Expand Up @@ -596,9 +596,8 @@

// Make new keypair and sign the public key with it
let signature_keypair = SigningKey::make(SignatureAlgorithm::Ed25519);
let temporary_signature_keypair_id = SigningKeyId::Local("temporary_key_for_rotation");
#[allow(deprecated)]
ctx.set_signing_key(temporary_signature_keypair_id, signature_keypair.clone())?;
let temporary_signature_keypair_id = ctx.add_local_signing_key(signature_keypair.clone())?;

Check warning on line 599 in crates/bitwarden-core/src/key_management/crypto.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-core/src/key_management/crypto.rs#L599

Added line #L599 was not covered by tests

let signed_public_key = ctx.make_signed_public_key(
AsymmetricKeyId::UserPrivateKey,
temporary_signature_keypair_id,
Expand Down
6 changes: 3 additions & 3 deletions crates/bitwarden-core/src/key_management/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ key_ids! {
User,
Organization(uuid::Uuid),
#[local]
Local(&'static str),
Local(LocalId),
}

#[asymmetric]
pub enum AsymmetricKeyId {
UserPrivateKey,
#[local]
Local(&'static str),
Local(LocalId),
}

#[signing]
pub enum SigningKeyId {
UserSigningKey,
#[local]
Local(&'static str),
Local(LocalId),
}

pub KeyIds => SymmetricKeyId, AsymmetricKeyId, SigningKeyId;
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub use signing::*;
mod traits;
mod xchacha20;
pub use traits::{
CompositeEncryptable, Decryptable, IdentifyKey, KeyId, KeyIds, PrimitiveEncryptable,
CompositeEncryptable, Decryptable, IdentifyKey, KeyId, KeyIds, LocalId, PrimitiveEncryptable,
};
pub use zeroizing_alloc::ZeroAlloc as ZeroizingAllocator;

Expand Down
79 changes: 42 additions & 37 deletions crates/bitwarden-crypto/src/store/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use crate::{
derive_shareable_key, error::UnsupportedOperation, signing, store::backend::StoreBackend,
AsymmetricCryptoKey, BitwardenLegacyKeyBytes, ContentFormat, CryptoError, EncString, KeyId,
KeyIds, Result, Signature, SignatureAlgorithm, SignedObject, SignedPublicKey,
KeyIds, LocalId, Result, Signature, SignatureAlgorithm, SignedObject, SignedPublicKey,
SignedPublicKeyMessage, SigningKey, SymmetricCryptoKey, UnsignedSharedKey,
};

Expand All @@ -37,15 +37,20 @@
/// # #[symmetric]
/// # pub enum SymmKeyId {
/// # User,
/// # Local(&'static str),
/// # #[local]
/// # Local(LocalId),
/// # }
/// # #[asymmetric]
/// # pub enum AsymmKeyId {
/// # UserPrivate,
/// # #[local]
/// # Local(LocalId),
/// # }
/// # #[signing]
/// # pub enum SigningKeyId {
/// # UserSigning,
/// # #[local]
/// # Local(LocalId),
/// # }
/// # pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId;
/// # }
Expand All @@ -59,11 +64,10 @@
/// # }
/// # }
///
/// const LOCAL_KEY: SymmKeyId = SymmKeyId::Local("local_key_id");
///
/// impl CompositeEncryptable<Ids, SymmKeyId, EncString> for Data {
/// fn encrypt_composite(&self, ctx: &mut KeyStoreContext<Ids>, key: SymmKeyId) -> Result<EncString, CryptoError> {
/// let local_key_id = ctx.unwrap_symmetric_key(key, LOCAL_KEY, &self.key)?;
/// let local_key_id = ctx.unwrap_symmetric_key(key, &self.key)?;
/// self.name.encrypt(ctx, local_key_id)
/// }
/// }
Expand Down Expand Up @@ -149,7 +153,6 @@
pub fn unwrap_symmetric_key(
&mut self,
wrapping_key: Ids::Symmetric,
new_key_id: Ids::Symmetric,
wrapped_key: &EncString,
) -> Result<Ids::Symmetric> {
let wrapping_key = self.get_symmetric_key(wrapping_key)?;
Expand Down Expand Up @@ -183,6 +186,8 @@
_ => return Err(CryptoError::InvalidKey),
};

let new_key_id = Ids::Symmetric::new_local(LocalId::new());

#[allow(deprecated)]
self.set_symmetric_key(new_key_id, key)?;

Expand Down Expand Up @@ -301,20 +306,14 @@
}

/// Generate a new random symmetric key and store it in the context
pub fn generate_symmetric_key(&mut self, key_id: Ids::Symmetric) -> Result<Ids::Symmetric> {
let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
#[allow(deprecated)]
self.set_symmetric_key(key_id, key)?;
Ok(key_id)
pub fn generate_symmetric_key(&mut self) -> Result<Ids::Symmetric> {
self.add_local_symmetric_key(SymmetricCryptoKey::make_aes256_cbc_hmac_key())
}

/// Generate a new signature key using the current default algorithm, and store it in the
/// context
pub fn make_signing_key(&mut self, key_id: Ids::Signing) -> Result<Ids::Signing> {
let key = SigningKey::make(SignatureAlgorithm::default_algorithm());
#[allow(deprecated)]
self.set_signing_key(key_id, key)?;
Ok(key_id)
pub fn make_signing_key(&mut self) -> Result<Ids::Signing> {
self.add_local_signing_key(SigningKey::make(SignatureAlgorithm::default_algorithm()))

Check warning on line 316 in crates/bitwarden-crypto/src/store/context.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/store/context.rs#L315-L316

Added lines #L315 - L316 were not covered by tests
}

/// Derive a shareable key using hkdf from secret and name and store it in the context.
Expand All @@ -323,11 +322,11 @@
/// Bitwarden `clients` repository.
pub fn derive_shareable_key(
&mut self,
key_id: Ids::Symmetric,
secret: Zeroizing<[u8; 16]>,
name: &str,
info: Option<&str>,
) -> Result<Ids::Symmetric> {
let key_id = Ids::Symmetric::new_local(LocalId::new());
#[allow(deprecated)]
self.set_symmetric_key(
key_id,
Expand Down Expand Up @@ -414,6 +413,13 @@
Ok(())
}

/// Add a new symmetric key to the local context, returning a new unique identifier for it.
pub fn add_local_symmetric_key(&mut self, key: SymmetricCryptoKey) -> Result<Ids::Symmetric> {
let key_id = Ids::Symmetric::new_local(LocalId::new());
self.local_symmetric_keys.upsert(key_id, key);
Ok(key_id)
}

#[deprecated(note = "This function should ideally never be used outside this crate")]
#[allow(missing_docs)]
pub fn set_asymmetric_key(
Expand Down Expand Up @@ -443,6 +449,13 @@
Ok(())
}

/// Add a new signing key to the local context, returning a new unique identifier for it.
pub fn add_local_signing_key(&mut self, key: SigningKey) -> Result<Ids::Signing> {
let key_id = Ids::Signing::new_local(LocalId::new());
self.local_signing_keys.upsert(key_id, key);
Ok(key_id)
}

Check warning on line 457 in crates/bitwarden-crypto/src/store/context.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/store/context.rs#L453-L457

Added lines #L453 - L457 were not covered by tests

pub(crate) fn decrypt_data_with_symmetric_key(
&self,
key: Ids::Symmetric,
Expand Down Expand Up @@ -525,7 +538,7 @@
KeyStore,
},
traits::tests::{TestIds, TestSigningKey, TestSymmKey},
CompositeEncryptable, CryptoError, Decryptable, SignatureAlgorithm, SigningKey,
CompositeEncryptable, CryptoError, Decryptable, LocalId, SignatureAlgorithm, SigningKey,
SigningNamespace, SymmetricCryptoKey,
};

Expand Down Expand Up @@ -567,19 +580,20 @@
#[test]
fn test_key_encryption() {
let store: KeyStore<TestIds> = KeyStore::default();
let local = LocalId::new();

let mut ctx = store.context();

// Generate and insert a key
let key_1_id = TestSymmKey::C(1);
let key_1_id = TestSymmKey::C(local);
let key_1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();

ctx.set_symmetric_key(key_1_id, key_1.clone()).unwrap();

assert!(ctx.has_symmetric_key(key_1_id));

// Generate and insert a new key
let key_2_id = TestSymmKey::C(2);
let key_2_id = TestSymmKey::C(local);
let key_2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();

ctx.set_symmetric_key(key_2_id, key_2.clone()).unwrap();
Expand All @@ -590,10 +604,7 @@
let key_2_enc = ctx.wrap_symmetric_key(key_1_id, key_2_id).unwrap();

// Decrypt the new key with the old key in a different identifier
let new_key_id = TestSymmKey::C(3);

ctx.unwrap_symmetric_key(key_1_id, new_key_id, &key_2_enc)
.unwrap();
let new_key_id = ctx.unwrap_symmetric_key(key_1_id, &key_2_enc).unwrap();

// Now `key_2_id` and `new_key_id` contain the same key, so we should be able to encrypt
// with one and decrypt with the other
Expand Down Expand Up @@ -646,24 +657,18 @@
.unwrap();

// Unwrap the keys
let unwrapped_key_2 = ctx
.unwrap_symmetric_key(key_aes_1_id, key_aes_2_id, &wrapped_key_1_2)
let _unwrapped_key_2 = ctx
.unwrap_symmetric_key(key_aes_1_id, &wrapped_key_1_2)
.unwrap();
let unwrapped_key_3 = ctx
.unwrap_symmetric_key(key_aes_1_id, key_xchacha_3_id, &wrapped_key_1_3)
let _unwrapped_key_3 = ctx
.unwrap_symmetric_key(key_aes_1_id, &wrapped_key_1_3)
.unwrap();
let unwrapped_key_1 = ctx
.unwrap_symmetric_key(key_xchacha_3_id, key_aes_1_id, &wrapped_key_3_1)
let _unwrapped_key_1 = ctx
.unwrap_symmetric_key(key_xchacha_3_id, &wrapped_key_3_1)
.unwrap();
let unwrapped_key_4 = ctx
.unwrap_symmetric_key(key_xchacha_3_id, key_xchacha_4_id, &wrapped_key_3_4)
let _unwrapped_key_4 = ctx
.unwrap_symmetric_key(key_xchacha_3_id, &wrapped_key_3_4)
.unwrap();

// Assert that the unwrapped keys are the same as the original keys
assert_eq!(unwrapped_key_2, key_aes_2_id);
assert_eq!(unwrapped_key_3, key_xchacha_3_id);
assert_eq!(unwrapped_key_1, key_aes_1_id);
assert_eq!(unwrapped_key_4, key_xchacha_4_id);
}

#[test]
Expand Down
6 changes: 5 additions & 1 deletion crates/bitwarden-crypto/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,19 @@ pub use context::KeyStoreContext;
/// pub enum SymmKeyId {
/// User,
/// #[local]
/// Local(&'static str)
/// Local(LocalId),
/// }
/// #[asymmetric]
/// pub enum AsymmKeyId {
/// UserPrivate,
/// #[local]
/// Local(LocalId),
/// }
/// #[signing]
/// pub enum SigningKeyId {
/// UserSigning,
/// #[local]
/// Local(LocalId),
/// }
/// pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId;
/// }
Expand Down
43 changes: 38 additions & 5 deletions crates/bitwarden-crypto/src/traits/key_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub trait KeyId:
/// Returns whether the key is local to the current context or shared globally by the
/// key store. See [crate::store::KeyStoreContext] for more information.
fn is_local(&self) -> bool;

/// Creates a new unique local key identifier.
fn new_local(id: LocalId) -> Self;
}

/// Represents a set of all the key identifiers that need to be defined to use a key store.
Expand All @@ -37,6 +40,17 @@ pub trait KeyIds {
type Signing: KeyId<KeyValue = SigningKey>;
}

/// An opaque identifier for a local key. Currently only contains a unique ID, but it can be
/// extended to contain scope information to allow cleanup on scope exit.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct LocalId(pub(crate) uuid::Uuid);

impl LocalId {
pub(crate) fn new() -> Self {
LocalId(uuid::Uuid::new_v4())
}
}

/// Just a small derive_like macro that can be used to generate the key identifier enums.
/// Example usage:
/// ```rust
Expand All @@ -47,17 +61,21 @@ pub trait KeyIds {
/// User,
/// Org(uuid::Uuid),
/// #[local]
/// Local(&'static str),
/// Local(LocalId),
/// }
///
/// #[asymmetric]
/// pub enum AsymmKeyId {
/// PrivateKey,
/// #[local]
/// Local(LocalId),
/// }
///
/// #[signing]
/// pub enum SigningKeyId {
/// SigningKey,
/// #[local]
/// Local(LocalId),
/// }
///
/// pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId;
Expand All @@ -76,6 +94,9 @@ macro_rules! key_ids {
)+
$ids_vis:vis $ids_name:ident => $symm_name:ident, $asymm_name:ident, $signing_name:ident;
) => {

use $crate::LocalId;

$(
#[derive(std::fmt::Debug, Clone, Copy, std::hash::Hash, Eq, PartialEq, Ord, PartialOrd)]
#[allow(missing_docs)]
Expand All @@ -93,6 +114,12 @@ macro_rules! key_ids {
key_ids!(@variant_value $( $variant_tag )? ),
)* }
}

fn new_local(id: LocalId) -> Self {
$(
{ key_ids!(@new_local $variant id $( $variant_tag )? ) }
)*
}
}
)+

Expand All @@ -114,27 +141,33 @@ macro_rules! key_ids {

( @variant_value local ) => { true };
( @variant_value ) => { false };

( @new_local $variant:ident $id:ident local ) => { Self::$variant($id) };
( @new_local $variant:ident $id:ident ) => {{}};
}

#[cfg(test)]
pub(crate) mod tests {

use crate::{
traits::tests::{TestAsymmKey, TestSigningKey, TestSymmKey},
KeyId,
KeyId, LocalId,
};

#[test]
fn test_local() {
let local = LocalId::new();

assert!(!TestSymmKey::A(0).is_local());
assert!(!TestSymmKey::B((4, 10)).is_local());
assert!(TestSymmKey::C(8).is_local());
assert!(TestSymmKey::C(local).is_local());

assert!(!TestAsymmKey::A(0).is_local());
assert!(!TestAsymmKey::B.is_local());
assert!(TestAsymmKey::C("test").is_local());
assert!(TestAsymmKey::C(local).is_local());

assert!(!TestSigningKey::A(0).is_local());
assert!(!TestSigningKey::B.is_local());
assert!(TestSigningKey::C("test").is_local());
assert!(TestSigningKey::C(local).is_local());
}
}
Loading