//! MS Container Map Records //! //! These appear(?) to be defined in Microsoft's Smart Card Minidriver Specification: //! // Adapted from yubico-piv-tool: // // // Copyright (c) 2014-2016 Yubico AB // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{error::Error, key::SlotId, serialization::*, yubikey::YubiKey, CB_OBJ_MAX}; use log::error; use std::{ convert::{TryFrom, TryInto}, fmt::{self, Debug}, }; /// Container name length const CONTAINER_NAME_LEN: usize = 40; /// Container record length: 27 = 80 + 1 + 1 + 2 + 1 + 1 + 1 + 20 const CONTAINER_REC_LEN: usize = (2 * CONTAINER_NAME_LEN) + 27; const OBJ_MSCMAP: u32 = 0x005f_ff10; const TAG_MSCMAP: u8 = 0x81; /// MS Container Map(?) Records #[derive(Copy, Clone)] pub struct Container { /// Container name pub name: [u16; CONTAINER_NAME_LEN], /// Card slot pub slot: SlotId, /// Key spec pub key_spec: u8, /// Key size in bits pub key_size_bits: u16, /// Flags pub flags: u8, /// PIN ID pub pin_id: u8, /// Associated ECHD(?) container (typo of "ecdh" perhaps?) pub associated_echd_container: u8, /// Cert fingerprint pub cert_fingerprint: [u8; 20], } impl Container { /// Read MS Container Map records pub fn read_mscmap(yubikey: &mut YubiKey) -> Result, Error> { 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(_) => { // TODO(tarcieri): is this really OK? return Ok(containers); } }; if tlv.tag != TAG_MSCMAP { // TODO(tarcieri): yubico-piv-tool returned success here? should we? return Err(Error::InvalidObject); } for chunk in tlv.value.chunks_exact(CONTAINER_REC_LEN) { containers.push(Container::new(chunk)?); } Ok(containers) } /// Write MS Container Map records. pub fn write_mscmap(yubikey: &mut YubiKey, containers: &[Self]) -> Result<(), Error> { let n_containers = containers.len(); let data_len = n_containers * CONTAINER_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(CONTAINER_REC_LEN).enumerate() { chunk.copy_from_slice(&containers[i].to_bytes()); } })?; txn.save_object(OBJ_MSCMAP, &buf[..offset]) } /// Parse a container record from a byte slice pub fn new(bytes: &[u8]) -> Result { if bytes.len() != CONTAINER_REC_LEN { error!( "couldn't parse PIV container: expected {}-bytes, got {}-bytes", CONTAINER_REC_LEN, bytes.len() ); return Err(Error::ParseError); } let mut name = [0u16; CONTAINER_NAME_LEN]; let name_bytes_len = CONTAINER_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(Container { 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, }) } /// Parse the container name as a UTF-16 string pub fn parse_name(&self) -> Result { String::from_utf16(&self.name).map_err(|_| Error::ParseError) } /// Serialize a container record as a byte size pub fn to_bytes(&self) -> [u8; CONTAINER_REC_LEN] { let mut bytes = Vec::with_capacity(CONTAINER_REC_LEN); for i in 0..CONTAINER_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); // TODO(tarcieri): use TryInto here when const generics are available let mut result = [0u8; CONTAINER_REC_LEN]; result.copy_from_slice(&bytes); result } } impl Debug for Container { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "PivContainer {{ name: {:?}, slot: {:?}, key_spec: {}, key_size_bits: {}, \ flags: {}, pin_id: {}, associated_echd_container: {}, cert_fingerprint: {:?} }}", &self.name[..], self.slot, self.key_spec, self.key_size_bits, self.flags, self.pin_id, self.associated_echd_container, &self.cert_fingerprint[..] ) } } impl<'a> TryFrom<&'a [u8]> for Container { type Error = Error; fn try_from(bytes: &'a [u8]) -> Result { Self::new(bytes) } }