pub mod emulated;
pub mod hardware;
pub mod lock;
pub mod mgmt;
pub mod session;
pub(crate) mod tlv;
pub mod virtual_piv;
pub use lock::{set_wait_notice, CardLock, CardScheduler};
pub use mgmt::MgmtAlgo;
pub use virtual_piv::{Fault, VirtualPiv};
use anyhow::Result;
pub trait FlashHandle: Send {}
pub struct NoopFlash;
impl FlashHandle for NoopFlash {}
#[derive(Debug, Clone)]
pub struct DeviceInfo {
pub serial: u32,
pub version: String,
pub reader: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PinRef {
Pin,
Puk,
}
impl PinRef {
pub fn reference(self) -> u8 {
match self {
Self::Pin => 0x80,
Self::Puk => 0x81,
}
}
}
impl std::fmt::Display for PinRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Pin => "PIN",
Self::Puk => "PUK",
})
}
}
pub trait PivBackend: Send + Sync {
fn list_readers(&self) -> Result<Vec<String>>;
fn list_devices(&self) -> Result<Vec<DeviceInfo>>;
fn exclusive(&self, reader: &str) -> Result<CardLock<'_>> {
let _ = reader;
Ok(CardLock::none())
}
fn read_object(&self, reader: &str, id: u32) -> Result<Vec<u8>>;
fn write_object(&self, reader: &str, id: u32, data: &[u8], management_key: &str) -> Result<()>;
fn authenticate_management_key(&self, reader: &str, management_key: &str) -> Result<()>;
fn verify_pin(&self, reader: &str, pin: &str) -> Result<()>;
fn send_apdu(&self, reader: &str, apdu: &[u8]) -> Result<Vec<u8>>;
fn ecdh(&self, reader: &str, slot: u8, peer_point: &[u8], pin: Option<&str>)
-> Result<Vec<u8>>;
fn ecdsa_sign(
&self,
reader: &str,
slot: u8,
digest: &[u8],
pin: Option<&str>,
) -> Result<[u8; 64]> {
let _ = (reader, slot, digest, pin);
anyhow::bail!("ecdsa_sign not implemented for this backend")
}
fn read_certificate(&self, reader: &str, slot: u8) -> Result<Vec<u8>>;
fn generate_key(&self, reader: &str, slot: u8, management_key: Option<&str>)
-> Result<Vec<u8>>;
fn generate_certificate(
&self,
reader: &str,
slot: u8,
subject: &str,
management_key: &str,
pin: Option<&str>,
) -> Result<Vec<u8>>;
fn read_printed_object_with_pin(&self, reader: &str, pin: &str) -> Result<Vec<u8>>;
fn management_key_algorithm(&self, reader: &str) -> Result<MgmtAlgo> {
let _ = reader;
Ok(MgmtAlgo::Tdes)
}
fn set_management_key(
&self,
reader: &str,
old_key_hex: &str,
new_key_hex: &str,
algo: MgmtAlgo,
) -> Result<()>;
fn change_reference(&self, reader: &str, which: PinRef, old: &str, new: &str) -> Result<()> {
let _ = (reader, which, old, new);
anyhow::bail!("changing the {which} is not implemented for this backend")
}
fn object_size(&self, reader: &str, id: u32) -> Result<Option<usize>> {
match self.read_object(reader, id) {
Ok(data) => Ok(Some(data.len())),
Err(_) => Ok(None),
}
}
fn save_fixture(&self, _path: &std::path::Path) -> Result<()> {
Ok(())
}
fn load_fixture(&self, _path: &std::path::Path) -> Result<()> {
Ok(())
}
fn reset_piv(&self, reader: &str) -> Result<()> {
block_reference(|wrong| self.verify_pin(reader, wrong))?;
block_reference(|wrong| self.change_reference(reader, PinRef::Puk, wrong, wrong))?;
self.reset_blocked_piv(reader)
}
fn reset_blocked_piv(&self, reader: &str) -> Result<()> {
self.send_apdu(reader, &[0x00, 0xFB, 0x00, 0x00])
.map(|_| ())
}
fn start_flash(&self, _reader: &str, _on_ms: u64, _off_ms: u64) -> Box<dyn FlashHandle> {
Box::new(NoopFlash)
}
}
pub fn fixture_path() -> Option<std::path::PathBuf> {
fixture_path_from(std::env::var_os("YB_FIXTURE"))
}
fn fixture_path_from(var: Option<std::ffi::OsString>) -> Option<std::path::PathBuf> {
if cfg!(feature = "test-fixture") {
var.map(std::path::PathBuf::from)
} else {
None
}
}
pub fn open_backend() -> Result<std::sync::Arc<dyn PivBackend>> {
#[cfg(feature = "test-fixture")]
if let Some(path) = fixture_path() {
return Ok(std::sync::Arc::new(VirtualPiv::from_fixture(&path)?));
}
Ok(std::sync::Arc::new(hardware::HardwarePiv::new()))
}
fn block_reference(mut attempt: impl FnMut(&str) -> Result<()>) -> Result<()> {
const WRONG: [&str; 3] = ["00000000", "99999999", "13579246"];
let mut wrong = 0;
for _ in 0..(256 + WRONG.len()) {
let Some(value) = WRONG.get(wrong) else { break };
match attempt(value) {
Ok(()) => wrong += 1,
Err(e) => match e.downcast_ref::<crate::errors::CardError>() {
Some(crate::errors::CardError::Status { sw: 0x6983, .. }) => return Ok(()),
Some(crate::errors::CardError::Status { sw, .. }) if sw & 0xFFF0 == 0x63C0 => {}
_ => return Err(e),
},
}
}
anyhow::bail!("could not block the PIN or PUK before the PIV reset")
}
#[cfg(test)]
mod fixture_tests {
#[test]
fn fixture_path_follows_the_feature() {
let got = super::fixture_path_from(Some("card.yaml".into()));
assert_eq!(got.is_some(), cfg!(feature = "test-fixture"));
assert_eq!(super::fixture_path_from(None), None);
}
}