// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-FileCopyrightText: 2022 Lars Wirzenius // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 use std::path::PathBuf; use anyhow::Result; use clap::{Parser, ValueEnum}; use openpgp_card_sequoia::{state::Open, Card}; use openpgp_card_sequoia::types::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 { #[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, }, /// 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, }, /// 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, /// 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 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> { 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, ) -> Result<(), Box> { let mut output = output::AttestationCert::default(); let backend = pick_card_for_reading(ident)?; let mut open: Card = 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, ) -> Result<(), Box> { let backend = util::open_card(ident)?; let mut open: Card = 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, key: BaseKeySlot) -> Result<(), Box> { let backend = pick_card_for_reading(ident)?; let mut open: Card = 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(()) }