use super::transport::PcscSession;
use crate::errors::{CardError, CardOp};
use crate::piv::mgmt::{parse_mgmt_metadata, MgmtAlgo, GET_METADATA_MGMT};
use crate::piv::tlv::{crypto_ecb, encode_length, encode_tlv, EcbDir};
use crate::piv::PinRef;
use anyhow::{bail, Context, Result};
use std::sync::Once;
use subtle::ConstantTimeEq;
use super::crypto::tlv_get;
fn pad_pin(value: &str, which: PinRef) -> Result<[u8; 8]> {
let bytes = value.as_bytes();
if bytes.len() > 8 {
bail!("{which} too long (max 8 bytes)");
}
let mut padded = [0xFFu8; 8];
padded[..bytes.len()].copy_from_slice(bytes);
Ok(padded)
}
static TOUCH_WARNING: Once = Once::new();
impl PcscSession {
pub(crate) fn verify_pin(&mut self, pin: &str) -> Result<()> {
let mut apdu = vec![0x00, 0x20, 0x00, 0x80, 0x08];
apdu.extend_from_slice(&pad_pin(pin, PinRef::Pin)?);
self.transmit_check(&apdu, CardOp::VerifyPin)?;
Ok(())
}
pub(crate) fn change_reference(&mut self, which: PinRef, old: &str, new: &str) -> Result<()> {
let op = match which {
PinRef::Pin => CardOp::ChangePin,
PinRef::Puk => CardOp::ChangePuk,
};
let mut apdu = vec![0x00, 0x24, 0x00, which.reference(), 0x10];
apdu.extend_from_slice(&pad_pin(old, which)?);
apdu.extend_from_slice(&pad_pin(new, which)?);
self.transmit_check(&apdu, op)?;
Ok(())
}
pub(crate) fn management_key_algorithm(&mut self) -> Result<MgmtAlgo> {
if let Some(algo) = self.mgmt_algo {
return Ok(algo);
}
let op = CardOp::GetMetadata(0x9B);
let resp = self.transmit_raw(&GET_METADATA_MGMT)?;
let n = resp.len();
if n < 2 {
return Err(CardError::protocol(op, "response too short").into());
}
let algo = match (resp[n - 2], resp[n - 1]) {
(0x90, 0x00) => {
let md = parse_mgmt_metadata(&resp[..n - 2])?;
if md.touch_required() {
TOUCH_WARNING.call_once(|| {
eprintln!(
"Warning: this YubiKey's management key requires touch; \
touch the key when it blinks during write operations."
);
});
}
md.algo
}
(0x6D, 0x00) => MgmtAlgo::Tdes,
(s1, s2) => return Err(self.status_error(op, s1, s2).into()),
};
self.mgmt_algo = Some(algo);
Ok(algo)
}
pub(crate) fn set_cached_management_key_algorithm(&mut self, algo: MgmtAlgo) {
self.mgmt_algo = Some(algo);
}
pub(crate) fn authenticate_management_key(&mut self, key_hex: &str) -> Result<()> {
let key_bytes = hex::decode(key_hex).context("decoding management key")?;
let algo = self.management_key_algorithm()?;
algo.check_key_len(&key_bytes)?;
self.mutual_auth(algo, &key_bytes)
}
fn mutual_auth(&mut self, algo: MgmtAlgo, key_bytes: &[u8]) -> Result<()> {
let p1 = algo.id();
let block_size = algo.block_size();
let step1 = [0x00, 0x87, p1, 0x9B, 0x04, 0x7C, 0x02, 0x80, 0x00];
let resp1 = self.transmit_check(&step1, CardOp::MgmtAuth)?;
let outer = tlv_get(&resp1, 0x7C, "MGMT AUTH step1")?;
let witness_enc = tlv_get(&outer, 0x80, "MGMT AUTH step1")?;
let witness_dec = crypto_ecb(algo, key_bytes, &witness_enc, EcbDir::Decrypt)?;
let challenge: Vec<u8> = (0..block_size).map(|_| rand::random::<u8>()).collect();
let mut inner2 = encode_tlv(0x80, &witness_dec);
inner2.extend(encode_tlv(0x81, &challenge));
let outer2 = encode_tlv(0x7C, &inner2);
let mut step2 = vec![0x00, 0x87, p1, 0x9B];
step2.extend(encode_length(outer2.len()));
step2.extend(&outer2);
let resp2 = self.transmit_check(&step2, CardOp::MgmtAuth)?;
let outer_r = tlv_get(&resp2, 0x7C, "MGMT AUTH step2")?;
let challenge_resp = tlv_get(&outer_r, 0x82, "MGMT AUTH step2")?;
let challenge_enc = crypto_ecb(algo, key_bytes, &challenge, EcbDir::Encrypt)?;
if challenge_enc.ct_eq(&challenge_resp).unwrap_u8() == 0 {
return Err(CardError::protocol(CardOp::MgmtAuth, "card response mismatch").into());
}
Ok(())
}
}