opgpcard: Extract attestation command into module
This commit is contained in:
parent
25ae73711d
commit
1be21cfc7f
4 changed files with 172 additions and 133 deletions
|
@ -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 {
|
||||
|
|
167
tools/src/bin/opgpcard/commands/attestation.rs
Normal file
167
tools/src/bin/opgpcard/commands/attestation.rs
Normal 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(())
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
cli::Command::Attestation(cmd) => {
|
||||
commands::attestation::attestation(cli.output_format, cli.output_version, cmd)?;
|
||||
}
|
||||
|
||||
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::FactoryReset(cmd) => {
|
||||
commands::factory_reset::factory_reset(cmd)?;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue