diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 8e0a2f8..25f2ff4 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -75,10 +75,7 @@ pub enum Command { Sign(commands::sign::SignCommand), /// Attestation management (Yubico) - Attestation { - #[clap(subcommand)] - cmd: AttCommand, - }, + Attestation(commands::attestation::AttestationCommand), /// Completely reset a card (deletes all data, including the keys on the card!) FactoryReset(commands::factory_reset::FactoryResetCommand), @@ -149,56 +146,6 @@ pub enum AdminCommand { }, } -#[derive(Parser, Debug)] -pub enum AttCommand { - /// Print the card's "Attestation Certificate" - Cert { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - }, - - /// Generate "Attestation Statement" for one of the key slots on the card - Generate { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - - #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] - key: BaseKeySlot, - - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] - user_pin: Option, - }, - - /// Print a "cardholder certificate" from the card. - /// This shows the "Attestation Statement", if one has been generated. - Statement { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - - #[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 openpgp_card_sequoia::types::KeyType { - fn from(ks: BaseKeySlot) -> Self { - use openpgp_card_sequoia::types::KeyType; - match ks { - BaseKeySlot::Sig => KeyType::Signing, - BaseKeySlot::Dec => KeyType::Decryption, - BaseKeySlot::Aut => KeyType::Authentication, - } - } -} - #[derive(ValueEnum, Debug, Clone)] #[clap(rename_all = "UPPER")] pub enum BasePlusAttKeySlot { diff --git a/tools/src/bin/opgpcard/commands/attestation.rs b/tools/src/bin/opgpcard/commands/attestation.rs new file mode 100644 index 0000000..a6bae12 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/attestation.rs @@ -0,0 +1,167 @@ +// 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::card::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" + Cert { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + }, + + /// Generate "Attestation Statement" for one of the key slots on the card + Generate { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: String, + + #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] + key: BaseKeySlot, + + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + user_pin: Option, + }, + + /// Print a "cardholder certificate" from the card. + /// This shows the "Attestation Statement", if one has been generated. + Statement { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + + #[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 openpgp_card_sequoia::types::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 card = Card::new(backend); + let mut open = card.transaction()?; + + output.ident(open.application_identifier()?.ident()); + + if let Ok(ac) = open.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 card = Card::new(backend); + let mut open = card.transaction()?; + + let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); + + let mut sign = util::verify_to_sign(&mut open, 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 card = Card::new(backend); + let mut open = card.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) = open.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 => open.select_data(0, &[0x7F, 0x21], select_data_workaround)?, + BaseKeySlot::Dec => open.select_data(1, &[0x7F, 0x21], select_data_workaround)?, + BaseKeySlot::Sig => open.select_data(2, &[0x7F, 0x21], select_data_workaround)?, + }; + + // Get DO "cardholder certificate" (returns the slot that was previously selected) + let cert = open.cardholder_certificate()?; + + if !cert.is_empty() { + let pem = util::pem_encode(cert); + println!("{}", pem); + } else { + println!("Cardholder certificate slot is empty"); + } + Ok(()) +} diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index be1cbe3..b2027f8 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 +pub mod attestation; pub mod decrypt; pub mod factory_reset; pub mod info; diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index fd0e381..bedda34 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -4,7 +4,6 @@ use anyhow::{anyhow, Result}; use clap::Parser; -use cli::BaseKeySlot; use std::path::PathBuf; use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; @@ -68,84 +67,9 @@ fn main() -> Result<(), Box> { cli::Command::Sign(cmd) => { commands::sign::sign(cmd)?; } - cli::Command::Attestation { cmd } => match cmd { - cli::AttCommand::Cert { ident } => { - let mut output = output::AttestationCert::default(); - - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - output.ident(open.application_identifier()?.ident()); - - if let Ok(ac) = open.attestation_certificate() { - let pem = util::pem_encode(ac); - output.attestation_cert(pem); - } - - println!("{}", output.print(cli.output_format, cli.output_version)?); - } - cli::AttCommand::Generate { - ident, - key, - user_pin, - } => { - let backend = util::open_card(&ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); - - let mut sign = util::verify_to_sign(&mut open, user_pin.as_deref())?; - - let kt = KeyType::from(key); - sign.generate_attestation(kt, &|| { - println!("Touch confirmation needed to generate an attestation") - })?; - } - cli::AttCommand::Statement { ident, key } => { - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.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) = open.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 => { - open.select_data(0, &[0x7F, 0x21], select_data_workaround)? - } - BaseKeySlot::Dec => { - open.select_data(1, &[0x7F, 0x21], select_data_workaround)? - } - BaseKeySlot::Sig => { - open.select_data(2, &[0x7F, 0x21], select_data_workaround)? - } - }; - - // Get DO "cardholder certificate" (returns the slot that was previously selected) - let cert = open.cardholder_certificate()?; - - if !cert.is_empty() { - let pem = util::pem_encode(cert); - println!("{}", pem); - } else { - println!("Cardholder certificate slot is empty"); - } - } - }, + cli::Command::Attestation(cmd) => { + commands::attestation::attestation(cli.output_format, cli.output_version, cmd)?; + } cli::Command::FactoryReset(cmd) => { commands::factory_reset::factory_reset(cmd)?; }