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.
24pub fn dangerous_get_v2_rotated_account_keys<Ids: KeyIds>(
25    current_user_private_key_id: Ids::Asymmetric,
26    current_user_signing_key_id: Ids::Signing,
27    ctx: &KeyStoreContext<Ids>,
28) -> Result<RotatedUserKeys, CryptoError> {
29    let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
30
31    let current_private_key = ctx.get_asymmetric_key(current_user_private_key_id)?;
32    let current_signing_key = ctx.get_signing_key(current_user_signing_key_id)?;
33
34    let current_public_key = &current_private_key.to_public_key();
35    let signed_public_key =
36        SignedPublicKeyMessage::from_public_key(current_public_key)?.sign(current_signing_key)?;
37
38    Ok(RotatedUserKeys {
39        verifying_key: current_signing_key.to_verifying_key().to_cose(),
40        signing_key: current_signing_key.to_cose().encrypt_with_key(&user_key)?,
41        signed_public_key,
42        public_key: current_public_key.to_der()?,
43        private_key: current_private_key.to_der()?.encrypt_with_key(&user_key)?,
44        user_key,
45    })
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::{
52        AsymmetricCryptoKey, KeyDecryptable, KeyStore, Pkcs8PrivateKeyBytes, SignatureAlgorithm,
53        SigningKey, traits::tests::TestIds,
54    };
55
56    #[test]
57    fn test_account_key_rotation() {
58        let store: KeyStore<TestIds> = KeyStore::default();
59        let mut ctx = store.context_mut();
60
61        // Make the keys
62        let current_user_signing_key_id =
63            ctx.make_signing_key(SignatureAlgorithm::Ed25519).unwrap();
64        let current_user_private_key_id = ctx.make_asymmetric_key().unwrap();
65
66        // Get the rotated account keys
67        let rotated_keys = dangerous_get_v2_rotated_account_keys(
68            current_user_private_key_id,
69            current_user_signing_key_id,
70            &ctx,
71        )
72        .unwrap();
73
74        // Public/Private key
75        assert_eq!(
76            rotated_keys.public_key,
77            ctx.get_asymmetric_key(current_user_private_key_id)
78                .unwrap()
79                .to_public_key()
80                .to_der()
81                .unwrap()
82        );
83        let decrypted_private_key: Vec<u8> = rotated_keys
84            .private_key
85            .decrypt_with_key(&rotated_keys.user_key)
86            .unwrap();
87        let private_key =
88            AsymmetricCryptoKey::from_der(&Pkcs8PrivateKeyBytes::from(decrypted_private_key))
89                .unwrap();
90        assert_eq!(
91            private_key.to_der().unwrap(),
92            ctx.get_asymmetric_key(current_user_private_key_id)
93                .unwrap()
94                .to_der()
95                .unwrap()
96        );
97
98        // Signing Key
99        let decrypted_signing_key: Vec<u8> = rotated_keys
100            .signing_key
101            .decrypt_with_key(&rotated_keys.user_key)
102            .unwrap();
103        let signing_key =
104            SigningKey::from_cose(&CoseKeyBytes::from(decrypted_signing_key)).unwrap();
105        assert_eq!(
106            signing_key.to_cose(),
107            ctx.get_signing_key(current_user_signing_key_id)
108                .unwrap()
109                .to_cose(),
110        );
111
112        // Signed Public Key
113        let signed_public_key = rotated_keys.signed_public_key;
114        let unwrapped_key = signed_public_key
115            .verify_and_unwrap(
116                &ctx.get_signing_key(current_user_signing_key_id)
117                    .unwrap()
118                    .to_verifying_key(),
119            )
120            .unwrap();
121        assert_eq!(
122            unwrapped_key.to_der().unwrap(),
123            ctx.get_asymmetric_key(current_user_private_key_id)
124                .unwrap()
125                .to_public_key()
126                .to_der()
127                .unwrap()
128        );
129    }
130}