use wacore::appstate::expand_app_state_keys;
use wacore::appstate::hash::{HashState, generate_content_mac, generate_patch_mac};
use waproto::whatsapp as wa;
fn make_record(key_id: &[u8], value_with_mac: Vec<u8>, index_mac: Vec<u8>) -> wa::SyncdRecord {
wa::SyncdRecord {
index: wa::SyncdIndex {
blob: Some(index_mac),
}
.into(),
value: wa::SyncdValue {
blob: Some(value_with_mac),
}
.into(),
key_id: wa::KeyId {
id: Some(key_id.to_vec()),
}
.into(),
}
}
#[test]
fn snapshot_and_patch_mac_roundtrip() {
let master_key = [7u8; 32];
let keys = expand_app_state_keys(&master_key);
let key_id = b"abc"; let mut state = HashState::default();
let iv = [1u8; 16];
let ciphertext1 = b"cipher_one".to_vec();
let mut content1 = iv.to_vec();
content1.extend_from_slice(&ciphertext1);
let value_mac1 = generate_content_mac(
wa::syncd_mutation::SyncdOperation::SET,
&content1,
key_id,
&keys.value_mac,
);
let mut value_blob1 = content1.clone();
value_blob1.extend_from_slice(&value_mac1);
let ciphertext2 = b"cipher_two".to_vec();
let mut content2 = iv.to_vec();
content2.extend_from_slice(&ciphertext2);
let value_mac2 = generate_content_mac(
wa::syncd_mutation::SyncdOperation::SET,
&content2,
key_id,
&keys.value_mac,
);
let mut value_blob2 = content2.clone();
value_blob2.extend_from_slice(&value_mac2);
let index_mac1 = vec![9u8; 32];
let index_mac2 = vec![8u8; 32];
let mutation1 = wa::SyncdMutation {
operation: Some(wa::syncd_mutation::SyncdOperation::SET.into()),
record: make_record(key_id, value_blob1.clone(), index_mac1.clone()).into(),
};
let mutation2 = wa::SyncdMutation {
operation: Some(wa::syncd_mutation::SyncdOperation::SET.into()),
record: make_record(key_id, value_blob2.clone(), index_mac2.clone()).into(),
};
let mutations = vec![mutation1.clone(), mutation2.clone()];
let (_warn, res) = state.update_hash(&mutations, |_idx, _i| Ok(None));
res.expect("update hash");
state.version = 1;
let snapshot_mac = state.generate_snapshot_mac("regular_high", &keys.snapshot_mac);
let patch = wa::SyncdPatch {
version: wa::SyncdVersion {
version: Some(state.version),
}
.into(),
mutations: mutations.clone(),
snapshot_mac: Some(snapshot_mac.clone()),
key_id: wa::KeyId {
id: Some(key_id.to_vec()),
}
.into(),
..Default::default()
};
let patch_mac = generate_patch_mac(&patch, "regular_high", &keys.patch_mac, state.version);
assert_ne!(
snapshot_mac, patch_mac,
"snapshot and patch MACs should differ"
);
assert_eq!(snapshot_mac.len(), 32); assert_eq!(patch_mac.len(), 32);
let mut altered_patch = patch.clone();
if let Some(rec) = altered_patch.mutations[0].record.as_option_mut()
&& let Some(val) = rec.value.as_option_mut()
&& let Some(blob) = val.blob.as_mut()
{
let last = blob.len() - 1;
blob[last] ^= 0x55; }
let altered_patch_mac = generate_patch_mac(
&altered_patch,
"regular_high",
&keys.patch_mac,
state.version,
);
assert_ne!(
patch_mac, altered_patch_mac,
"patch MAC must change if a value MAC mutates"
);
}