195 lines
5.8 KiB
Rust
195 lines
5.8 KiB
Rust
// 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::types::KeyType;
|
|
use openpgp_card_sequoia::{state::Open, Card};
|
|
|
|
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<String>,
|
|
},
|
|
|
|
/// 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<PathBuf>,
|
|
},
|
|
|
|
/// 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<String>,
|
|
|
|
/// 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<BaseKeySlot> 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<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 open: Card<Open> = 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<PathBuf>,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let backend = util::open_card(ident)?;
|
|
let mut open: Card<Open> = 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<String>, key: BaseKeySlot) -> Result<(), Box<dyn std::error::Error>> {
|
|
let backend = pick_card_for_reading(ident)?;
|
|
let mut open: Card<Open> = 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(())
|
|
}
|