bitwarden_crypto/store/
key_rotation.rs

1use crate::{
2    CoseKeyBytes, CoseSerializable, CryptoError, EncString, KeyEncryptable, KeyIds,
3    KeyStoreContext, SignedPublicKey, SignedPublicKeyMessage, SpkiPublicKeyBytes,
4    SymmetricCryptoKey,
5};
6
7/// Rotated set of account keys
8pub struct RotatedUserKeys {
9    /// The user's user key
10    pub user_key: SymmetricCryptoKey,
11    /// The verifying key
12    pub verifying_key: CoseKeyBytes,
13    /// Signing key, encrypted with a symmetric key (user key, org key)
14    pub signing_key: EncString,
15    /// The user's public key, signed by the signing key
16    pub signed_public_key: SignedPublicKey,
17    /// The user's public key, without signature
18    pub public_key: SpkiPublicKeyBytes,
19    /// The user's private key, encrypted with the user key
20    pub private_key: EncString,
21}
22
23/// Generates a new user key and re-encrypts the current private and signing keys with it.
24#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
25pub fn dangerous_get_v2_rotated_account_keys<Ids: KeyIds>(
26    current_user_private_key_id: Ids::Private,
27    current_user_signing_key_id: Ids::Signing,
28    ctx: &KeyStoreContext<Ids>,
29) -> Result<RotatedUserKeys, CryptoError> {
30    let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
31
32    let current_private_key = ctx.get_private_key(current_user_private_key_id)?;
33    let current_signing_key = ctx.get_signing_key(current_user_signing_key_id)?;
34
35    let current_public_key = &current_private_key.to_public_key();
36    let signed_public_key =
37        SignedPublicKeyMessage::from_public_key(current_public_key)?.sign(current_signing_key)?;
38
39    Ok(RotatedUserKeys {
40        verifying_key: current_signing_key.to_verifying_key().to_cose(),
41        signing_key: current_signing_key.to_cose().encrypt_with_key(&user_key)?,
42        signed_public_key,
43        public_key: current_public_key.to_der()?,
44        private_key: current_private_key.to_der()?.encrypt_with_key(&user_key)?,
45        user_key,
46    })
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::{
53        KeyDecryptable, KeyStore, Pkcs8PrivateKeyBytes, PrivateKey, PublicKeyEncryptionAlgorithm,
54        SignatureAlgorithm, SigningKey, traits::tests::TestIds,
55    };
56
57    #[test]
58    fn test_account_key_rotation() {
59        let store: KeyStore<TestIds> = KeyStore::default();
60        let mut ctx = store.context_mut();
61
62        // Make the keys
63        let current_user_signing_key_id = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
64        let current_user_private_key_id =
65            ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
66
67        // Get the rotated account keys
68        #[expect(deprecated)]
69        let rotated_keys = dangerous_get_v2_rotated_account_keys(
70            current_user_private_key_id,
71            current_user_signing_key_id,
72            &ctx,
73        )
74        .unwrap();
75
76        // Public/Private key
77        assert_eq!(
78            rotated_keys.public_key,
79            ctx.get_private_key(current_user_private_key_id)
80                .unwrap()
81                .to_public_key()
82                .to_der()
83                .unwrap()
84        );
85        let decrypted_private_key: Vec<u8> = rotated_keys
86            .private_key
87            .decrypt_with_key(&rotated_keys.user_key)
88            .unwrap();
89        let private_key =
90            PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(decrypted_private_key)).unwrap();
91        assert_eq!(
92            private_key.to_der().unwrap(),
93            ctx.get_private_key(current_user_private_key_id)
94                .unwrap()
95                .to_der()
96                .unwrap()
97        );
98
99        // Signing Key
100        let decrypted_signing_key: Vec<u8> = rotated_keys
101            .signing_key
102            .decrypt_with_key(&rotated_keys.user_key)
103            .unwrap();
104        let signing_key =
105            SigningKey::from_cose(&CoseKeyBytes::from(decrypted_signing_key)).unwrap();
106        assert_eq!(
107            signing_key.to_cose(),
108            ctx.get_signing_key(current_user_signing_key_id)
109                .unwrap()
110                .to_cose(),
111        );
112
113        // Signed Public Key
114        let signed_public_key = rotated_keys.signed_public_key;
115        let unwrapped_key = signed_public_key
116            .verify_and_unwrap(
117                &ctx.get_signing_key(current_user_signing_key_id)
118                    .unwrap()
119                    .to_verifying_key(),
120            )
121            .unwrap();
122        assert_eq!(
123            unwrapped_key.to_der().unwrap(),
124            ctx.get_private_key(current_user_private_key_id)
125                .unwrap()
126                .to_public_key()
127                .to_der()
128                .unwrap()
129        );
130    }
131}