|
| 1 | +use serde::{Deserialize, Serialize}; |
| 2 | +use tsify::Tsify; |
| 3 | + |
1 | 4 | use crate::{
|
2 |
| - CoseKeyBytes, CoseSerializable, CryptoError, EncString, KeyEncryptable, KeyIds, |
3 |
| - KeyStoreContext, SignedPublicKey, SignedPublicKeyMessage, SpkiPublicKeyBytes, |
4 |
| - SymmetricCryptoKey, |
| 5 | + AsymmetricCryptoKey, AsymmetricPublicCryptoKey, CoseKeyBytes, CoseSerializable, CryptoError, |
| 6 | + EncString, KeyDecryptable, KeyEncryptable, KeyIds, KeyStoreContext, Pkcs8PrivateKeyBytes, |
| 7 | + SignedPublicKey, SignedPublicKeyMessage, SpkiPublicKeyBytes, SymmetricCryptoKey, |
| 8 | + UnsignedSharedKey, |
5 | 9 | };
|
6 | 10 |
|
7 | 11 | /// Rotated set of account keys
|
@@ -45,6 +49,113 @@ pub fn dangerous_get_v2_rotated_account_keys<Ids: KeyIds>(
|
45 | 49 | })
|
46 | 50 | }
|
47 | 51 |
|
| 52 | +/// A set of keys where a given `EncryptionKey` is protected by an encrypted public/private |
| 53 | +/// key-pair. The `EncryptionKey` is used to encrypt/decrypt data, while the public/private key-pair |
| 54 | +/// is used to rotate the `EncryptionKey`. |
| 55 | +/// |
| 56 | +/// The `PrivateKey` is protected by an `ExternalKey`, such as a `DeviceKey`, or `PrfKey`, |
| 57 | +/// and the `PublicKey` is protected by the `EncryptionKey`. This setup allows: |
| 58 | +/// |
| 59 | +/// - Access to `EncryptionKey` by knowing the `ExternalKey` |
| 60 | +/// - Rotation to a `NewEncryptionKey` by knowing the current `UserKey`, without needing access to |
| 61 | +/// the `ExternalKey` |
| 62 | +#[derive(Serialize, Deserialize, Debug)] |
| 63 | +#[serde(rename_all = "camelCase", deny_unknown_fields)] |
| 64 | +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] |
| 65 | +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] |
| 66 | +pub struct RotateableKeySet { |
| 67 | + /// `EncryptionKey` protected by encapsulation key |
| 68 | + encapsulated_encryption_key: UnsignedSharedKey, |
| 69 | + /// Encapsulation key protected by `EncryptionKey` |
| 70 | + encrypted_encapsulation_key: EncString, |
| 71 | + /// Decapsulation key protected by `ExternalKey` |
| 72 | + wrapped_decapsulation_key: EncString, |
| 73 | +} |
| 74 | + |
| 75 | +impl RotateableKeySet { |
| 76 | + /// Create a set of keys to allow access to the user key via the provided |
| 77 | + /// symmetric wrapping key while allowing the user key to be rotated. |
| 78 | + pub fn new<Ids: KeyIds>( |
| 79 | + ctx: &KeyStoreContext<Ids>, |
| 80 | + wrapping_key: &SymmetricCryptoKey, |
| 81 | + key_to_wrap: Ids::Symmetric, |
| 82 | + ) -> Result<Self, CryptoError> { |
| 83 | + let key_pair = AsymmetricCryptoKey::make(crate::PublicKeyEncryptionAlgorithm::RsaOaepSha1); |
| 84 | + |
| 85 | + #[allow(deprecated)] |
| 86 | + let key_to_wrap_instance = ctx.dangerous_get_symmetric_key(key_to_wrap)?; |
| 87 | + // encapsulate encryption key |
| 88 | + let encapsulated_encryption_key = UnsignedSharedKey::encapsulate_key_unsigned( |
| 89 | + key_to_wrap_instance, |
| 90 | + &key_pair.to_public_key(), |
| 91 | + )?; |
| 92 | + |
| 93 | + // wrap decapsulation key |
| 94 | + let wrapped_decapsulation_key = key_pair.to_der()?.encrypt_with_key(wrapping_key)?; |
| 95 | + |
| 96 | + // wrap encapsulation key with encryption key |
| 97 | + // Note: Usually, a public key is - by definition - public, so this should not be necessary. |
| 98 | + // The specific use-case for this function is to enable rotateable key sets, where |
| 99 | + // the "public key" is not public, with the intent of preventing the server from being able |
| 100 | + // to overwrite the user key unlocked by the rotateable keyset. |
| 101 | + let encrypted_encapsulation_key = key_pair |
| 102 | + .to_public_key() |
| 103 | + .to_der()? |
| 104 | + .encrypt_with_key(&key_to_wrap_instance)?; |
| 105 | + |
| 106 | + Ok(RotateableKeySet { |
| 107 | + encapsulated_encryption_key, |
| 108 | + encrypted_encapsulation_key, |
| 109 | + wrapped_decapsulation_key, |
| 110 | + }) |
| 111 | + } |
| 112 | + |
| 113 | + fn unlock<Ids: KeyIds>( |
| 114 | + &self, |
| 115 | + ctx: &mut KeyStoreContext<Ids>, |
| 116 | + unwrapping_key: &SymmetricCryptoKey, |
| 117 | + key_to_unwrap: Ids::Symmetric, |
| 118 | + ) -> Result<(), CryptoError> { |
| 119 | + let priv_key_bytes: Vec<u8> = self |
| 120 | + .wrapped_decapsulation_key |
| 121 | + .decrypt_with_key(unwrapping_key)?; |
| 122 | + let decapsulation_key = |
| 123 | + AsymmetricCryptoKey::from_der(&Pkcs8PrivateKeyBytes::from(priv_key_bytes))?; |
| 124 | + let encryption_key = self |
| 125 | + .encapsulated_encryption_key |
| 126 | + .decapsulate_key_unsigned(&decapsulation_key)?; |
| 127 | + #[allow(deprecated)] |
| 128 | + ctx.set_symmetric_key(key_to_unwrap, encryption_key)?; |
| 129 | + Ok(()) |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +fn rotate_key_set<Ids: KeyIds>( |
| 134 | + ctx: &KeyStoreContext<Ids>, |
| 135 | + key_set: RotateableKeySet, |
| 136 | + old_encryption_key_id: Ids::Symmetric, |
| 137 | + new_encryption_key_id: Ids::Symmetric, |
| 138 | +) -> Result<RotateableKeySet, CryptoError> { |
| 139 | + let pub_key_bytes = ctx.decrypt_data_with_symmetric_key( |
| 140 | + old_encryption_key_id, |
| 141 | + &key_set.encrypted_encapsulation_key, |
| 142 | + )?; |
| 143 | + let pub_key = SpkiPublicKeyBytes::from(pub_key_bytes); |
| 144 | + let encapsulation_key = AsymmetricPublicCryptoKey::from_der(&pub_key)?; |
| 145 | + // TODO: There is no method to store only the public key in the store, so we |
| 146 | + // have pull out the encryption key to encapsulate it manually. |
| 147 | + #[allow(deprecated)] |
| 148 | + let new_encryption_key = ctx.dangerous_get_symmetric_key(new_encryption_key_id)?; |
| 149 | + let new_encapsulated_key = |
| 150 | + UnsignedSharedKey::encapsulate_key_unsigned(new_encryption_key, &encapsulation_key)?; |
| 151 | + let new_encrypted_encapsulation_key = pub_key.encrypt_with_key(new_encryption_key)?; |
| 152 | + Ok(RotateableKeySet { |
| 153 | + encapsulated_encryption_key: new_encapsulated_key, |
| 154 | + encrypted_encapsulation_key: new_encrypted_encapsulation_key, |
| 155 | + wrapped_decapsulation_key: key_set.wrapped_decapsulation_key, |
| 156 | + }) |
| 157 | +} |
| 158 | + |
48 | 159 | #[cfg(test)]
|
49 | 160 | mod tests {
|
50 | 161 | use super::*;
|
@@ -137,4 +248,79 @@ mod tests {
|
137 | 248 | .unwrap()
|
138 | 249 | );
|
139 | 250 | }
|
| 251 | + |
| 252 | + #[test] |
| 253 | + fn test_rotateable_key_set_can_unlock() { |
| 254 | + // generate initial keys |
| 255 | + let external_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); |
| 256 | + // set up store |
| 257 | + let store: KeyStore<TestIds> = KeyStore::default(); |
| 258 | + let mut ctx = store.context_mut(); |
| 259 | + let original_encryption_key_id = TestSymmKey::A(0); |
| 260 | + ctx.generate_symmetric_key(original_encryption_key_id) |
| 261 | + .unwrap(); |
| 262 | + |
| 263 | + // create key set |
| 264 | + let key_set = |
| 265 | + RotateableKeySet::new(&ctx, &external_key, original_encryption_key_id).unwrap(); |
| 266 | + |
| 267 | + // unlock key set |
| 268 | + let unwrapped_encryption_key_id = TestSymmKey::A(1); |
| 269 | + key_set |
| 270 | + .unlock(&mut ctx, &external_key, unwrapped_encryption_key_id) |
| 271 | + .unwrap(); |
| 272 | + |
| 273 | + #[allow(deprecated)] |
| 274 | + let original_key = ctx |
| 275 | + .dangerous_get_symmetric_key(original_encryption_key_id) |
| 276 | + .unwrap(); |
| 277 | + #[allow(deprecated)] |
| 278 | + let unwrapped_key = ctx |
| 279 | + .dangerous_get_symmetric_key(unwrapped_encryption_key_id) |
| 280 | + .unwrap(); |
| 281 | + assert_eq!(original_key, unwrapped_key); |
| 282 | + } |
| 283 | + |
| 284 | + #[test] |
| 285 | + fn test_rotateable_key_set_rotation() { |
| 286 | + // generate initial keys |
| 287 | + let external_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); |
| 288 | + // set up store |
| 289 | + let store: KeyStore<TestIds> = KeyStore::default(); |
| 290 | + let mut ctx = store.context_mut(); |
| 291 | + let original_encryption_key_id = TestSymmKey::A(1); |
| 292 | + ctx.generate_symmetric_key(original_encryption_key_id) |
| 293 | + .unwrap(); |
| 294 | + |
| 295 | + // create key set |
| 296 | + let key_set = |
| 297 | + RotateableKeySet::new(&ctx, &external_key, original_encryption_key_id).unwrap(); |
| 298 | + |
| 299 | + // rotate |
| 300 | + let new_encryption_key_id = TestSymmKey::A(2_1); |
| 301 | + ctx.generate_symmetric_key(new_encryption_key_id).unwrap(); |
| 302 | + let new_key_set = rotate_key_set( |
| 303 | + &ctx, |
| 304 | + key_set, |
| 305 | + original_encryption_key_id, |
| 306 | + new_encryption_key_id, |
| 307 | + ) |
| 308 | + .unwrap(); |
| 309 | + |
| 310 | + // After rotation, the new key set should be unlocked by the same |
| 311 | + // external key and return the new encryption key. |
| 312 | + let unwrapped_encryption_key_id = TestSymmKey::A(2_2); |
| 313 | + new_key_set |
| 314 | + .unlock(&mut ctx, &external_key, unwrapped_encryption_key_id) |
| 315 | + .unwrap(); |
| 316 | + #[allow(deprecated)] |
| 317 | + let new_encryption_key = ctx |
| 318 | + .dangerous_get_symmetric_key(new_encryption_key_id) |
| 319 | + .unwrap(); |
| 320 | + #[allow(deprecated)] |
| 321 | + let unwrapped_encryption_key = ctx |
| 322 | + .dangerous_get_symmetric_key(unwrapped_encryption_key_id) |
| 323 | + .unwrap(); |
| 324 | + assert_eq!(new_encryption_key, unwrapped_encryption_key); |
| 325 | + } |
140 | 326 | }
|
0 commit comments