use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, ValueEnum};
use openpgp_card::ocard::KeyType;
use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion};
use crate::ENTER_USER_PIN;
use crate::{output, pick_card_for_reading, util};
#[derive(Parser, Debug)]
pub struct AttestationCommand {
#[command(subcommand)]
pub cmd: AttSubCommand,
}
#[derive(Parser, Debug)]
pub enum AttSubCommand {
Cert {
#[arg(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
ident: Option<String>,
},
Generate {
#[arg(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
ident: String,
#[arg(name = "Key slot", short = 'k', long = "key", value_enum)]
key: BaseKeySlot,
#[arg(
name = "User PIN file",
short = 'p',
long = "user-pin",
help = "Optionally, get User PIN from a file"
)]
user_pin: Option<PathBuf>,
},
Statement {
#[arg(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to reset"
)]
ident: Option<String>,
#[arg(name = "Key slot", short = 'k', long = "key", value_enum)]
key: BaseKeySlot,
},
}
#[derive(ValueEnum, Debug, Clone)]
#[value(rename_all = "UPPER")]
pub enum BaseKeySlot {
Sig,
Dec,
Aut,
}
impl From<BaseKeySlot> for KeyType {
fn from(ks: BaseKeySlot) -> Self {
match ks {
BaseKeySlot::Sig => KeyType::Signing,
BaseKeySlot::Dec => KeyType::Decryption,
BaseKeySlot::Aut => KeyType::Authentication,
}
}
}
pub fn attestation(
output_format: OutputFormat,
output_version: OutputVersion,
command: AttestationCommand,
) -> Result<(), Box<dyn std::error::Error>> {
match command.cmd {
AttSubCommand::Cert { ident } => cert(output_format, output_version, ident),
AttSubCommand::Generate {
ident,
key,
user_pin,
} => generate(&ident, key, user_pin),
AttSubCommand::Statement { ident, key } => statement(ident, key),
}
}
fn cert(
output_format: OutputFormat,
output_version: OutputVersion,
ident: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut output = output::AttestationCert::default();
let mut open = pick_card_for_reading(ident)?;
let mut card = open.transaction()?;
output.ident(card.application_identifier()?.ident());
if let Ok(ac) = card.attestation_certificate() {
let pem = util::pem_encode(ac);
output.attestation_cert(pem);
}
println!("{}", output.print(output_format, output_version)?);
Ok(())
}
fn generate(
ident: &str,
key: BaseKeySlot,
user_pin: Option<PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut open = util::open_card(ident)?;
let mut card = open.transaction()?;
let user_pin = util::get_pin(&mut card, user_pin, ENTER_USER_PIN)?;
let mut sign = util::verify_to_sign(&mut card, user_pin)?;
let kt = KeyType::from(key);
sign.generate_attestation(kt, &|| {
eprintln!("Touch confirmation needed to generate an attestation")
})?;
Ok(())
}
fn statement(ident: Option<String>, key: BaseKeySlot) -> Result<(), Box<dyn std::error::Error>> {
let mut open = pick_card_for_reading(ident)?;
let mut card = open.transaction()?;
match key {
BaseKeySlot::Aut => card.select_data(0, &[0x7F, 0x21])?,
BaseKeySlot::Dec => card.select_data(1, &[0x7F, 0x21])?,
BaseKeySlot::Sig => card.select_data(2, &[0x7F, 0x21])?,
};
let cert = card.cardholder_certificate()?;
if !cert.is_empty() {
let pem = util::pem_encode(cert);
println!("{pem}");
} else {
eprintln!("Cardholder certificate slot is empty");
}
Ok(())
}