use super::{open_aes_256_gcm, RequestError};
use crate::recovery_key_store;
use alloc::{collections::btree_map::BTreeMap, vec::Vec};
use cbor::{cbor, MapKey, Value};
pub(crate) const KEY_PURPOSE_PIN_DATA_KEY: &[u8] =
b"KeychainApplicationKey:chrome:GPM PIN data wrapping key";
#[derive(PartialEq, Debug)]
pub(crate) struct VaultCohortDetails {
pub cert_xml_serial_number: i64,
pub cohort_public_key: Vec<u8>,
}
impl TryFrom<&alloc::collections::BTreeMap<cbor::MapKey, cbor::Value>> for VaultCohortDetails {
type Error = ();
fn try_from(
map: &alloc::collections::BTreeMap<cbor::MapKey, cbor::Value>,
) -> Result<VaultCohortDetails, Self::Error> {
let Some(Value::Int(cert_xml_serial_number)) = map.get(&MapKey::Int(6)) else {
return Err(());
};
let Some(Value::Bytestring(cohort_public_key)) = map.get(&MapKey::Int(7)) else {
return Err(());
};
Ok(VaultCohortDetails {
cert_xml_serial_number: *cert_xml_serial_number,
cohort_public_key: cohort_public_key.to_vec(),
})
}
}
#[derive(PartialEq, Debug)]
pub(crate) struct Data {
pub pin_hash: [u8; 32],
pub claim_key: [u8; 32],
pub counter_id: [u8; recovery_key_store::COUNTER_ID_LEN],
pub vault_handle_without_type: [u8; recovery_key_store::VAULT_HANDLE_LEN - 1],
pub vault_cohort_details: Option<VaultCohortDetails>,
}
impl TryFrom<Vec<u8>> for Data {
type Error = ();
fn try_from(pin_data: Vec<u8>) -> Result<Data, Self::Error> {
let parsed = cbor::parse(pin_data).map_err(|_| ())?;
let Value::Map(map) = parsed else {
return Err(());
};
let Some(Value::Bytestring(pin_hash)) = map.get(&MapKey::Int(1)) else {
return Err(());
};
let Some(Value::Bytestring(claim_key)) = map.get(&MapKey::Int(3)) else {
return Err(());
};
let Some(Value::Bytestring(counter_id)) = map.get(&MapKey::Int(4)) else {
return Err(());
};
let Some(Value::Bytestring(vault_handle)) = map.get(&MapKey::Int(5)) else {
return Err(());
};
let vault_cohort_details = match VaultCohortDetails::try_from(&map) {
Ok(result) => Some(result),
Err(_) => None,
};
Ok(Data {
pin_hash: pin_hash.as_ref().try_into().map_err(|_| ())?,
claim_key: claim_key.as_ref().try_into().map_err(|_| ())?,
counter_id: counter_id.as_ref().try_into().map_err(|_| ())?,
vault_handle_without_type: vault_handle.as_ref().try_into().map_err(|_| ())?,
vault_cohort_details,
})
}
}
impl Data {
#[allow(unused_parens)]
pub fn to_bytes(&self) -> Vec<u8> {
let mut map: BTreeMap<cbor::MapKey, cbor::Value> = BTreeMap::default();
map.insert(cbor::MapKey::Int(1), cbor!((&self.pin_hash)));
map.insert(cbor::MapKey::Int(3), cbor!((&self.claim_key)));
map.insert(cbor::MapKey::Int(4), cbor!((&self.counter_id)));
map.insert(
cbor::MapKey::Int(5),
cbor!((&self.vault_handle_without_type)),
);
if let Some(vault_cohort_details) = &self.vault_cohort_details {
map.insert(
cbor::MapKey::Int(6),
cbor!((vault_cohort_details.cert_xml_serial_number)),
);
map.insert(
cbor::MapKey::Int(7),
cbor!((vault_cohort_details.cohort_public_key.clone())),
);
}
cbor::Value::from(map).to_bytes()
}
pub fn from_wrapped(
wrapped_pin_data: &[u8],
security_domain_secret: &[u8],
) -> Result<Data, RequestError> {
let mut pin_data_key = [0u8; 32];
crypto::hkdf_sha256(
security_domain_secret,
&[],
KEY_PURPOSE_PIN_DATA_KEY,
&mut pin_data_key,
)
.unwrap();
open_aes_256_gcm(&pin_data_key, wrapped_pin_data, &[])
.ok_or(RequestError::Debug("PIN data decryption failed"))?
.try_into()
.map_err(|_| RequestError::Debug("invalid PIN data"))
}
pub fn encrypt(&self, security_domain_secret: &[u8]) -> Vec<u8> {
let mut pin_data_key = [0u8; 32];
crypto::hkdf_sha256(
security_domain_secret,
&[],
KEY_PURPOSE_PIN_DATA_KEY,
&mut pin_data_key,
)
.unwrap();
let mut nonce = [0u8; crypto::NONCE_LEN];
crypto::rand_bytes(&mut nonce);
let mut serialized = self.to_bytes();
crypto::aes_256_gcm_seal_in_place(&pin_data_key, &nonce, &[], &mut serialized);
[nonce.as_ref(), &serialized].concat()
}
}