use crate::{consts::CB_OBJ_MAX, piv::SlotId, serialization::*, Error, Result, YubiKey};
use log::error;
const OBJ_MSCMAP: u32 = 0x005f_ff10;
const TAG_MSCMAP: u8 = 0x81;
#[cfg_attr(docsrs, doc(cfg(feature = "untested")))]
#[derive(Clone, Debug)]
pub struct MsContainer {
pub name: [u16; Self::NAME_LEN],
pub slot: SlotId,
pub key_spec: u8,
pub key_size_bits: u16,
pub flags: u8,
pub pin_id: u8,
pub associated_echd_container: u8,
pub cert_fingerprint: [u8; Self::CERT_FINGERPRINT_LEN],
}
impl MsContainer {
const NAME_LEN: usize = 40;
const REC_LEN: usize = (2 * Self::NAME_LEN) + 27;
const CERT_FINGERPRINT_LEN: usize = 20;
pub fn read_mscmap(yubikey: &mut YubiKey) -> Result<Vec<Self>> {
let txn = yubikey.begin_transaction()?;
let response = txn.fetch_object(OBJ_MSCMAP)?;
let mut containers = vec![];
let (_, tlv) = match Tlv::parse(&response) {
Ok(res) => res,
Err(_) => {
return Ok(containers);
}
};
if tlv.tag != TAG_MSCMAP {
return Err(Error::InvalidObject);
}
for chunk in tlv.value.chunks_exact(Self::REC_LEN) {
containers.push(MsContainer::new(chunk)?);
}
Ok(containers)
}
pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<()> {
let n_containers = containers.len();
let data_len = n_containers * Self::REC_LEN;
let txn = yubikey.begin_transaction()?;
if n_containers == 0 {
return txn.save_object(OBJ_MSCMAP, &[]);
}
let mut buf = [0u8; CB_OBJ_MAX];
let offset = Tlv::write_as(&mut buf, TAG_MSCMAP, data_len, |buf| {
for (i, chunk) in buf.chunks_exact_mut(Self::REC_LEN).enumerate() {
chunk.copy_from_slice(&containers[i].to_bytes());
}
})?;
txn.save_object(OBJ_MSCMAP, &buf[..offset])
}
pub fn new(bytes: &[u8]) -> Result<Self> {
if bytes.len() != Self::REC_LEN {
error!(
"couldn't parse PIV container: expected {}-bytes, got {}-bytes",
Self::REC_LEN,
bytes.len()
);
return Err(Error::ParseError);
}
let mut name = [0u16; Self::NAME_LEN];
let name_bytes_len = Self::NAME_LEN * 2;
for (i, chunk) in bytes[..name_bytes_len].chunks_exact(2).enumerate() {
name[i] = u16::from_le_bytes(chunk.try_into().unwrap());
}
let mut cert_fingerprint = [0u8; 20];
cert_fingerprint.copy_from_slice(&bytes[(bytes.len() - 20)..]);
Ok(Self {
name,
slot: bytes[name_bytes_len].try_into()?,
key_spec: bytes[name_bytes_len + 1],
key_size_bits: u16::from_le_bytes(
bytes[(name_bytes_len + 2)..(name_bytes_len + 4)]
.try_into()
.unwrap(),
),
flags: bytes[name_bytes_len + 4],
pin_id: bytes[name_bytes_len + 5],
associated_echd_container: bytes[name_bytes_len + 6],
cert_fingerprint,
})
}
pub fn parse_name(&self) -> Result<String> {
String::from_utf16(&self.name).map_err(|_| Error::ParseError)
}
pub fn to_bytes(&self) -> [u8; Self::REC_LEN] {
let mut bytes = Vec::with_capacity(Self::REC_LEN);
for i in 0..Self::NAME_LEN {
bytes.extend_from_slice(&self.name[i].to_le_bytes());
}
bytes.push(self.slot.into());
bytes.push(self.key_spec);
bytes.extend_from_slice(&self.key_size_bits.to_le_bytes());
bytes.push(self.flags);
bytes.push(self.pin_id);
bytes.push(self.associated_echd_container);
bytes.extend_from_slice(&self.cert_fingerprint);
bytes.as_slice().try_into().unwrap()
}
}
impl<'a> TryFrom<&'a [u8]> for MsContainer {
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self> {
Self::new(bytes)
}
}