From 59d77f584de8b99e039fd2ecb67d281f8dc0b981 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 28 Oct 2021 00:09:44 +0200 Subject: [PATCH] Add openpgp-card-tools crate --- Cargo.toml | 4 +- tools/Cargo.toml | 28 +++ tools/README.md | 129 ++++++++++ tools/src/bin/opgpcard-pin/cli.rs | 31 +++ tools/src/bin/opgpcard-pin/main.rs | 147 +++++++++++ tools/src/bin/opgpcard/cli.rs | 117 +++++++++ tools/src/bin/opgpcard/main.rs | 377 +++++++++++++++++++++++++++++ tools/src/bin/opgpcard/util.rs | 67 +++++ 8 files changed, 898 insertions(+), 2 deletions(-) create mode 100644 tools/Cargo.toml create mode 100644 tools/README.md create mode 100644 tools/src/bin/opgpcard-pin/cli.rs create mode 100644 tools/src/bin/opgpcard-pin/main.rs create mode 100644 tools/src/bin/opgpcard/cli.rs create mode 100644 tools/src/bin/opgpcard/main.rs create mode 100644 tools/src/bin/opgpcard/util.rs diff --git a/Cargo.toml b/Cargo.toml index 4c9c4b4..ebbf02d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ # SPDX-FileCopyrightText: 2021 Heiko Schaefer # SPDX-License-Identifier: MIT OR Apache-2.0 - [workspace] members = [ - "openpgp-card-apps", "openpgp-card", "openpgp-card-sequoia", "pcsc", "scdc", + "openpgp-card-apps", + "tools", "card-functionality", ] diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 0000000..8822291 --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz +# SPDX-License-Identifier: MIT OR Apache-2.0 + +[package] +name = "openpgp-card-tools" +description = "Tools for using OpenPGP cards from the command line" +license = "MIT OR Apache-2.0" +version = "0.0.1" +authors = ["Heiko Schaefer "] +edition = "2018" +repository = "https://gitlab.com/hkos/openpgp-card" +documentation = "https://docs.rs/crate/openpgp-card-tools" + +[dependencies] +sequoia-openpgp = "1.3" +nettle = "7" +openpgp-card = { path = "../openpgp-card", version = "0.0.4" } +openpgp-card-pcsc = { path = "../pcsc", version = "0.0.4" } +openpgp-card-scdc = { path = "../scdc", version = "0.0.2" } +openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.3" } +rpassword = "5" +chrono = "0.4" +anyhow = "1" +thiserror = "1" +structopt = "0.3" +clap = "2.33" +env_logger = "0.8" +log = "0.4" diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..4ac3494 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,129 @@ + + +# OpenPGP card tools + +This crate contains two tools for inspecting, configuring and using OpenPGP +cards: + +`opgpcard` and `opgpcard-pin` + +## opgpcard + +A tool to inspect, configure and use OpenPGP cards. All calls of this tool +are non-interactive (this tool is designed to be easily usable from +shell-scripts). + +### List and inspect cards + +List idents of all currently connected cards: +``` +$ opgpcard list +``` + +Print status information about a card. The card is implicitly selected. +However, this only works if exactly one card is connected: +``` +$ opgpcard status +``` + +Explicitly print the status information for a specific card: +``` +$ opgpcard status -c ABCD:12345678 +``` + +Add `-v` for more verbose card status, including the list of supported +algorithms of the card: +``` +$ opgpcard status -c ABCD:12345678 -v +``` + +### Import keys + +Import private key onto a card. This works if at most one (sub)key +per role (sign, decrypt, auth) exists in `key.priv`: +``` +$ opgpcard admin -c ABCD:12345678 -p import key.priv +``` + +Import private key onto a card while explicitly selecting subkeys. +Explicitly specified fingerprints are necessary if more than one subkey +exists in `key.priv` for any role (note: spaces in fingerprints are +ignored). +``` +$ opgpcard admin -c ABCD:12345678 -p import key.priv \ + --sig-fp "F290 DBBF 21DB 8634 3C96 157B 87BE 15B7 F548 D97C" \ + --dec-fp "3C6E 08F6 7613 8935 8B8D 7666 73C7 F1A9 EEDA C360" \ + --auth-fp "D6AA 48EF 39A2 6F26 C42D 5BCB AAD2 14D5 5332 C838" +``` + +When fingerprints are only specified for a subset of the roles, no +keys will be imported for the other roles. + +### Set card metadata + +Set cardholder name: +``` +$ opgpcard admin -c ABCD:12345678 -p name "Bar< url "https://keyurl.example" +``` + +### Signing + +For now, this tool only supports creating detached signatures, like this +(if no input file is set, stdin is read): + +``` +$ opgpcard sign --detached -c ABCD:12345678 -p -s +``` + +### Decrypting + +Decryption using a card (if no input file is set, stdin is read): + +``` +$ opgpcard decrypt -c ABCD:12345678 -p -r +``` + +### Factory reset + +Factory reset: +``` +$ opgpcard factory-reset -c ABCD:12345678 +``` + +## opgpcard-pin + +An interactive tool to set the admin and user PINs, and to reset the user +PIN on OpenPGP cards. + +Set the user PIN (requires admin PIN): +``` +opgpcard-pin -c 1234:12345678 set-user-pin +``` + +Set new admin PIN (requires admin PIN): +``` +opgpcard-pin -c 1234:12345678 set-admin-pin +``` + +Reset user PIN after it has been blocked (requires admin PIN): +``` +opgpcard-pin -c 1234:12345678 reset-user-pin -a +``` + +Set resetting code (requires admin PIN): +``` +opgpcard-pin -c 1234:12345678 set-reset-code +``` + +Reset user PIN (requires resetting code): +``` +opgpcard-pin -c 1234:12345678 reset-user-pin +``` diff --git a/tools/src/bin/opgpcard-pin/cli.rs b/tools/src/bin/opgpcard-pin/cli.rs new file mode 100644 index 0000000..33f84fd --- /dev/null +++ b/tools/src/bin/opgpcard-pin/cli.rs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use clap::AppSettings; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "opgpcard", +author = "Heiko Schäfer ", +global_settings(& [AppSettings::VersionlessSubcommands, +AppSettings::DisableHelpSubcommand, AppSettings::DeriveDisplayOrder]), +about = "A tool for managing OpenPGP cards." +)] +pub struct Cli { + #[structopt(name = "card ident", short = "c", long = "card")] + pub ident: String, + + #[structopt(subcommand)] + pub cmd: Command, +} + +#[derive(StructOpt, Debug)] +pub enum Command { + SetUserPin {}, + SetAdminPin {}, + SetResetCode {}, + ResetUserPin { + #[structopt(name = "reset as admin", short = "a", long = "admin")] + admin: bool, + }, +} diff --git a/tools/src/bin/opgpcard-pin/main.rs b/tools/src/bin/opgpcard-pin/main.rs new file mode 100644 index 0000000..c7650d1 --- /dev/null +++ b/tools/src/bin/opgpcard-pin/main.rs @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::Result; +use structopt::StructOpt; + +use openpgp_card_pcsc::PcscClient; +use openpgp_card_sequoia::card::Open; + +mod cli; + +fn main() -> Result<(), Box> { + let cli = cli::Cli::from_args(); + + let ccb = PcscClient::open_by_ident(&cli.ident)?; + let mut card = Open::open_card(ccb)?; + + match cli.cmd { + cli::Command::SetUserPin {} => { + // get current user pin + let pin = + rpassword::read_password_from_tty(Some("Enter user PIN: "))?; + + // verify pin + card.verify_user(&pin)?; + + // get new user pin + let newpin1 = rpassword::read_password_from_tty(Some( + "Enter new user PIN: ", + ))?; + let newpin2 = rpassword::read_password_from_tty(Some( + "Repeat the new user PIN: ", + ))?; + + if newpin1 != newpin2 { + return Err(anyhow::anyhow!("PINs do not match.").into()); + } + + // set new user pin + card.change_user_pin(&pin, &newpin1)?; + + println!("\nUser PIN has been set."); + } + cli::Command::SetAdminPin {} => { + // get current admin pin + let pin = + rpassword::read_password_from_tty(Some("Enter admin PIN: "))?; + + // verify pin + card.verify_admin(&pin)?; + + // get new admin pin + let newpin1 = rpassword::read_password_from_tty(Some( + "Enter new admin PIN: ", + ))?; + let newpin2 = rpassword::read_password_from_tty(Some( + "Repeat the new admin PIN: ", + ))?; + + if newpin1 != newpin2 { + return Err(anyhow::anyhow!("PINs do not match.").into()); + } + + // set new user pin + card.change_admin_pin(&pin, &newpin1)?; + + println!("\nAdmin PIN has been set."); + } + cli::Command::SetResetCode {} => { + // get current admin pin + let pin = + rpassword::read_password_from_tty(Some("Enter admin PIN: "))?; + + // verify admin pin + card.verify_admin(&pin)?; + + if let Some(mut admin) = card.admin_card() { + // ask user for new resetting code + let newpin1 = rpassword::read_password_from_tty(Some( + "Enter new resetting code: ", + ))?; + let newpin2 = rpassword::read_password_from_tty(Some( + "Repeat the new resetting code: ", + ))?; + + if newpin1 == newpin2 { + admin.set_resetting_code(&pin)?; + } else { + return Err(anyhow::anyhow!("PINs do not match.").into()); + } + } else { + return Err(anyhow::anyhow!( + "Failed to use card in admin-mode." + ) + .into()); + } + println!("\nResetting code has been set."); + } + cli::Command::ResetUserPin { admin } => { + // either with resetting code, or by presenting pw3 + + let rst = if admin { + // get current admin pin + let pin = rpassword::read_password_from_tty(Some( + "Enter admin PIN: ", + ))?; + + // verify pin + card.verify_admin(&pin)?; + + None + } else { + // get current admin pin + let rst = rpassword::read_password_from_tty(Some( + "Enter resetting code: ", + ))?; + + Some(rst) + }; + + // get new user pin + let newpin1 = rpassword::read_password_from_tty(Some( + "Enter new user PIN: ", + ))?; + let newpin2 = rpassword::read_password_from_tty(Some( + "Repeat the new user PIN: ", + ))?; + + if newpin1 != newpin2 { + return Err(anyhow::anyhow!("PINs do not match.").into()); + } + if let Some(rst) = rst { + // reset to new user pin + card.reset_user_pin(&rst, &newpin1)?; + } else { + if let Some(mut admin) = card.admin_card() { + admin.reset_user_pin(&newpin1)?; + } else { + unimplemented!() + } + } + println!("\nUser PIN has been set."); + } + } + + Ok(()) +} diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs new file mode 100644 index 0000000..0a74825 --- /dev/null +++ b/tools/src/bin/opgpcard/cli.rs @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use clap::AppSettings; +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "opgpcard", +author = "Heiko Schäfer ", +global_settings(& [AppSettings::VersionlessSubcommands, +AppSettings::DisableHelpSubcommand, AppSettings::DeriveDisplayOrder]), +about = "A tool for managing OpenPGP cards." +)] +pub struct Cli { + #[structopt(subcommand)] + pub cmd: Command, +} + +#[derive(StructOpt, Debug)] +pub enum Command { + List {}, + Status { + #[structopt(name = "card ident", short = "c", long = "card")] + ident: Option, + + #[structopt(name = "verbose", short = "v", long = "verbose")] + verbose: bool, + }, + FactoryReset { + #[structopt(name = "card ident", short = "c", long = "card")] + ident: String, + }, + Admin { + #[structopt(name = "card ident", short = "c", long = "card")] + ident: String, + + #[structopt(name = "admin pin file", short = "p", long = "pin-file")] + pin_file: PathBuf, + + #[structopt(subcommand)] + cmd: AdminCommand, + }, + Decrypt { + #[structopt(name = "card ident", short = "c", long = "card")] + ident: String, + + #[structopt(name = "user pin file", short = "p", long = "pin-file")] + pin_file: PathBuf, + + #[structopt( + name = "recipient-cert-file", + short = "r", + long = "recipient-cert" + )] + cert_file: PathBuf, + + #[structopt(about = "Input file (stdin if unset)", name = "input")] + input: Option, + }, + Sign { + #[structopt(name = "card ident", short = "c", long = "card")] + ident: String, + + #[structopt(name = "user pin file", short = "p", long = "pin-file")] + pin_file: PathBuf, + + #[structopt(name = "detached", short = "d", long = "detached")] + detached: bool, + + #[structopt( + name = "signer-cert-file", + short = "s", + long = "signer-cert" + )] + cert_file: PathBuf, + + #[structopt(about = "Input file (stdin if unset)", name = "input")] + input: Option, + }, +} + +#[derive(StructOpt, Debug)] +pub enum AdminCommand { + /// Set name + Name { name: String }, + + /// Set URL + Url { url: String }, + + /// Import a Key. + /// If no fingerprint is provided, the key will + Import { + keyfile: PathBuf, + + #[structopt( + name = "Signature key fingerprint", + short = "s", + long = "sig-fp" + )] + sig_fp: Option, + + #[structopt( + name = "Decryption key fingerprint", + short = "d", + long = "dec-fp" + )] + dec_fp: Option, + + #[structopt( + name = "Authentication key fingerprint", + short = "a", + long = "auth-fp" + )] + auth_fp: Option, + }, +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs new file mode 100644 index 0000000..de9bef2 --- /dev/null +++ b/tools/src/bin/opgpcard/main.rs @@ -0,0 +1,377 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::Result; +use std::path::Path; +use structopt::StructOpt; + +use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse}; +use sequoia_openpgp::policy::StandardPolicy; +use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer}; +use sequoia_openpgp::Cert; + +use openpgp_card_sequoia::card::Admin; +use openpgp_card_sequoia::sq_util; + +use openpgp_card::{card_do::Sex, KeyType}; + +mod cli; +mod util; + +fn main() -> Result<(), Box> { + let cli = cli::Cli::from_args(); + + match cli.cmd { + cli::Command::List {} => { + list_cards()?; + } + cli::Command::Status { ident, verbose } => { + print_status(ident, verbose)?; + } + cli::Command::Decrypt { + ident, + pin_file, + cert_file, + input, + } => { + decrypt(&ident, &pin_file, &cert_file, input.as_deref())?; + } + cli::Command::Sign { + ident, + pin_file, + cert_file, + detached, + input, + } => { + if detached { + sign_detached( + &ident, + &pin_file, + &cert_file, + input.as_deref(), + )?; + } else { + return Err(anyhow::anyhow!( + "Only detached signatures are supported for now" + ) + .into()); + } + } + cli::Command::FactoryReset { ident } => { + factory_reset(&ident)?; + } + cli::Command::Admin { + ident, + pin_file, + cmd, + } => { + let mut open = util::open_card(&ident)?; + let mut admin = util::get_admin(&mut open, &pin_file)?; + + match cmd { + cli::AdminCommand::Name { name } => { + let _ = admin.set_name(&name)?; + } + cli::AdminCommand::Url { url } => { + let _ = admin.set_url(&url)?; + } + cli::AdminCommand::Import { + keyfile, + sig_fp, + dec_fp, + auth_fp, + } => { + let key = Cert::from_file(keyfile)?; + + if (&sig_fp, &dec_fp, &auth_fp) == (&None, &None, &None) { + // If no fingerprint has been provided, we check if + // there is zero or one (sub)key for each keytype, + // and if so, import these keys to the card. + key_import_yolo(admin, &key)?; + } else { + key_import_explicit( + admin, &key, sig_fp, dec_fp, auth_fp, + )?; + } + } + } + } + } + + Ok(()) +} + +fn list_cards() -> Result<()> { + let cards = util::cards()?; + if !cards.is_empty() { + println!("Available OpenPGP cards:"); + + for card in cards { + println!(" {}", card.application_identifier()?.ident()); + } + } else { + println!("No OpenPGP cards found."); + } + Ok(()) +} + +fn print_status(ident: Option, verbose: bool) -> Result<()> { + let mut open = if let Some(ident) = ident { + util::open_card(&ident)? + } else { + let mut cards = util::cards()?; + if cards.len() == 1 { + cards.pop().unwrap() + } else { + return Err(anyhow::anyhow!("Found {} cards", cards.len()).into()); + } + }; + + 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()?; + + if let Some(name) = crd.name() { + 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); + } + + let url = open.url()?; + if !url.is_empty() { + println!("URL: {}", url); + } + + if let Some(lang) = crd.lang() { + let lang = lang + .iter() + .map(|lang| lang.iter().collect::()) + .collect::>() + .join(", "); + println!("Language preferences '{}'", lang); + } + + // information about subkeys + + let fps = open.fingerprints()?; + let kgt = open.key_generation_times()?; + + println!(); + println!( + "Signature key ({})", + open.algorithm_attributes(KeyType::Signing)?, + ); + if let Some(fp) = fps.signature() { + println!(" fingerprint: {}", fp.to_spaced_hex()); + } + if let Some(kgt) = kgt.signature() { + println! {" created: {}",kgt.formatted()}; + } + + println!(); + println!( + "Decryption key ({})", + open.algorithm_attributes(KeyType::Decryption)?, + ); + if let Some(fp) = fps.decryption() { + println!(" fingerprint: {}", fp.to_spaced_hex()); + } + if let Some(kgt) = kgt.decryption() { + println! {" created: {}",kgt.formatted()}; + } + + println!(); + println!( + "Authentication key ({})", + open.algorithm_attributes(KeyType::Authentication)?, + ); + if let Some(fp) = fps.authentication() { + println!(" fingerprint: {}", fp.to_spaced_hex()); + } + if let Some(kgt) = kgt.authentication() { + println! {" created: {}",kgt.formatted()}; + } + + // technical details about the card and its state + + println!(); + + let sst = open.security_support_template()?; + println!("Signature counter: {}", sst.get_signature_count()); + + let pws = open.pw_status_bytes()?; + + println!( + "Signature pin only valid once: {}", + pws.get_pw1_cds_valid_once() + ); + + println!("Password validation retry count:"); + println!( + " user pw: {}, reset: {}, admin pw: {}", + pws.get_err_count_pw1(), + pws.get_err_count_rst(), + pws.get_err_count_pw3(), + ); + + // FIXME: add General key info; login data; KDF setting + + if verbose { + if let Some(ai) = open.algorithm_information()? { + println!(); + println!("Supported algorithms:"); + println!("{}", ai); + } + } + + Ok(()) +} + +fn decrypt( + ident: &str, + pin_file: &Path, + cert_file: &Path, + input: Option<&Path>, +) -> Result<(), Box> { + let p = StandardPolicy::new(); + let cert = Cert::from_file(cert_file)?; + + let input = util::open_or_stdin(input.as_deref())?; + + let mut open = util::open_card(&ident)?; + let mut user = util::get_user(&mut open, &pin_file)?; + let d = user.decryptor(&cert, &p)?; + + 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: &Path, + cert_file: &Path, + input: Option<&Path>, +) -> Result<(), Box> { + let p = StandardPolicy::new(); + let cert = Cert::from_file(cert_file)?; + + let mut input = util::open_or_stdin(input.as_deref())?; + + let mut open = util::open_card(&ident)?; + let mut sign = util::get_sign(&mut open, &pin_file)?; + let s = sign.signer(&cert, &p)?; + + 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); + util::open_card(ident)?.factory_reset() +} + +fn key_import_yolo(mut admin: Admin, key: &Cert) -> Result<()> { + let p = StandardPolicy::new(); + + let sig = + openpgp_card_sequoia::sq_util::get_subkey(&key, &p, KeyType::Signing)?; + + let dec = openpgp_card_sequoia::sq_util::get_subkey( + &key, + &p, + KeyType::Decryption, + )?; + + let auth = openpgp_card_sequoia::sq_util::get_subkey( + &key, + &p, + KeyType::Authentication, + )?; + + if let Some(sig) = sig { + println!("Uploading {} as signing key", sig.fingerprint()); + admin.upload_key(sig, KeyType::Signing, None)?; + } + if let Some(dec) = dec { + println!("Uploading {} as decryption key", dec.fingerprint()); + admin.upload_key(dec, KeyType::Decryption, None)?; + } + if let Some(auth) = auth { + println!("Uploading {} as authentication key", auth.fingerprint()); + admin.upload_key(auth, KeyType::Authentication, None)?; + } + + Ok(()) +} + +fn key_import_explicit( + mut admin: Admin, + key: &Cert, + sig_fp: Option, + dec_fp: Option, + auth_fp: Option, +) -> Result<()> { + let p = StandardPolicy::new(); + + if let Some(sig_fp) = sig_fp { + if let Some(sig) = + sq_util::get_subkey_by_fingerprint(&key, &p, &sig_fp)? + { + println!("Uploading {} as signing key", sig.fingerprint()); + admin.upload_key(sig, KeyType::Signing, None)?; + } else { + println!("ERROR: Couldn't find {} as signing key", sig_fp); + } + } + + if let Some(dec_fp) = dec_fp { + if let Some(dec) = + sq_util::get_subkey_by_fingerprint(&key, &p, &dec_fp)? + { + println!("Uploading {} as decryption key", dec.fingerprint()); + admin.upload_key(dec, KeyType::Decryption, None)?; + } else { + println!("ERROR: Couldn't find {} as decryption key", dec_fp); + } + } + + if let Some(auth_fp) = auth_fp { + if let Some(auth) = + sq_util::get_subkey_by_fingerprint(&key, &p, &auth_fp)? + { + println!("Uploading {} as authentication key", auth.fingerprint()); + admin.upload_key(auth, KeyType::Authentication, None)?; + } else { + println!("ERROR: Couldn't find {} as authentication key", auth_fp); + } + } + + Ok(()) +} diff --git a/tools/src/bin/opgpcard/util.rs b/tools/src/bin/opgpcard/util.rs new file mode 100644 index 0000000..78e7181 --- /dev/null +++ b/tools/src/bin/opgpcard/util.rs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::{Context, Result}; + +use openpgp_card::Error; +use openpgp_card_pcsc::PcscClient; +use openpgp_card_sequoia::card::{Admin, Open, Sign, User}; +use std::path::Path; + +pub(crate) fn cards() -> Result> { + let mut cards = vec![]; + + for card in PcscClient::cards()? { + cards.push(Open::open_card(card)?); + } + + Ok(cards) +} + +pub(crate) fn open_card(ident: &str) -> Result { + let ccb = PcscClient::open_by_ident(ident)?; + Open::open_card(ccb) +} + +pub(crate) fn get_user<'a>( + open: &'a mut Open, + pin_file: &Path, +) -> Result, Box> { + open.verify_user(&get_pin(pin_file)?)?; + open.user_card() + .ok_or_else(|| anyhow::anyhow!("Couldn't get user access").into()) +} + +pub(crate) fn get_sign<'a>( + open: &'a mut Open, + pin_file: &Path, +) -> Result, Box> { + open.verify_user_for_signing(&get_pin(pin_file)?)?; + open.signing_card() + .ok_or_else(|| anyhow::anyhow!("Couldn't get sign access").into()) +} + +pub(crate) fn get_admin<'a>( + open: &'a mut Open, + pin_file: &Path, +) -> Result, Box> { + open.verify_admin(&get_pin(pin_file)?)?; + open.admin_card() + .ok_or_else(|| anyhow::anyhow!("Couldn't get admin access").into()) +} + +fn get_pin(pin_file: &Path) -> Result { + let pin = std::fs::read_to_string(pin_file)?; + Ok(pin.trim().to_string()) +} + +pub(crate) fn open_or_stdin( + f: Option<&Path>, +) -> Result> { + match f { + Some(f) => Ok(Box::new( + std::fs::File::open(f).context("Failed to open input file")?, + )), + None => Ok(Box::new(std::io::stdin())), + } +}