opgpcard: Extract attestation command into module

This commit is contained in:
Nora Widdecke 2022-10-26 14:26:04 +02:00
parent 25ae73711d
commit 1be21cfc7f
No known key found for this signature in database
GPG key ID: 2D4111B31DBB99B6
4 changed files with 172 additions and 133 deletions

View file

@ -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<String>,
},
/// 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<PathBuf>,
},
/// 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<String>,
#[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 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 {

View file

@ -0,0 +1,167 @@
// 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::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<String>,
},
/// 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<PathBuf>,
},
/// 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<String>,
#[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 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<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 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<PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
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<String>, key: BaseKeySlot) -> Result<(), Box<dyn std::error::Error>> {
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(())
}

View file

@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: 2022 Nora Widdecke <mail@nora.pink>
// SPDX-License-Identifier: MIT OR Apache-2.0
pub mod attestation;
pub mod decrypt;
pub mod factory_reset;
pub mod info;

View file

@ -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<dyn std::error::Error>> {
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)?;
}