use anyhow::{anyhow, Result};
use card_backend_pcsc::PcscBackend;
use clap::{Parser, ValueEnum};
use openpgp_card::ocard::data::KdfDo;
use openpgp_card::Error;
use crate::util;
#[derive(Parser, Debug)]
pub struct SystemCommand {
#[command(subcommand)]
pub cmd: SystemSubCommand,
}
#[derive(Parser, Debug)]
pub enum SystemSubCommand {
FactoryReset {
#[arg(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
ident: String,
},
KdfSetup {
#[arg(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
ident: String,
},
SetIdentity {
#[arg(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
ident: String,
#[arg(name = "identity", value_enum)]
id: SetIdentityId,
},
ActivateFile {},
}
pub fn system(command: SystemCommand) -> Result<(), Box<dyn std::error::Error>> {
match command.cmd {
SystemSubCommand::FactoryReset { ident } => factory_reset(&ident)?,
SystemSubCommand::KdfSetup { ident } => kdf_setup(&ident)?,
SystemSubCommand::SetIdentity { ident, id } => set_identity(&ident, id)?,
SystemSubCommand::ActivateFile {} => activate()?,
}
Ok(())
}
pub fn factory_reset(ident: &str) -> Result<()> {
eprintln!("Resetting Card {ident}");
let mut open = util::open_card(ident)?;
let mut card = open.transaction()?;
card.factory_reset().map_err(|e| anyhow!(e))
}
pub fn kdf_setup(ident: &str) -> Result<()> {
eprintln!("Setting up KDF mode for {ident}");
let mut open = util::open_card(ident)?;
let mut card = open.transaction()?;
const DEFAULT_PW3: &str = "12345678";
card.verify_admin_pin(DEFAULT_PW3.to_string().into())?;
use rand::Rng;
let mut rng = rand::thread_rng();
let salt_pw1: [u8; 8] = rng.gen();
let salt_rc: [u8; 8] = rng.gen();
let salt_pw3: [u8; 8] = rng.gen();
const HASH_ALGO: u8 = 0x08;
let kdf_do = KdfDo::iter_salted(HASH_ALGO, salt_pw1.into(), salt_rc.into(), salt_pw3.into())?;
card.card().set_kdf_do(&kdf_do)?;
if let Some(pw3) = kdf_do.initial_hash_pw3() {
if card
.card()
.change_pw3(DEFAULT_PW3.as_bytes().to_vec().into(), pw3.to_vec().into())
.is_ok()
{
if let Some(pw1) = kdf_do.initial_hash_pw1() {
card.card()
.reset_retry_counter_pw1(pw1.to_vec().into(), None)?;
}
} else {
log::debug!(
"change_pw3: Failed to align PIN (if the card is a Gnuk, that's expected and ok)"
);
}
}
Ok(())
}
#[derive(ValueEnum, Debug, Clone)]
pub enum SetIdentityId {
#[value(name = "0")]
Zero,
#[value(name = "1")]
One,
#[value(name = "2")]
Two,
}
impl From<SetIdentityId> for u8 {
fn from(id: SetIdentityId) -> Self {
match id {
SetIdentityId::Zero => 0,
SetIdentityId::One => 1,
SetIdentityId::Two => 2,
}
}
}
pub fn set_identity(ident: &str, id: SetIdentityId) -> Result<(), Box<dyn std::error::Error>> {
let mut open = util::open_card(ident)?;
let mut card = open.transaction()?;
card.set_identity(u8::from(id))?;
Ok(())
}
pub fn activate() -> Result<(), Box<dyn std::error::Error>> {
let mut cards: Vec<_> = PcscBackend::card_backends(None)?.collect();
if cards.len() != 1 {
return Err(Error::InternalError(format!(
"This command is only allowed if exactly one card is connected, found {}.",
cards.len()
))
.into());
}
let mut card = cards
.pop()
.ok_or_else(|| anyhow!("There are no more cards"))??;
let mut tx = card.transaction(None)?;
let select_res = tx.transmit(
&[
0x00, 0xa4, 0x04, 0x00, 0x00, 0x00, 0x06, 0xd2, 0x76, 0x00, 0x01, 0x24, 0x01,
],
254,
)?;
if select_res == [0x62, 0x85] {
let activate_res = tx.transmit(&[0x00, 0x44, 0x00, 0x00], 254)?;
if activate_res == [0x90, 0x00] {
Ok(())
} else {
Err(Error::InternalError(format!(
"ACTIVATE FILE failed with status {activate_res:02x?}"
))
.into())
}
} else {
Err(Error::InternalError("Card doesn't appear to be terminated.".to_string()).into())
}
}