openpgp-card/tools/src/bin/opgpcard/commands/attestation.rs

195 lines
5.8 KiB
Rust

// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
// SPDX-FileCopyrightText: 2022 Lars Wirzenius <liw@liw.fi>
// SPDX-FileCopyrightText: 2022 Nora Widdecke <mail@nora.pink>
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, ValueEnum};
use openpgp_card_sequoia::types::KeyType;
use openpgp_card_sequoia::{state::Open, Card};
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 {
#[clap(subcommand)]
pub cmd: AttSubCommand,
}
#[derive(Parser, Debug)]
pub enum AttSubCommand {
/// Print the card's attestation certificate
///
/// New YubiKeys are preloaded with an attestation certificate issued by the Yubico CA.
Cert {
#[clap(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
ident: Option<String>,
},
/// Generate attestation statement for one of the key slots on the card
///
/// An attestation statement can only be generated for key slots that contain keys that were
/// generated by the card. See 'opgpcard admin generate' and 'opgpcard status -v'.
Generate {
#[clap(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to use"
)]
ident: String,
/// Key slot to use
#[clap(name = "Key slot", short = 'k', long = "key", value_enum)]
key: BaseKeySlot,
#[clap(
name = "User PIN file",
short = 'p',
long = "user-pin",
help = "Optionally, get User PIN from a file"
)]
user_pin: Option<PathBuf>,
},
/// Print the attestation statement for one of the key slots on the card
///
/// An attestation statement can only be printed after generating it. See 'opgpcard attestation
/// generate'.
Statement {
#[clap(
name = "card ident",
short = 'c',
long = "card",
help = "Identifier of the card to reset"
)]
ident: Option<String>,
/// Key slot to use
#[clap(name = "Key slot", short = 'k', long = "key", value_enum)]
key: BaseKeySlot,
},
}
#[derive(ValueEnum, Debug, Clone)]
#[clap(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 backend = pick_card_for_reading(ident)?;
let mut open: Card<Open> = backend.into();
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 backend = util::open_card(ident)?;
let mut open: Card<Open> = backend.into();
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.as_deref())?;
let kt = KeyType::from(key);
sign.generate_attestation(kt, &|| {
println!("Touch confirmation needed to generate an attestation")
})?;
Ok(())
}
fn statement(ident: Option<String>, key: BaseKeySlot) -> Result<(), Box<dyn std::error::Error>> {
let backend = pick_card_for_reading(ident)?;
let mut open: Card<Open> = backend.into();
let mut card = open.transaction()?;
// Get cardholder certificate from card.
let mut select_data_workaround = false;
// Use "select data" workaround if the card reports a
// yk firmware version number >= 5 and <= 5.4.3
if let Ok(version) = card.firmware_version() {
if version.len() == 3
&& version[0] == 5
&& (version[1] < 4 || (version[1] == 4 && version[2] <= 3))
{
select_data_workaround = true;
}
}
// Select cardholder certificate
match key {
BaseKeySlot::Aut => card.select_data(0, &[0x7F, 0x21], select_data_workaround)?,
BaseKeySlot::Dec => card.select_data(1, &[0x7F, 0x21], select_data_workaround)?,
BaseKeySlot::Sig => card.select_data(2, &[0x7F, 0x21], select_data_workaround)?,
};
// Get DO "cardholder certificate" (returns the slot that was previously selected)
let cert = card.cardholder_certificate()?;
if !cert.is_empty() {
let pem = util::pem_encode(cert);
println!("{}", pem);
} else {
println!("Cardholder certificate slot is empty");
}
Ok(())
}