use bitwarden_api_api::models::V2UpgradeTokenResponseModel;
use bitwarden_crypto::{
Decryptable, EncString, KeySlotIds, KeyStoreContext, SymmetricKeyAlgorithm,
};
use thiserror::Error;
use tracing::instrument;
#[cfg_attr(
feature = "wasm",
derive(tsify::Tsify),
tsify(into_wasm_abi, from_wasm_abi)
)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct V2UpgradeToken {
pub wrapped_user_key_1: EncString,
pub wrapped_user_key_2: EncString,
}
impl V2UpgradeToken {
#[instrument(skip(ctx))]
pub fn create<Ids: KeySlotIds>(
v1_key_id: Ids::Symmetric,
v2_key_id: Ids::Symmetric,
ctx: &KeyStoreContext<Ids>,
) -> Result<Self, V2UpgradeTokenError> {
if ctx
.get_symmetric_key_algorithm(v1_key_id)
.map_err(|_| V2UpgradeTokenError::KeyMissing)?
!= SymmetricKeyAlgorithm::Aes256CbcHmac
{
return Err(V2UpgradeTokenError::WrongKeyType);
}
if ctx
.get_symmetric_key_algorithm(v2_key_id)
.map_err(|_| V2UpgradeTokenError::KeyMissing)?
!= SymmetricKeyAlgorithm::XChaCha20Poly1305
{
return Err(V2UpgradeTokenError::WrongKeyType);
}
let wrapped_user_key_1 = ctx
.wrap_symmetric_key(v2_key_id, v1_key_id)
.map_err(|_| V2UpgradeTokenError::EncryptionFailed)?;
let wrapped_user_key_2 = ctx
.wrap_symmetric_key(v1_key_id, v2_key_id)
.map_err(|_| V2UpgradeTokenError::EncryptionFailed)?;
Ok(V2UpgradeToken {
wrapped_user_key_1,
wrapped_user_key_2,
})
}
#[instrument(skip(self, ctx))]
pub fn unwrap_v1<Ids: KeySlotIds>(
&self,
v2_key_id: Ids::Symmetric,
ctx: &mut KeyStoreContext<Ids>,
) -> Result<Ids::Symmetric, V2UpgradeTokenError> {
let v1_key_id = ctx
.unwrap_symmetric_key(v2_key_id, &self.wrapped_user_key_1)
.map_err(|_| V2UpgradeTokenError::DecryptionFailed)?;
let _: Vec<u8> = self
.wrapped_user_key_2
.decrypt(ctx, v1_key_id)
.map_err(|_| V2UpgradeTokenError::ValidationFailed)?;
Ok(v1_key_id)
}
#[instrument(skip(self, ctx))]
pub fn unwrap_v2<Ids: KeySlotIds>(
&self,
v1_key_id: Ids::Symmetric,
ctx: &mut KeyStoreContext<Ids>,
) -> Result<Ids::Symmetric, V2UpgradeTokenError> {
let v2_key_id = ctx
.unwrap_symmetric_key(v1_key_id, &self.wrapped_user_key_2)
.map_err(|_| V2UpgradeTokenError::DecryptionFailed)?;
let _: Vec<u8> = self
.wrapped_user_key_1
.decrypt(ctx, v2_key_id)
.map_err(|_| V2UpgradeTokenError::ValidationFailed)?;
Ok(v2_key_id)
}
}
impl TryFrom<&V2UpgradeTokenResponseModel> for V2UpgradeToken {
type Error = V2UpgradeTokenError;
fn try_from(response: &V2UpgradeTokenResponseModel) -> Result<Self, Self::Error> {
let wrapped_user_key_1 = response
.wrapped_user_key1
.as_deref()
.ok_or(V2UpgradeTokenError::ResponseModelMalformed)?
.parse()
.map_err(|_| V2UpgradeTokenError::ResponseModelMalformed)?;
let wrapped_user_key_2 = response
.wrapped_user_key2
.as_deref()
.ok_or(V2UpgradeTokenError::ResponseModelMalformed)?
.parse()
.map_err(|_| V2UpgradeTokenError::ResponseModelMalformed)?;
Ok(V2UpgradeToken {
wrapped_user_key_1,
wrapped_user_key_2,
})
}
}
#[derive(Debug, Error)]
pub enum V2UpgradeTokenError {
#[error("Decryption failed")]
DecryptionFailed,
#[error("Validation failed")]
ValidationFailed,
#[error("Serialization error")]
Serialization,
#[error("Wrong key type")]
WrongKeyType,
#[error("Key missing")]
KeyMissing,
#[error("Encryption failed")]
EncryptionFailed,
#[error("Response model malformed")]
ResponseModelMalformed,
}
#[cfg(test)]
mod tests {
use bitwarden_crypto::{KeyStore, SymmetricKeyAlgorithm};
use super::*;
use crate::key_management::KeySlotIds;
#[test]
fn test_create_and_round_trip() {
let key_store = KeyStore::<KeySlotIds>::default();
let mut ctx = key_store.context_mut();
let v1_key_id = ctx.generate_symmetric_key();
let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
.expect("Token creation should succeed");
let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
let deserialized: V2UpgradeToken =
serde_json::from_str(&serialized).expect("Deserialization should succeed");
let unwrapped_v2_id = deserialized
.unwrap_v2(v1_key_id, &mut ctx)
.expect("Unwrapping V2 should succeed");
#[allow(deprecated)]
let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
#[allow(deprecated)]
let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
assert_eq!(original_v2, unwrapped_v2);
}
#[test]
fn test_unwrap_bidirectional() {
let key_store = KeyStore::<KeySlotIds>::default();
let mut ctx = key_store.context_mut();
let v1_key_id = ctx.generate_symmetric_key();
let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
.expect("Token creation should succeed");
let unwrapped_v2_id = token
.unwrap_v2(v1_key_id, &mut ctx)
.expect("Unwrapping V2 should succeed");
let unwrapped_v1_id = token
.unwrap_v1(unwrapped_v2_id, &mut ctx)
.expect("Unwrapping V1 should succeed");
#[allow(deprecated)]
let original_v1 = ctx.dangerous_get_symmetric_key(v1_key_id).unwrap();
#[allow(deprecated)]
let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
#[allow(deprecated)]
let unwrapped_v1 = ctx.dangerous_get_symmetric_key(unwrapped_v1_id).unwrap();
#[allow(deprecated)]
let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
assert_eq!(original_v1, unwrapped_v1);
assert_eq!(original_v2, unwrapped_v2);
}
#[test]
fn test_create_wrong_key_type_error() {
let key_store = KeyStore::<KeySlotIds>::default();
let mut ctx = key_store.context_mut();
let v1_key_1 = ctx.generate_symmetric_key();
let v1_key_2 = ctx.generate_symmetric_key();
let result = V2UpgradeToken::create(v1_key_1, v1_key_2, &ctx);
assert!(matches!(result, Err(V2UpgradeTokenError::WrongKeyType)));
}
#[test]
fn test_serialization_round_trip() {
let key_store = KeyStore::<KeySlotIds>::default();
let mut ctx = key_store.context_mut();
let v1_key_id = ctx.generate_symmetric_key();
let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
.expect("Token creation should succeed");
let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
let json: serde_json::Value =
serde_json::from_str(&serialized).expect("Should be valid JSON");
assert!(json.is_object());
assert!(json.get("wrapped_user_key_1").is_some());
assert!(json.get("wrapped_user_key_2").is_some());
let deserialized: V2UpgradeToken =
serde_json::from_str(&serialized).expect("Deserialization should succeed");
let reserialized =
serde_json::to_string(&deserialized).expect("Reserialization should succeed");
assert_eq!(serialized, reserialized);
}
fn build_response_model<Ids: bitwarden_crypto::KeySlotIds>(
v1_key_id: Ids::Symmetric,
v2_key_id: Ids::Symmetric,
ctx: &KeyStoreContext<Ids>,
) -> V2UpgradeTokenResponseModel {
let wrapped_user_key_1 = ctx.wrap_symmetric_key(v2_key_id, v1_key_id).unwrap();
let wrapped_user_key_2 = ctx.wrap_symmetric_key(v1_key_id, v2_key_id).unwrap();
V2UpgradeTokenResponseModel {
wrapped_user_key1: Some(wrapped_user_key_1.to_string()),
wrapped_user_key2: Some(wrapped_user_key_2.to_string()),
}
}
#[test]
fn test_from_response_model_missing_wrapped_uk1() {
let response = V2UpgradeTokenResponseModel {
wrapped_user_key1: None,
wrapped_user_key2: None,
};
assert!(matches!(
V2UpgradeToken::try_from(&response),
Err(V2UpgradeTokenError::ResponseModelMalformed)
));
}
#[test]
fn test_from_response_model_missing_wrapped_uk2() {
let key_store = KeyStore::<KeySlotIds>::default();
let mut ctx = key_store.context_mut();
let v1_key_id = ctx.generate_symmetric_key();
let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
let mut response = build_response_model(v1_key_id, v2_key_id, &ctx);
response.wrapped_user_key2 = None;
assert!(matches!(
V2UpgradeToken::try_from(&response),
Err(V2UpgradeTokenError::ResponseModelMalformed)
));
}
#[test]
fn test_serde_round_trip() {
let key_store = KeyStore::<KeySlotIds>::default();
let mut ctx = key_store.context_mut();
let v1_key_id = ctx.generate_symmetric_key();
let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
.expect("Token creation should succeed");
let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
let deserialized: V2UpgradeToken =
serde_json::from_str(&serialized).expect("Deserialization should succeed");
let unwrapped_v2_id = deserialized
.unwrap_v2(v1_key_id, &mut ctx)
.expect("Unwrapping V2 from serde-deserialized token should succeed");
#[allow(deprecated)]
let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
#[allow(deprecated)]
let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
assert_eq!(original_v2, unwrapped_v2);
}
}