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),
|
Sign(commands::sign::SignCommand),
|
||||||
|
|
||||||
/// Attestation management (Yubico)
|
/// Attestation management (Yubico)
|
||||||
Attestation {
|
Attestation(commands::attestation::AttestationCommand),
|
||||||
#[clap(subcommand)]
|
|
||||||
cmd: AttCommand,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Completely reset a card (deletes all data, including the keys on the card!)
|
/// Completely reset a card (deletes all data, including the keys on the card!)
|
||||||
FactoryReset(commands::factory_reset::FactoryResetCommand),
|
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)]
|
#[derive(ValueEnum, Debug, Clone)]
|
||||||
#[clap(rename_all = "UPPER")]
|
#[clap(rename_all = "UPPER")]
|
||||||
pub enum BasePlusAttKeySlot {
|
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-FileCopyrightText: 2022 Nora Widdecke <mail@nora.pink>
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
pub mod attestation;
|
||||||
pub mod decrypt;
|
pub mod decrypt;
|
||||||
pub mod factory_reset;
|
pub mod factory_reset;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::BaseKeySlot;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation;
|
use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation;
|
||||||
|
@ -68,84 +67,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
cli::Command::Sign(cmd) => {
|
cli::Command::Sign(cmd) => {
|
||||||
commands::sign::sign(cmd)?;
|
commands::sign::sign(cmd)?;
|
||||||
}
|
}
|
||||||
cli::Command::Attestation { cmd } => match cmd {
|
cli::Command::Attestation(cmd) => {
|
||||||
cli::AttCommand::Cert { ident } => {
|
commands::attestation::attestation(cli.output_format, cli.output_version, cmd)?;
|
||||||
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::FactoryReset(cmd) => {
|
cli::Command::FactoryReset(cmd) => {
|
||||||
commands::factory_reset::factory_reset(cmd)?;
|
commands::factory_reset::factory_reset(cmd)?;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue