openpgp-card-tools 0.11.11

A tool for inspecting, configuring and using OpenPGP cards
// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer <heiko@schaefer.name>
// SPDX-FileCopyrightText: 2022 Lars Wirzenius <liw@liw.fi>
// SPDX-FileCopyrightText: 2022 Nora Widdecke <mail@nora.pink>
// SPDX-FileCopyrightText: 2023 David Runge <dave@sleepmap.de>
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::path::PathBuf;

use anyhow::anyhow;
use anyhow::Result;
use clap::Parser;
use openpgp_card::ocard::KeyType;
use openpgp_card_rpgp::public_key_material_and_fp_to_key;

use crate::output;
use crate::pick_card_for_reading;
use crate::util;
use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion};

#[derive(Parser, Debug)]
pub struct PubkeyCommand {
    #[arg(
        name = "card ident",
        short = 'c',
        long = "card",
        help = "Identifier of the card to use"
    )]
    ident: String,

    #[arg(
        name = "User PIN file",
        short = 'p',
        long = "user-pin",
        help = "Optionally, get User PIN from a file"
    )]
    user_pin: Option<PathBuf>,

    /// User ID to add to the exported certificate representation
    #[arg(name = "User ID", short = 'u', long = "userid", required = true)]
    user_ids: Vec<String>,
}

pub fn print_pubkey(
    format: OutputFormat,
    output_version: OutputVersion,
    command: PubkeyCommand,
) -> Result<()> {
    let mut output = output::PublicKey::default();

    let mut open = pick_card_for_reading(Some(command.ident))?;
    let mut card = open.transaction()?;

    let ident = card.application_identifier()?.ident();
    output.ident(ident);

    // This PIN is required to produce new binding signatures.
    //
    // FIXME: this whole "pubkey" command should probably be replaced by getters for individual
    // raw component key material. producing new bindings by default is probably too bold.
    let user_pin = util::get_pin(&mut card, command.user_pin, crate::ENTER_USER_PIN)?;

    let pkm = card.public_key_material(KeyType::Signing)?;
    let times = card.key_generation_times()?;
    let fps = card.fingerprints()?;

    let key_sig = public_key_material_and_fp_to_key(
        &pkm,
        KeyType::Signing,
        times
            .signature()
            .ok_or(anyhow!("Signature time is unset"))?,
        fps.signature()
            .ok_or(anyhow!("Signature fingerprint is unset"))?,
    )?;

    let mut key_dec = None;
    if let Ok(pkm) = card.public_key_material(KeyType::Decryption) {
        if let Some(ts) = times.decryption() {
            key_dec = Some(public_key_material_and_fp_to_key(
                &pkm,
                KeyType::Decryption,
                ts,
                fps.decryption()
                    .ok_or(anyhow!("Decryption fingerprint is unset"))?,
            )?);
        }
    }

    let mut key_aut = None;
    if let Ok(pkm) = card.public_key_material(KeyType::Authentication) {
        if let Some(ts) = times.authentication() {
            key_aut = Some(public_key_material_and_fp_to_key(
                &pkm,
                KeyType::Authentication,
                ts,
                fps.authentication()
                    .ok_or(anyhow!("Authentication fingerprint is unset"))?,
            )?);
        }
    }

    let cert = crate::get_cert(
        &mut card,
        key_sig,
        key_dec,
        key_aut,
        user_pin,
        &command.user_ids,
        &|| eprintln!("Enter User PIN on card reader pinpad."),
    )?;

    let mut buf = Vec::new();
    cert.save(true, &mut buf)?;
    let armored = std::str::from_utf8(buf.as_slice())?.to_string();

    output.public_key(armored);

    println!("{}", output.print(format, output_version)?);
    Ok(())
}