// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::{anyhow, Result}; use clap::Parser; use std::path::{Path, PathBuf}; use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole}; use sequoia_openpgp::packet::Key; use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse}; use sequoia_openpgp::policy::{Policy, StandardPolicy}; use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer}; use sequoia_openpgp::serialize::SerializeInto; use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{Sex, TouchPolicy}; use openpgp_card::{CardBackend, KeyType, OpenPgp}; use openpgp_card_sequoia::card::{Admin, Card, Open}; use openpgp_card_sequoia::util::{ make_cert, public_key_material_and_fp_to_key, public_key_material_to_key, }; use openpgp_card_sequoia::{sq_util, PublicKey}; use crate::util::{load_pin, print_gnuk_note}; use std::io::Write; mod cli; mod util; const ENTER_USER_PIN: &str = "Enter User PIN:"; const ENTER_ADMIN_PIN: &str = "Enter Admin PIN:"; fn main() -> Result<(), Box> { env_logger::init(); let cli = cli::Cli::parse(); match cli.cmd { cli::Command::List {} => { println!("Available OpenPGP cards:"); list_cards()?; } cli::Command::Status { ident, verbose, pkm, } => { print_status(ident, verbose, pkm)?; } cli::Command::Info { ident } => { print_info(ident)?; } cli::Command::Ssh { ident } => { print_ssh(ident)?; } cli::Command::Pubkey { ident, user_pin, user_id, } => { print_pubkey(ident, user_pin, user_id)?; } cli::Command::SetIdentity { ident, id } => { set_identity(&ident, id)?; } cli::Command::Decrypt { ident, user_pin, input, } => { decrypt(&ident, user_pin, input.as_deref())?; } cli::Command::Sign { ident, user_pin, detached, input, } => { if detached { sign_detached(&ident, user_pin, input.as_deref())?; } else { return Err( anyhow::anyhow!("Only detached signatures are supported for now").into(), ); } } cli::Command::Attestation { cmd } => match cmd { cli::AttCommand::Cert { ident } => { let card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.transaction()?)?; if let Ok(ac) = open.attestation_certificate() { let pem = util::pem_encode(ac); println!("{}", pem); } } cli::AttCommand::Generate { ident, key, user_pin, } => { let card = util::open_card(&ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.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 = match key.as_str() { "SIG" => KeyType::Signing, "DEC" => KeyType::Decryption, "AUT" => KeyType::Authentication, _ => { return Err(anyhow!("Unexpected Key Type {}", key).into()); } }; sign.generate_attestation(kt, &|| { println!("Touch confirmation needed to generate an attestation") })?; } cli::AttCommand::Statement { ident, key } => { let card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.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.as_str() { "AUT" => open.select_data(0, &[0x7F, 0x21], select_data_workaround)?, "DEC" => open.select_data(1, &[0x7F, 0x21], select_data_workaround)?, "SIG" => open.select_data(2, &[0x7F, 0x21], select_data_workaround)?, _ => { return Err(anyhow!("Unexpected Key Type {}", key).into()); } }; // 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 { ident } => { factory_reset(&ident)?; } cli::Command::Admin { ident, admin_pin, cmd, } => { let card = util::open_card(&ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.transaction()?)?; let admin_pin = util::get_pin(&mut open, admin_pin, ENTER_ADMIN_PIN); match cmd { cli::AdminCommand::Name { name } => { let mut admin = util::verify_to_admin(&mut open, admin_pin.as_deref())?; admin.set_name(&name)?; } cli::AdminCommand::Url { url } => { let mut admin = util::verify_to_admin(&mut open, admin_pin.as_deref())?; admin.set_url(&url)?; } cli::AdminCommand::Import { keyfile, sig_fp, dec_fp, auth_fp, } => { 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, &auth_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, auth_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 open, admin_pin.as_deref())?; 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)?; } } cli::AdminCommand::Generate { user_pin, output, no_decrypt, no_auth, algo, user_id, } => { let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); generate_keys( open, admin_pin.as_deref(), user_pin.as_deref(), output, !no_decrypt, !no_auth, algo, user_id, )?; } cli::AdminCommand::Touch { key, policy } => { let kt = match key.as_str() { "SIG" => KeyType::Signing, "DEC" => KeyType::Decryption, "AUT" => KeyType::Authentication, "ATT" => KeyType::Attestation, _ => { return Err(anyhow!("Unexpected Key Type {}", key).into()); } }; let pol = match policy.as_str() { "Off" => TouchPolicy::Off, "On" => TouchPolicy::On, "Fixed" => TouchPolicy::Fixed, "Cached" => TouchPolicy::Cached, "Cached-Fixed" => TouchPolicy::CachedFixed, _ => { return Err(anyhow!("Unexpected Policy {}", policy).into()); } }; let mut admin = util::verify_to_admin(&mut open, admin_pin.as_deref())?; admin.set_uif(kt, pol)?; } } } cli::Command::Pin { ident, cmd } => { let card = util::open_card(&ident)?; let mut pgp = OpenPgp::new(card); let pgpt = pgp.transaction()?; let pinpad_modify = pgpt.feature_pinpad_modify(); let mut open = Open::new(pgpt)?; match cmd { cli::PinCommand::SetUser { user_pin_old, user_pin_new, } => { let res = if !pinpad_modify { // get current user pin let user_pin1 = util::get_pin(&mut open, user_pin_old, ENTER_USER_PIN) .expect("this should never be None"); // verify pin open.verify_user(&user_pin1)?; println!("PIN was accepted by the card.\n"); let pin_new = match user_pin_new { None => { // ask user for new user pin util::input_pin_twice( "Enter new User PIN: ", "Repeat the new User PIN: ", )? } Some(path) => load_pin(&path)?, }; // set new user pin open.change_user_pin(&user_pin1, &pin_new) } else { // set new user pin via pinpad open.change_user_pin_pinpad(&|| { println!( "Enter old User PIN on card reader pinpad, then new User PIN (twice)." ) }) }; if res.is_err() { println!("\nFailed to change the User PIN!"); println!("{:?}", res); if let Err(err) = res { print_gnuk_note(err, &open)?; } } else { println!("\nUser PIN has been set."); } } cli::PinCommand::SetAdmin { admin_pin_old, admin_pin_new, } => { if !pinpad_modify { // get current admin pin let admin_pin1 = util::get_pin(&mut open, admin_pin_old, ENTER_ADMIN_PIN) .expect("this should never be None"); // verify pin open.verify_admin(&admin_pin1)?; // (Verifying the PIN here fixes this class of problems: // https://developers.yubico.com/PGP/PGP_PIN_Change_Behavior.html // It is also just generally more user friendly than failing later) println!("PIN was accepted by the card.\n"); let pin_new = match admin_pin_new { None => { // ask user for new admin pin util::input_pin_twice( "Enter new Admin PIN: ", "Repeat the new Admin PIN: ", )? } Some(path) => load_pin(&path)?, }; // set new admin pin open.change_admin_pin(&admin_pin1, &pin_new)?; } else { // set new admin pin via pinpad open.change_admin_pin_pinpad(&|| { println!( "Enter old Admin PIN on card reader pinpad, then new Admin PIN (twice)." ) })?; }; println!("\nAdmin PIN has been set."); } cli::PinCommand::ResetUser { admin_pin, user_pin_new, } => { // verify admin pin match util::get_pin(&mut open, admin_pin, ENTER_ADMIN_PIN) { Some(admin_pin) => { // verify pin open.verify_admin(&admin_pin)?; } None => { open.verify_admin_pinpad(&|| println!("Enter Admin PIN on pinpad."))?; } } println!("PIN was accepted by the card.\n"); // ask user for new user pin let pin = match user_pin_new { None => util::input_pin_twice( "Enter new User PIN: ", "Repeat the new User PIN: ", )?, Some(path) => load_pin(&path)?, }; let res = if let Some(mut admin) = open.admin_card() { admin.reset_user_pin(&pin) } else { return Err(anyhow::anyhow!("Failed to use card in admin-mode.").into()); }; if res.is_err() { println!("\nFailed to change the User PIN!"); if let Err(err) = res { print_gnuk_note(err, &open)?; } } else { println!("\nUser PIN has been set."); } } cli::PinCommand::SetReset { admin_pin, reset_code, } => { // verify admin pin match util::get_pin(&mut open, admin_pin, ENTER_ADMIN_PIN) { Some(admin_pin) => { // verify pin open.verify_admin(&admin_pin)?; } None => { open.verify_admin_pinpad(&|| println!("Enter Admin PIN on pinpad."))?; } } println!("PIN was accepted by the card.\n"); // ask user for new resetting code let code = match reset_code { None => util::input_pin_twice( "Enter new resetting code: ", "Repeat the new resetting code: ", )?, Some(path) => load_pin(&path)?, }; if let Some(mut admin) = open.admin_card() { admin.set_resetting_code(&code)?; println!("\nResetting code has been set."); } else { return Err(anyhow::anyhow!("Failed to use card in admin-mode.").into()); }; } cli::PinCommand::ResetUserRc { reset_code, user_pin_new, } => { // reset by presenting resetting code let rst = if let Some(path) = reset_code { // load resetting code from file load_pin(&path)? } else { // input resetting code rpassword::prompt_password("Enter resetting code: ")? .as_bytes() .to_vec() }; // ask user for new user pin let pin = match user_pin_new { None => util::input_pin_twice( "Enter new User PIN: ", "Repeat the new User PIN: ", )?, Some(path) => load_pin(&path)?, }; // reset to new user pin match open.reset_user_pin(&rst, &pin) { Err(err) => { println!("\nFailed to change the User PIN!"); print_gnuk_note(err, &open)?; } Ok(_) => println!("\nUser PIN has been set."), } } } } } Ok(()) } fn list_cards() -> Result<()> { let cards = util::cards()?; if !cards.is_empty() { for card in cards { let mut pgp = OpenPgp::new(card); let open = Open::new(pgp.transaction()?)?; println!(" {}", open.application_identifier()?.ident()); } } else { println!("No OpenPGP cards found."); } Ok(()) } fn set_identity(ident: &str, id: u8) -> Result<(), Box> { let card = util::open_card(ident)?; let mut pgp = OpenPgp::new(card); let mut pgpt = pgp.transaction()?; pgpt.set_identity(id)?; Ok(()) } /// Return a card for a read operation. If `ident` is None, and exactly one card /// is plugged in, that card is returned. (We don't This fn pick_card_for_reading(ident: Option) -> Result> { if let Some(ident) = ident { Ok(util::open_card(&ident)?) } else { let mut cards = util::cards()?; if cards.len() == 1 { Ok(cards.pop().unwrap()) } else if cards.is_empty() { Err(anyhow::anyhow!("No cards found")) } else { println!("Found {} cards:", cards.len()); list_cards()?; println!(); println!("Specify which card to use with '--card '"); println!(); Err(anyhow::anyhow!("Specify card")) } } } fn print_status(ident: Option, verbose: bool, pkm: bool) -> Result<()> { let card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(card); let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; let mut open = Open::new(pgpt)?; print!("OpenPGP card {}", open.application_identifier()?.ident()); let ai = open.application_identifier()?; let version = ai.version().to_be_bytes(); println!(" (card version {}.{})\n", version[0], version[1]); // card / cardholder metadata let crd = open.cardholder_related_data()?; // Remember if any cardholder information is printed (if so, we print a newline later) let mut card_holder_output = false; if let Some(name) = crd.name() { // FIXME: decoding as utf8 is wrong (the spec defines this field as latin1 encoded) let name = String::from_utf8_lossy(name).to_string(); print!("Cardholder: "); // This field is silly, maybe ignore it?! if let Some(sex) = crd.sex() { if sex == Sex::Male { print!("Mr. "); } else if sex == Sex::Female { print!("Mrs. "); } } // re-format name ("last< = name.split("<<").collect(); let name = name.iter().cloned().rev().collect::>().join(" "); println!("{}", name); card_holder_output = true; } let url = open.url()?; if !url.is_empty() { println!("URL: {}", url); card_holder_output = true; } if let Some(lang) = crd.lang() { let l = lang .iter() .map(|l| format!("{}", l)) .collect::>() .join(", "); println!("Language preferences: '{}'", l); card_holder_output = true; } if card_holder_output { println!(); } // key information (imported vs. generated on card) let ki = ard.key_information().ok().flatten(); let pws = open.pw_status_bytes()?; // information about subkeys let fps = open.fingerprints()?; let kgt = open.key_generation_times()?; println!("Signature key"); if let Some(fp) = fps.signature() { println!(" Fingerprint: {}", fp.to_spaced_hex()); } println! {" Algorithm: {}", open.algorithm_attributes(KeyType::Signing)?}; if let Some(kgt) = kgt.signature() { println! {" Created: {}", kgt.to_datetime()}; } if verbose { if let Some(uif) = ard.uif_pso_cds()? { println!( " Touch policy: {} [Features: {}]", uif.touch_policy(), uif.features() ); } if let Some(ks) = ki.as_ref().map(|ki| ki.sig_status()) { println!(" Key Status: {}", ks); } } if verbose { if pws.pw1_cds_valid_once() { println!(" User PIN presentation valid for one signature"); } else { println!(" User PIN presentation valid for unlimited signatures"); } } let sst = open.security_support_template()?; println!(" Signatures made: {}", sst.signature_count()); if pkm { if let Ok(pkm) = open.public_key(KeyType::Signing) { println! {" Public key material: {}", pkm}; } } println!(); println!("Decryption key"); if let Some(fp) = fps.decryption() { println!(" Fingerprint: {}", fp.to_spaced_hex()); } println! {" Algorithm: {}", open.algorithm_attributes(KeyType::Decryption)?}; if let Some(kgt) = kgt.decryption() { println! {" Created: {}", kgt.to_datetime()}; } if verbose { if let Some(uif) = ard.uif_pso_dec()? { println!( " Touch policy: {} [Features: {}]", uif.touch_policy(), uif.features() ); } if let Some(ks) = ki.as_ref().map(|ki| ki.dec_status()) { println!(" Key Status: {}", ks); } } if pkm { if let Ok(pkm) = open.public_key(KeyType::Decryption) { println! {" Public key material: {}", pkm}; } } println!(); println!("Authentication key"); if let Some(fp) = fps.authentication() { println!(" Fingerprint: {}", fp.to_spaced_hex()); } println! {" Algorithm: {}", open.algorithm_attributes(KeyType::Authentication)?}; if let Some(kgt) = kgt.authentication() { println! {" Created: {}", kgt.to_datetime()}; } if verbose { if let Some(uif) = ard.uif_pso_aut()? { println!( " Touch policy: {} [Features: {}]", uif.touch_policy(), uif.features() ); } if let Some(ks) = ki.as_ref().map(|ki| ki.aut_status()) { println!(" Key Status: {}", ks); } } if pkm { if let Ok(pkm) = open.public_key(KeyType::Authentication) { println! {" public key material: {}", pkm}; } } // technical details about the card's state println!(); println!( "Remaining PIN attempts: User: {}, Admin: {}, Reset Code: {}", pws.err_count_pw1(), pws.err_count_pw3(), pws.err_count_rc(), ); if verbose { println!(); if let Some(uif) = ard.uif_attestation()? { println!( "Touch policy attestation: {} [Features: {}]", uif.touch_policy(), uif.features() ); println!(); } if let Some(ki) = ki { let num = ki.num_additional(); for i in 0..num { println!( "Key Status (#{}): {}", ki.additional_ref(i), ki.additional_status(i) ); } if num > 0 { println!(); } } if let Ok(fps) = ard.ca_fingerprints() { for (num, fp) in fps.iter().enumerate() { if let Some(fp) = fp { println!("CA fingerprint {}: {:x?}", num + 1, fp); } } } } // FIXME: print "Login Data" Ok(()) } /// print metadata information about a card fn print_info(ident: Option) -> Result<()> { let card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.transaction()?)?; let ai = open.application_identifier()?; print!("OpenPGP card {}", ai.ident()); let version = ai.version().to_be_bytes(); println!(" (card version {}.{})\n", version[0], version[1]); println!("Application Identifier: {}", ai); println!( "Manufacturer [{:04X}]: {}\n", ai.manufacturer(), ai.manufacturer_name() ); if let Some(cc) = open.historical_bytes()?.card_capabilities() { println!("Card Capabilities:\n{}", cc); } if let Some(csd) = open.historical_bytes()?.card_service_data() { println!("Card service data:\n{}", csd); } if let Some(eli) = open.extended_length_information()? { println!("Extended Length Info:\n{}", eli); } let ec = open.extended_capabilities()?; println!("Extended Capabilities:\n{}", ec); // Algorithm information (list of supported algorithms) if let Ok(Some(ai)) = open.algorithm_information() { println!("Supported algorithms:"); println!("{}", ai); } // FIXME: print KDF info // YubiKey specific (?) firmware version if let Ok(ver) = open.firmware_version() { let ver = ver.iter().map(u8::to_string).collect::>().join("."); println!("Firmware Version: {}\n", ver); } Ok(()) } fn print_ssh(ident: Option) -> Result<()> { let card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.transaction()?)?; let ident = open.application_identifier()?.ident(); println!("OpenPGP card {}", ident); // Print fingerprint of authentication subkey let fps = open.fingerprints()?; println!(); if let Some(fp) = fps.authentication() { println!("Authentication key fingerprint:\n{}", fp); } // Show authentication subkey as openssh public key string if let Ok(pkm) = open.public_key(KeyType::Authentication) { if let Ok(ssh) = util::get_ssh_pubkey_string(&pkm, ident) { println!(); println!("SSH public key:\n{}", ssh); } } Ok(()) } fn print_pubkey( ident: Option, user_pin: Option, user_ids: Vec, ) -> Result<()> { let card = pick_card_for_reading(ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.transaction()?)?; let ident = open.application_identifier()?.ident(); println!("OpenPGP card {}", ident); let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); let pkm = open.public_key(KeyType::Signing)?; let times = open.key_generation_times()?; let fps = open.fingerprints()?; let key_sig = public_key_material_and_fp_to_key( &pkm, KeyType::Signing, times.signature().expect("Signature time is unset"), fps.signature().expect("Signature fingerprint is unset"), )?; let mut key_dec = None; if let Ok(pkm) = open.public_key(KeyType::Decryption) { if let Some(ts) = times.decryption() { key_dec = Some(public_key_material_and_fp_to_key( &pkm, KeyType::Decryption, ts, fps.decryption().expect("Decryption fingerprint is unset"), )?); } } let mut key_aut = None; if let Ok(pkm) = open.public_key(KeyType::Authentication) { if let Some(ts) = times.authentication() { key_aut = Some(public_key_material_and_fp_to_key( &pkm, KeyType::Authentication, ts, fps.authentication() .expect("Authentication fingerprint is unset"), )?); } } let cert = get_cert( &mut open, key_sig, key_dec, key_aut, user_pin.as_deref(), &user_ids, &|| println!("Enter User PIN on card reader pinpad."), )?; let armored = String::from_utf8(cert.armored().to_vec()?)?; println!("{}", armored); Ok(()) } fn decrypt( ident: &str, pin_file: Option, input: Option<&Path>, ) -> Result<(), Box> { let p = StandardPolicy::new(); let input = util::open_or_stdin(input)?; let card = util::open_card(ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.transaction()?)?; let user_pin = util::get_pin(&mut open, pin_file, ENTER_USER_PIN); let mut user = util::verify_to_user(&mut open, user_pin.as_deref())?; let d = user.decryptor(&|| println!("Touch confirmation needed for decryption"))?; let db = DecryptorBuilder::from_reader(input)?; let mut decryptor = db.with_policy(&p, None, d)?; std::io::copy(&mut decryptor, &mut std::io::stdout())?; Ok(()) } fn sign_detached( ident: &str, pin_file: Option, input: Option<&Path>, ) -> Result<(), Box> { let mut input = util::open_or_stdin(input)?; let card = util::open_card(ident)?; let mut pgp = OpenPgp::new(card); let mut open = Open::new(pgp.transaction()?)?; let user_pin = util::get_pin(&mut open, pin_file, ENTER_USER_PIN); let mut sign = util::verify_to_sign(&mut open, user_pin.as_deref())?; let s = sign.signer(&|| println!("Touch confirmation needed for signing"))?; let message = Armorer::new(Message::new(std::io::stdout())).build()?; let mut signer = Signer::new(message, s).detached().build()?; std::io::copy(&mut input, &mut signer)?; signer.finalize()?; Ok(()) } fn factory_reset(ident: &str) -> Result<()> { println!("Resetting Card {}", ident); let card = util::open_card(ident)?; let mut card = Card::new(card); let mut open = card.transaction()?; open.factory_reset().map_err(|e| anyhow!(e)) } 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, auth_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(auth_fp)?]) } fn get_cert( open: &mut Open, key_sig: PublicKey, key_dec: Option, key_aut: Option, user_pin: Option<&[u8]>, user_ids: &[String], prompt: &dyn Fn(), ) -> Result { if user_pin.is_none() && open.feature_pinpad_verify() { println!( "The public cert will now be generated.\n\n\ You will need to enter your User PIN multiple times during this process.\n\n" ); } make_cert( open, key_sig, key_dec, key_aut, user_pin, prompt, &|| println!("Touch confirmation needed for signing"), user_ids, ) } #[allow(clippy::too_many_arguments)] fn generate_keys( mut open: Open, admin_pin: Option<&[u8]>, user_pin: Option<&[u8]>, output: Option, decrypt: bool, auth: bool, algo: Option, user_ids: Vec, ) -> Result<()> { // 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 a = match algo.as_deref() { None => None, Some("rsa2048") => Some(AlgoSimple::RSA2k), Some("rsa3072") => Some(AlgoSimple::RSA3k), Some("rsa4096") => Some(AlgoSimple::RSA4k), Some("nistp256") => Some(AlgoSimple::NIST256), Some("nistp384") => Some(AlgoSimple::NIST384), Some("nistp521") => Some(AlgoSimple::NIST521), Some("25519") => Some(AlgoSimple::Curve25519), _ => return Err(anyhow!("Unexpected algorithm")), }; log::info!(" Key generation will be attempted with algo: {:?}", a); // 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 open, admin_pin) { gen_subkeys(&mut admin, decrypt, auth, a)? } 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 = get_cert( &mut open, key_sig, key_dec, key_aut, user_pin, &user_ids, &|| println!("Enter User PIN on card reader pinpad."), )?; let armored = String::from_utf8(cert.armored().to_vec()?)?; // Write armored certificate to the output file (or stdout) let mut output = util::open_or_stdout(output.as_deref())?; output.write_all(armored.as_bytes())?; Ok(()) } fn gen_subkeys( admin: &mut Admin, 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)) }