use serde::{Deserialize, Serialize};
use crate::{
CryptoError, EncString, KeyDecryptable, KeyEncryptable, KeySlotIds, KeyStoreContext,
Pkcs8PrivateKeyBytes, PrivateKey, PublicKey, SpkiPublicKeyBytes, SymmetricCryptoKey,
UnsignedSharedKey,
};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(
feature = "wasm",
derive(tsify::Tsify),
tsify(into_wasm_abi, from_wasm_abi)
)]
pub struct RotateableKeySet {
encapsulated_downstream_key: UnsignedSharedKey,
encrypted_encapsulation_key: EncString,
encrypted_decapsulation_key: EncString,
}
impl RotateableKeySet {
pub fn new<Ids: KeySlotIds>(
ctx: &KeyStoreContext<Ids>,
upstream_key: &SymmetricCryptoKey,
downstream_key_id: Ids::Symmetric,
) -> Result<Self, CryptoError> {
let key_pair = PrivateKey::make(crate::PublicKeyEncryptionAlgorithm::RsaOaepSha1);
#[allow(deprecated)]
let downstream_key = ctx.dangerous_get_symmetric_key(downstream_key_id)?;
#[expect(deprecated)]
let encapsulated_downstream_key =
UnsignedSharedKey::encapsulate_key_unsigned(downstream_key, &key_pair.to_public_key())?;
let encrypted_decapsulation_key = key_pair.to_der()?.encrypt_with_key(upstream_key)?;
let encrypted_encapsulation_key = key_pair
.to_public_key()
.to_der()?
.encrypt_with_key(downstream_key)?;
Ok(RotateableKeySet {
encapsulated_downstream_key,
encrypted_encapsulation_key,
encrypted_decapsulation_key,
})
}
#[allow(dead_code)]
fn unlock<Ids: KeySlotIds>(
&self,
ctx: &mut KeyStoreContext<Ids>,
upstream_key: &SymmetricCryptoKey,
downstream_key_id: Ids::Symmetric,
) -> Result<(), CryptoError> {
let priv_key_bytes: Vec<u8> = self
.encrypted_decapsulation_key
.decrypt_with_key(upstream_key)?;
let decapsulation_key = PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(priv_key_bytes))?;
#[expect(deprecated)]
let downstream_key = self
.encapsulated_downstream_key
.decapsulate_key_unsigned(&decapsulation_key)?;
#[allow(deprecated)]
ctx.set_symmetric_key(downstream_key_id, downstream_key)?;
Ok(())
}
}
#[allow(dead_code)]
fn rotate_key_set<Ids: KeySlotIds>(
ctx: &KeyStoreContext<Ids>,
key_set: RotateableKeySet,
old_downstream_key_id: Ids::Symmetric,
new_downstream_key_id: Ids::Symmetric,
) -> Result<RotateableKeySet, CryptoError> {
let pub_key_bytes = ctx.decrypt_data_with_symmetric_key(
old_downstream_key_id,
&key_set.encrypted_encapsulation_key,
)?;
let pub_key = SpkiPublicKeyBytes::from(pub_key_bytes);
let encapsulation_key = PublicKey::from_der(&pub_key)?;
#[allow(deprecated)]
let new_downstream_key = ctx.dangerous_get_symmetric_key(new_downstream_key_id)?;
#[expect(deprecated)]
let new_encapsulated_key =
UnsignedSharedKey::encapsulate_key_unsigned(new_downstream_key, &encapsulation_key)?;
let new_encrypted_encapsulation_key = pub_key.encrypt_with_key(new_downstream_key)?;
Ok(RotateableKeySet {
encapsulated_downstream_key: new_encapsulated_key,
encrypted_encapsulation_key: new_encrypted_encapsulation_key,
encrypted_decapsulation_key: key_set.encrypted_decapsulation_key,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
KeyStore,
traits::tests::{TestIds, TestSymmKey},
};
#[test]
fn test_rotateable_key_set_can_unlock() {
let upstream_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
let store: KeyStore<TestIds> = KeyStore::default();
let mut ctx = store.context_mut();
let original_downstream_key_id = ctx.generate_symmetric_key();
let key_set =
RotateableKeySet::new(&ctx, &upstream_key, original_downstream_key_id).unwrap();
let unwrapped_downstream_key_id = TestSymmKey::A(1);
key_set
.unlock(&mut ctx, &upstream_key, unwrapped_downstream_key_id)
.unwrap();
#[allow(deprecated)]
let original_downstream_key = ctx
.dangerous_get_symmetric_key(original_downstream_key_id)
.unwrap();
#[allow(deprecated)]
let unwrapped_downstream_key = ctx
.dangerous_get_symmetric_key(unwrapped_downstream_key_id)
.unwrap();
assert_eq!(original_downstream_key, unwrapped_downstream_key);
}
#[test]
fn test_rotateable_key_set_rotation() {
let upstream_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
let store: KeyStore<TestIds> = KeyStore::default();
let mut ctx = store.context_mut();
let original_downstream_key_id = ctx.generate_symmetric_key();
let key_set =
RotateableKeySet::new(&ctx, &upstream_key, original_downstream_key_id).unwrap();
let new_downstream_key_id = ctx.generate_symmetric_key();
let new_key_set = rotate_key_set(
&ctx,
key_set,
original_downstream_key_id,
new_downstream_key_id,
)
.unwrap();
let unwrapped_downstream_key_id = TestSymmKey::A(2_2);
new_key_set
.unlock(&mut ctx, &upstream_key, unwrapped_downstream_key_id)
.unwrap();
#[allow(deprecated)]
let new_downstream_key = ctx
.dangerous_get_symmetric_key(new_downstream_key_id)
.unwrap();
#[allow(deprecated)]
let unwrapped_downstream_key = ctx
.dangerous_get_symmetric_key(unwrapped_downstream_key_id)
.unwrap();
assert_eq!(new_downstream_key, unwrapped_downstream_key);
}
}