// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-FileCopyrightText: 2022 Lars Wirzenius // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::{anyhow, Result}; use clap::{Parser, ValueEnum}; use openpgp_card_sequoia::card::{Admin, Open, Transaction}; use openpgp_card_sequoia::util::public_key_material_to_key; use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use std::path::PathBuf; use openpgp_card_sequoia::{sq_util, PublicKey}; use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole}; use sequoia_openpgp::packet::Key; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::policy::Policy; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::SerializeInto; use sequoia_openpgp::Cert; use openpgp_card_sequoia::types::AlgoSimple; use openpgp_card_sequoia::{card::Card, types::KeyType}; use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; use crate::{output, util, ENTER_ADMIN_PIN, ENTER_USER_PIN}; #[derive(Parser, Debug)] pub struct AdminCommand { #[clap(name = "card ident", short = 'c', long = "card")] pub ident: String, #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] pub admin_pin: Option, #[clap(subcommand)] pub cmd: AdminSubCommand, } #[derive(Parser, Debug)] pub enum AdminSubCommand { /// Set cardholder name Name { name: String }, /// Set cardholder URL Url { url: String }, /// Import a Key. /// /// If no fingerprint is provided, the key will only be imported if /// there are zero or one (sub)keys for each key slot on the card. Import { keyfile: PathBuf, #[clap(name = "Signature key fingerprint", short = 's', long = "sig-fp")] sig_fp: Option, #[clap(name = "Decryption key fingerprint", short = 'd', long = "dec-fp")] dec_fp: Option, #[clap(name = "Authentication key fingerprint", short = 'a', long = "aut-fp")] aut_fp: Option, }, /// Generate a Key. /// /// A signing key is always created, decryption and authentication keys /// are optional. Generate(AdminGenerateCommand), /// Set touch policy Touch { #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] key: BasePlusAttKeySlot, #[clap(name = "Policy", short = 'p', long = "policy", value_enum)] policy: TouchPolicy, }, } #[derive(Parser, Debug)] pub struct AdminGenerateCommand { #[clap(name = "User PIN file", short = 'p', long = "user-pin")] user_pin: Option, /// Output file #[clap(name = "output", long = "output", short = 'o')] output_file: PathBuf, #[clap(long = "no-decrypt", action = clap::ArgAction::SetFalse)] decrypt: bool, #[clap(long = "no-auth", action = clap::ArgAction::SetFalse)] auth: bool, /// Algorithm #[clap(value_enum)] algo: Option, /// User ID to add to the exported certificate representation #[clap(name = "User ID", short = 'u', long = "userid")] user_ids: Vec, } #[derive(ValueEnum, Debug, Clone)] #[clap(rename_all = "UPPER")] pub enum BasePlusAttKeySlot { Sig, Dec, Aut, Att, } impl From for KeyType { fn from(ks: BasePlusAttKeySlot) -> Self { match ks { BasePlusAttKeySlot::Sig => KeyType::Signing, BasePlusAttKeySlot::Dec => KeyType::Decryption, BasePlusAttKeySlot::Aut => KeyType::Authentication, BasePlusAttKeySlot::Att => KeyType::Attestation, } } } #[derive(ValueEnum, Debug, Clone)] pub enum TouchPolicy { #[clap(name = "Off")] Off, #[clap(name = "On")] On, #[clap(name = "Fixed")] Fixed, #[clap(name = "Cached")] Cached, #[clap(name = "Cached-Fixed")] CachedFixed, } impl From for openpgp_card_sequoia::types::TouchPolicy { fn from(tp: TouchPolicy) -> Self { use openpgp_card_sequoia::types::TouchPolicy as OCTouchPolicy; match tp { TouchPolicy::On => OCTouchPolicy::On, TouchPolicy::Off => OCTouchPolicy::Off, TouchPolicy::Fixed => OCTouchPolicy::Fixed, TouchPolicy::Cached => OCTouchPolicy::Cached, TouchPolicy::CachedFixed => OCTouchPolicy::CachedFixed, } } } #[derive(ValueEnum, Debug, Clone)] #[clap(rename_all = "lower")] pub enum Algo { Rsa2048, Rsa3072, Rsa4096, Nistp256, Nistp384, Nistp521, Cv25519, } impl From for AlgoSimple { fn from(a: Algo) -> Self { match a { Algo::Rsa2048 => AlgoSimple::RSA2k, Algo::Rsa3072 => AlgoSimple::RSA3k, Algo::Rsa4096 => AlgoSimple::RSA4k, Algo::Nistp256 => AlgoSimple::NIST256, Algo::Nistp384 => AlgoSimple::NIST384, Algo::Nistp521 => AlgoSimple::NIST521, Algo::Cv25519 => AlgoSimple::Curve25519, } } } pub fn admin( output_format: OutputFormat, output_version: OutputVersion, command: AdminCommand, ) -> Result<(), Box> { let backend = util::open_card(&command.ident)?; let mut open: Card = backend.into(); let mut card = open.transaction()?; let admin_pin = util::get_pin(&mut card, command.admin_pin, ENTER_ADMIN_PIN); match command.cmd { AdminSubCommand::Name { name } => { name_command(&name, card, admin_pin.as_deref())?; } AdminSubCommand::Url { url } => { url_command(&url, card, admin_pin.as_deref())?; } AdminSubCommand::Import { keyfile, sig_fp, dec_fp, aut_fp, } => { import_command(keyfile, sig_fp, dec_fp, aut_fp, card, admin_pin.as_deref())?; } AdminSubCommand::Generate(cmd) => { generate_command( output_format, output_version, card, admin_pin.as_deref(), cmd, )?; } AdminSubCommand::Touch { key, policy } => { touch_command(card, admin_pin.as_deref(), key, policy)?; } } Ok(()) } fn keys_pick_yolo<'a>( key: &'a Cert, policy: &'a dyn Policy, ) -> Result<[Option>; 3]> { let key_by_type = |kt| sq_util::subkey_by_type(key, policy, kt); Ok([ key_by_type(KeyType::Signing)?, key_by_type(KeyType::Decryption)?, key_by_type(KeyType::Authentication)?, ]) } fn keys_pick_explicit<'a>( key: &'a Cert, policy: &'a dyn Policy, sig_fp: Option, dec_fp: Option, aut_fp: Option, ) -> Result<[Option>; 3]> { let key_by_fp = |fp: Option| match fp { Some(fp) => sq_util::private_subkey_by_fingerprint(key, policy, &fp), None => Ok(None), }; Ok([key_by_fp(sig_fp)?, key_by_fp(dec_fp)?, key_by_fp(aut_fp)?]) } fn gen_subkeys( admin: &mut Card, decrypt: bool, auth: bool, algo: Option, ) -> Result<(PublicKey, Option, Option)> { // We begin by generating the signing subkey, which is mandatory. println!(" Generate subkey for Signing"); let (pkm, ts) = admin.generate_key_simple(KeyType::Signing, algo)?; let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, &ts, None, None)?; // make decryption subkey (unless disabled), with the same algorithm as // the sig key let key_dec = if decrypt { println!(" Generate subkey for Decryption"); let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, algo)?; Some(public_key_material_to_key( &pkm, KeyType::Decryption, &ts, Some(HashAlgorithm::SHA256), // FIXME Some(SymmetricAlgorithm::AES128), // FIXME )?) } else { None }; // make authentication subkey (unless disabled), with the same // algorithm as the sig key let key_aut = if auth { println!(" Generate subkey for Authentication"); let (pkm, ts) = admin.generate_key_simple(KeyType::Authentication, algo)?; Some(public_key_material_to_key( &pkm, KeyType::Authentication, &ts, None, None, )?) } else { None }; Ok((key_sig, key_dec, key_aut)) } fn name_command( name: &str, mut card: Card, admin_pin: Option<&[u8]>, ) -> Result<(), Box> { let mut admin = util::verify_to_admin(&mut card, admin_pin)?; admin.set_name(name)?; Ok(()) } fn url_command( url: &str, mut card: Card, admin_pin: Option<&[u8]>, ) -> Result<(), Box> { let mut admin = util::verify_to_admin(&mut card, admin_pin)?; admin.set_url(url)?; Ok(()) } fn import_command( keyfile: PathBuf, sig_fp: Option, dec_fp: Option, aut_fp: Option, mut card: Card, admin_pin: Option<&[u8]>, ) -> Result<(), Box> { let key = Cert::from_file(keyfile)?; let p = StandardPolicy::new(); // select the (sub)keys to upload let [sig, dec, auth] = match (&sig_fp, &dec_fp, &aut_fp) { // No fingerprint has been provided, try to autoselect keys // (this fails if there is more than one (sub)key for any keytype). (&None, &None, &None) => keys_pick_yolo(&key, &p)?, _ => keys_pick_explicit(&key, &p, sig_fp, dec_fp, aut_fp)?, }; let mut pws: Vec = vec![]; // helper: true, if `pw` decrypts `key` let pw_ok = |key: &Key, pw: &str| { key.clone() .decrypt_secret(&sequoia_openpgp::crypto::Password::from(pw)) .is_ok() }; // helper: if any password in `pws` decrypts `key`, return that password let find_pw = |key: &Key, pws: &[String]| { pws.iter().find(|pw| pw_ok(key, pw)).cloned() }; // helper: check if we have the right password for `key` in `pws`, // if so return it. otherwise ask the user for the password, // add it to `pws` and return it. let mut get_pw_for_key = |key: &Option>, key_type: &str| -> Result> { if let Some(k) = key { if !k.has_secret() { // key has no secret key material, it can't be imported return Err(anyhow!( "(Sub)Key {} contains no private key material", k.fingerprint() )); } if k.has_unencrypted_secret() { // key is unencrypted, we need no password return Ok(None); } // key is encrypted, we need the password // do we already have the right password? if let Some(pw) = find_pw(k, &pws) { return Ok(Some(pw)); } // no, we need to get the password from user let pw = rpassword::prompt_password(format!( "Enter password for {} (sub)key {}:", key_type, k.fingerprint() ))?; if pw_ok(k, &pw) { // remember pw for next subkeys pws.push(pw.clone()); Ok(Some(pw)) } else { // this password doesn't work, error out Err(anyhow!( "Password not valid for (Sub)Key {}", k.fingerprint() )) } } else { // we have no key for this slot, so we don't need a password Ok(None) } }; // get passwords, if encrypted (try previous pw before asking for user input) let sig_p = get_pw_for_key(&sig, "signing")?; let dec_p = get_pw_for_key(&dec, "decryption")?; let auth_p = get_pw_for_key(&auth, "authentication")?; // upload keys to card let mut admin = util::verify_to_admin(&mut card, admin_pin)?; if let Some(sig) = sig { println!("Uploading {} as signing key", sig.fingerprint()); admin.upload_key(sig, KeyType::Signing, sig_p)?; } if let Some(dec) = dec { println!("Uploading {} as decryption key", dec.fingerprint()); admin.upload_key(dec, KeyType::Decryption, dec_p)?; } if let Some(auth) = auth { println!("Uploading {} as authentication key", auth.fingerprint()); admin.upload_key(auth, KeyType::Authentication, auth_p)?; } Ok(()) } fn generate_command( output_format: OutputFormat, output_version: OutputVersion, mut card: Card, admin_pin: Option<&[u8]>, cmd: AdminGenerateCommand, ) -> Result<()> { let user_pin = util::get_pin(&mut card, cmd.user_pin, ENTER_USER_PIN); let mut output = output::AdminGenerate::default(); output.ident(card.application_identifier()?.ident()); // 1) Interpret the user's choice of algorithm. // // Unset (None) means that the algorithm that is specified on the card // should remain unchanged. // // For RSA, different cards use different exact algorithm // specifications. In particular, the length of the value `e` differs // between cards. Some devices use 32 bit length for e, others use 17 bit. // In some cases, it's possible to get this information from the card, // but I believe this information is not obtainable in all cases. // Because of this, for generation of RSA keys, here we take the approach // of first trying one variant, and then if that fails, try the other. let algo = cmd.algo.map(AlgoSimple::from); log::info!(" Key generation will be attempted with algo: {:?}", algo); output.algorithm(format!("{:?}", algo)); // 2) Then, generate keys on the card. // We need "admin" access to the card for this). let (key_sig, key_dec, key_aut) = { if let Ok(mut admin) = util::verify_to_admin(&mut card, admin_pin) { gen_subkeys(&mut admin, cmd.decrypt, cmd.auth, algo)? } else { return Err(anyhow!("Failed to open card in admin mode.")); } }; // 3) Generate a Cert from the generated keys. For this, we // need "signing" access to the card (to make binding signatures within // the Cert). let cert = crate::get_cert( &mut card, key_sig, key_dec, key_aut, user_pin.as_deref(), &cmd.user_ids, &|| println!("Enter User PIN on card reader pinpad."), )?; let armored = String::from_utf8(cert.armored().to_vec()?)?; output.public_key(armored); // Write armored certificate to the output file let mut handle = util::open_or_stdout(Some(&cmd.output_file))?; handle.write_all(output.print(output_format, output_version)?.as_bytes())?; let _ = handle.write(b"\n")?; Ok(()) } fn touch_command( mut card: Card, admin_pin: Option<&[u8]>, key: BasePlusAttKeySlot, policy: TouchPolicy, ) -> Result<(), Box> { let kt = KeyType::from(key); let pol = openpgp_card_sequoia::types::TouchPolicy::from(policy); let mut admin = util::verify_to_admin(&mut card, admin_pin)?; admin.set_uif(kt, pol)?; Ok(()) }