From 4e575685a150fd1197aea62d9cad752bf5558152 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Tue, 25 Oct 2022 18:37:41 +0200 Subject: [PATCH 01/17] opgpcard: Make default output values type safe --- tools/src/bin/opgpcard/cli.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index d5874ec..92579cd 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -7,8 +7,8 @@ use std::path::PathBuf; use crate::{OutputFormat, OutputVersion}; -pub const DEFAULT_OUTPUT_VERSION: &str = "0.9.0"; pub const OUTPUT_VERSIONS: &[OutputVersion] = &[OutputVersion::new(0, 9, 0)]; +pub const DEFAULT_OUTPUT_VERSION: OutputVersion = OutputVersion::new(0, 9, 0); #[derive(Parser, Debug)] #[clap( @@ -20,11 +20,11 @@ pub const OUTPUT_VERSIONS: &[OutputVersion] = &[OutputVersion::new(0, 9, 0)]; )] pub struct Cli { /// Produce output in the chosen format. - #[clap(long, value_enum, default_value = "text")] + #[clap(long, value_enum, default_value_t = OutputFormat::Text)] pub output_format: OutputFormat, /// Pick output version to use, for non-textual formats. - #[clap(long, default_value = DEFAULT_OUTPUT_VERSION)] + #[clap(long, default_value_t = DEFAULT_OUTPUT_VERSION)] pub output_version: OutputVersion, #[clap(subcommand)] From f0ab24b040133319457295d505a8b009dd1fff65 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 10:59:34 +0200 Subject: [PATCH 02/17] opgpcard: Extract status command into module --- tools/src/bin/opgpcard/cli.rs | 13 +- tools/src/bin/opgpcard/commands/mod.rs | 5 + tools/src/bin/opgpcard/commands/status.rs | 206 ++++++++++++++++++++++ tools/src/bin/opgpcard/main.rs | 190 +------------------- 4 files changed, 216 insertions(+), 198 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/mod.rs create mode 100644 tools/src/bin/opgpcard/commands/status.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 92579cd..2aadee6 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -5,6 +5,7 @@ use clap::{AppSettings, Parser, ValueEnum}; use std::path::PathBuf; +use crate::commands; use crate::{OutputFormat, OutputVersion}; pub const OUTPUT_VERSIONS: &[OutputVersion] = &[OutputVersion::new(0, 9, 0)]; @@ -41,17 +42,7 @@ pub enum Command { List {}, /// Show information about the data on a card - Status { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - - #[clap(name = "verbose", short = 'v', long = "verbose")] - verbose: bool, - - /// Print public key material for each key slot - #[clap(name = "pkm", short = 'K', long = "public-key-material")] - pkm: bool, - }, + Status(commands::status::StatusCommand), /// Show technical details about a card Info { diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs new file mode 100644 index 0000000..1cf4238 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2022 Nora Widdecke +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pub mod status; diff --git a/tools/src/bin/opgpcard/commands/status.rs b/tools/src/bin/opgpcard/commands/status.rs new file mode 100644 index 0000000..f4b5405 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/status.rs @@ -0,0 +1,206 @@ +// 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::Result; +use clap::Parser; + +use openpgp_card_sequoia::card::Card; +use openpgp_card_sequoia::types::KeyType; + +use crate::output; +use crate::pick_card_for_reading; +use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; + +#[derive(Parser, Debug)] +pub struct StatusCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + pub ident: Option, + + #[clap(name = "verbose", short = 'v', long = "verbose")] + pub verbose: bool, + + /// Print public key material for each key slot + #[clap(name = "pkm", short = 'K', long = "public-key-material")] + pub pkm: bool, +} + +pub fn print_status( + format: OutputFormat, + output_version: OutputVersion, + command: StatusCommand, +) -> Result<()> { + let mut output = output::Status::default(); + output.verbose(command.verbose); + + let backend = pick_card_for_reading(command.ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + output.ident(open.application_identifier()?.ident()); + + let ai = open.application_identifier()?; + let version = ai.version().to_be_bytes(); + output.card_version(format!("{}.{}", version[0], version[1])); + + // card / cardholder metadata + let crd = open.cardholder_related_data()?; + + 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(); + + // // 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(" "); + + output.card_holder(name); + } + + let url = open.url()?; + if !url.is_empty() { + output.url(url); + } + + if let Some(lang) = crd.lang() { + for lang in lang { + output.language_preference(format!("{}", lang)); + } + } + + // key information (imported vs. generated on card) + let ki = open.key_information().ok().flatten(); + + let pws = open.pw_status_bytes()?; + + // information about subkeys + + let fps = open.fingerprints()?; + let kgt = open.key_generation_times()?; + + let mut signature_key = output::KeySlotInfo::default(); + if let Some(fp) = fps.signature() { + signature_key.fingerprint(fp.to_spaced_hex()); + } + signature_key.algorithm(format!("{}", open.algorithm_attributes(KeyType::Signing)?)); + if let Some(kgt) = kgt.signature() { + signature_key.created(format!("{}", kgt.to_datetime())); + } + if let Some(uif) = open.uif_signing()? { + signature_key.touch_policy(format!("{}", uif.touch_policy())); + signature_key.touch_features(format!("{}", uif.features())); + } + if let Some(ks) = ki.as_ref().map(|ki| ki.sig_status()) { + signature_key.status(format!("{}", ks)); + } + + if pws.pw1_cds_valid_once() { + signature_key.pin_valid_once(); + } + + if command.pkm { + if let Ok(pkm) = open.public_key(KeyType::Signing) { + signature_key.public_key_material(pkm.to_string()); + } + } + + output.signature_key(signature_key); + + let sst = open.security_support_template()?; + output.signature_count(sst.signature_count()); + + let mut decryption_key = output::KeySlotInfo::default(); + if let Some(fp) = fps.decryption() { + decryption_key.fingerprint(fp.to_spaced_hex()); + } + decryption_key.algorithm(format!( + "{}", + open.algorithm_attributes(KeyType::Decryption)? + )); + if let Some(kgt) = kgt.decryption() { + decryption_key.created(format!("{}", kgt.to_datetime())); + } + if let Some(uif) = open.uif_decryption()? { + decryption_key.touch_policy(format!("{}", uif.touch_policy())); + decryption_key.touch_features(format!("{}", uif.features())); + } + if let Some(ks) = ki.as_ref().map(|ki| ki.dec_status()) { + decryption_key.status(format!("{}", ks)); + } + if command.pkm { + if let Ok(pkm) = open.public_key(KeyType::Decryption) { + decryption_key.public_key_material(pkm.to_string()); + } + } + output.decryption_key(decryption_key); + + let mut authentication_key = output::KeySlotInfo::default(); + if let Some(fp) = fps.authentication() { + authentication_key.fingerprint(fp.to_spaced_hex()); + } + authentication_key.algorithm(format!( + "{}", + open.algorithm_attributes(KeyType::Authentication)? + )); + if let Some(kgt) = kgt.authentication() { + authentication_key.created(format!("{}", kgt.to_datetime())); + } + if let Some(uif) = open.uif_authentication()? { + authentication_key.touch_policy(format!("{}", uif.touch_policy())); + authentication_key.touch_features(format!("{}", uif.features())); + } + if let Some(ks) = ki.as_ref().map(|ki| ki.aut_status()) { + authentication_key.status(format!("{}", ks)); + } + if command.pkm { + if let Ok(pkm) = open.public_key(KeyType::Authentication) { + authentication_key.public_key_material(pkm.to_string()); + } + } + output.authentication_key(authentication_key); + + // technical details about the card's state + + output.user_pin_remaining_attempts(pws.err_count_pw1()); + output.admin_pin_remaining_attempts(pws.err_count_pw3()); + output.reset_code_remaining_attempts(pws.err_count_rc()); + + // FIXME: Handle attestation key information as a separate + // KeySlotInfo! Attestation touch information should go into its + // own `Option`, and (if any information about the + // attestation key exists at all, which is not the case for most + // cards) it should be printed as a fourth KeySlot block. + if let Some(uif) = open.uif_attestation()? { + output.card_touch_policy(uif.touch_policy().to_string()); + output.card_touch_features(uif.features().to_string()); + } + + if let Some(ki) = ki { + let num = ki.num_additional(); + for i in 0..num { + output.key_status(ki.additional_ref(i), ki.additional_status(i).to_string()); + } + } + + if let Ok(fps) = open.ca_fingerprints() { + for fp in fps.iter().flatten() { + output.ca_fingerprint(fp.to_string()); + } + } + + // FIXME: print "Login Data" + + println!("{}", output.print(format, output_version)?); + + Ok(()) +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index def94e8..9e0b1ea 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -28,6 +28,7 @@ use crate::util::{load_pin, print_gnuk_note}; use std::io::Write; mod cli; +mod commands; mod output; mod util; mod versioned_output; @@ -50,12 +51,8 @@ fn main() -> Result<(), Box> { cli::Command::List {} => { list_cards(cli.output_format, cli.output_version)?; } - cli::Command::Status { - ident, - verbose, - pkm, - } => { - print_status(cli.output_format, cli.output_version, ident, verbose, pkm)?; + cli::Command::Status(cmd) => { + commands::status::print_status(cli.output_format, cli.output_version, cmd)?; } cli::Command::Info { ident } => { print_info(cli.output_format, cli.output_version, ident)?; @@ -610,187 +607,6 @@ fn pick_card_for_reading(ident: Option) -> Result, - verbose: bool, - pkm: bool, -) -> Result<()> { - let mut output = output::Status::default(); - output.verbose(verbose); - - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - output.ident(open.application_identifier()?.ident()); - - let ai = open.application_identifier()?; - let version = ai.version().to_be_bytes(); - output.card_version(format!("{}.{}", version[0], version[1])); - - // card / cardholder metadata - let crd = open.cardholder_related_data()?; - - 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(); - - // // 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(" "); - - output.card_holder(name); - } - - let url = open.url()?; - if !url.is_empty() { - output.url(url); - } - - if let Some(lang) = crd.lang() { - for lang in lang { - output.language_preference(format!("{}", lang)); - } - } - - // key information (imported vs. generated on card) - let ki = open.key_information().ok().flatten(); - - let pws = open.pw_status_bytes()?; - - // information about subkeys - - let fps = open.fingerprints()?; - let kgt = open.key_generation_times()?; - - let mut signature_key = output::KeySlotInfo::default(); - if let Some(fp) = fps.signature() { - signature_key.fingerprint(fp.to_spaced_hex()); - } - signature_key.algorithm(format!("{}", open.algorithm_attributes(KeyType::Signing)?)); - if let Some(kgt) = kgt.signature() { - signature_key.created(format!("{}", kgt.to_datetime())); - } - if let Some(uif) = open.uif_signing()? { - signature_key.touch_policy(format!("{}", uif.touch_policy())); - signature_key.touch_features(format!("{}", uif.features())); - } - if let Some(ks) = ki.as_ref().map(|ki| ki.sig_status()) { - signature_key.status(format!("{}", ks)); - } - - if pws.pw1_cds_valid_once() { - signature_key.pin_valid_once(); - } - - if pkm { - if let Ok(pkm) = open.public_key(KeyType::Signing) { - signature_key.public_key_material(pkm.to_string()); - } - } - - output.signature_key(signature_key); - - let sst = open.security_support_template()?; - output.signature_count(sst.signature_count()); - - let mut decryption_key = output::KeySlotInfo::default(); - if let Some(fp) = fps.decryption() { - decryption_key.fingerprint(fp.to_spaced_hex()); - } - decryption_key.algorithm(format!( - "{}", - open.algorithm_attributes(KeyType::Decryption)? - )); - if let Some(kgt) = kgt.decryption() { - decryption_key.created(format!("{}", kgt.to_datetime())); - } - if let Some(uif) = open.uif_decryption()? { - decryption_key.touch_policy(format!("{}", uif.touch_policy())); - decryption_key.touch_features(format!("{}", uif.features())); - } - if let Some(ks) = ki.as_ref().map(|ki| ki.dec_status()) { - decryption_key.status(format!("{}", ks)); - } - if pkm { - if let Ok(pkm) = open.public_key(KeyType::Decryption) { - decryption_key.public_key_material(pkm.to_string()); - } - } - output.decryption_key(decryption_key); - - let mut authentication_key = output::KeySlotInfo::default(); - if let Some(fp) = fps.authentication() { - authentication_key.fingerprint(fp.to_spaced_hex()); - } - authentication_key.algorithm(format!( - "{}", - open.algorithm_attributes(KeyType::Authentication)? - )); - if let Some(kgt) = kgt.authentication() { - authentication_key.created(format!("{}", kgt.to_datetime())); - } - if let Some(uif) = open.uif_authentication()? { - authentication_key.touch_policy(format!("{}", uif.touch_policy())); - authentication_key.touch_features(format!("{}", uif.features())); - } - if let Some(ks) = ki.as_ref().map(|ki| ki.aut_status()) { - authentication_key.status(format!("{}", ks)); - } - if pkm { - if let Ok(pkm) = open.public_key(KeyType::Authentication) { - authentication_key.public_key_material(pkm.to_string()); - } - } - output.authentication_key(authentication_key); - - // technical details about the card's state - - output.user_pin_remaining_attempts(pws.err_count_pw1()); - output.admin_pin_remaining_attempts(pws.err_count_pw3()); - output.reset_code_remaining_attempts(pws.err_count_rc()); - - // FIXME: Handle attestation key information as a separate - // KeySlotInfo! Attestation touch information should go into its - // own `Option`, and (if any information about the - // attestation key exists at all, which is not the case for most - // cards) it should be printed as a fourth KeySlot block. - if let Some(uif) = open.uif_attestation()? { - output.card_touch_policy(uif.touch_policy().to_string()); - output.card_touch_features(uif.features().to_string()); - } - - if let Some(ki) = ki { - let num = ki.num_additional(); - for i in 0..num { - output.key_status(ki.additional_ref(i), ki.additional_status(i).to_string()); - } - } - - if let Ok(fps) = open.ca_fingerprints() { - for fp in fps.iter().flatten() { - output.ca_fingerprint(fp.to_string()); - } - } - - // FIXME: print "Login Data" - - println!("{}", output.print(format, output_version)?); - - Ok(()) -} - /// print metadata information about a card fn print_info( format: OutputFormat, From d05feec605d4cc9ac82652f3d5a462ad7d892b68 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 11:22:56 +0200 Subject: [PATCH 03/17] opgpcard: Extract info command into module --- tools/src/bin/opgpcard/cli.rs | 5 +- tools/src/bin/opgpcard/commands/info.rs | 89 +++++++++++++++++++++++++ tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/main.rs | 74 +------------------- 4 files changed, 93 insertions(+), 76 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/info.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 2aadee6..c22125a 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -45,10 +45,7 @@ pub enum Command { Status(commands::status::StatusCommand), /// Show technical details about a card - Info { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - }, + Info(commands::info::InfoCommand), /// Display a card's authentication key as an SSH public key Ssh { diff --git a/tools/src/bin/opgpcard/commands/info.rs b/tools/src/bin/opgpcard/commands/info.rs new file mode 100644 index 0000000..635e857 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/info.rs @@ -0,0 +1,89 @@ +// 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::Result; +use clap::Parser; + +use openpgp_card_sequoia::card::Card; + +use crate::output; +use crate::pick_card_for_reading; +use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; + +#[derive(Parser, Debug)] +pub struct InfoCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + pub ident: Option, +} + +/// print metadata information about a card +pub fn print_info( + format: OutputFormat, + output_version: OutputVersion, + command: InfoCommand, +) -> Result<()> { + let mut output = output::Info::default(); + + let backend = pick_card_for_reading(command.ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + let ai = open.application_identifier()?; + + output.ident(ai.ident()); + + let version = ai.version().to_be_bytes(); + output.card_version(format!("{}.{}", version[0], version[1])); + + output.application_id(ai.to_string()); + output.manufacturer_id(format!("{:04X}", ai.manufacturer())); + output.manufacturer_name(ai.manufacturer_name().to_string()); + + if let Some(cc) = open.historical_bytes()?.card_capabilities() { + for line in cc.to_string().lines() { + let line = line.strip_prefix("- ").unwrap_or(line); + output.card_capability(line.to_string()); + } + } + if let Some(csd) = open.historical_bytes()?.card_service_data() { + for line in csd.to_string().lines() { + let line = line.strip_prefix("- ").unwrap_or(line); + output.card_service_data(line.to_string()); + } + } + + if let Some(eli) = open.extended_length_information()? { + for line in eli.to_string().lines() { + let line = line.strip_prefix("- ").unwrap_or(line); + output.extended_length_info(line.to_string()); + } + } + + let ec = open.extended_capabilities()?; + for line in ec.to_string().lines() { + let line = line.strip_prefix("- ").unwrap_or(line); + output.extended_capability(line.to_string()); + } + + // Algorithm information (list of supported algorithms) + if let Ok(Some(ai)) = open.algorithm_information() { + for line in ai.to_string().lines() { + let line = line.strip_prefix("- ").unwrap_or(line); + output.algorithm(line.to_string()); + } + } + + // 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("."); + output.firmware_version(ver); + } + + println!("{}", output.print(format, output_version)?); + + Ok(()) +} diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index 1cf4238..328ce90 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -2,4 +2,5 @@ // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 +pub mod info; pub mod status; diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 9e0b1ea..18067f2 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -54,8 +54,8 @@ fn main() -> Result<(), Box> { cli::Command::Status(cmd) => { commands::status::print_status(cli.output_format, cli.output_version, cmd)?; } - cli::Command::Info { ident } => { - print_info(cli.output_format, cli.output_version, ident)?; + cli::Command::Info(cmd) => { + commands::info::print_info(cli.output_format, cli.output_version, cmd)?; } cli::Command::Ssh { ident } => { print_ssh(cli.output_format, cli.output_version, ident)?; @@ -607,76 +607,6 @@ fn pick_card_for_reading(ident: Option) -> Result, -) -> Result<()> { - let mut output = output::Info::default(); - - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - let ai = open.application_identifier()?; - - output.ident(ai.ident()); - - let version = ai.version().to_be_bytes(); - output.card_version(format!("{}.{}", version[0], version[1])); - - output.application_id(ai.to_string()); - output.manufacturer_id(format!("{:04X}", ai.manufacturer())); - output.manufacturer_name(ai.manufacturer_name().to_string()); - - if let Some(cc) = open.historical_bytes()?.card_capabilities() { - for line in cc.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.card_capability(line.to_string()); - } - } - if let Some(csd) = open.historical_bytes()?.card_service_data() { - for line in csd.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.card_service_data(line.to_string()); - } - } - - if let Some(eli) = open.extended_length_information()? { - for line in eli.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.extended_length_info(line.to_string()); - } - } - - let ec = open.extended_capabilities()?; - for line in ec.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.extended_capability(line.to_string()); - } - - // Algorithm information (list of supported algorithms) - if let Ok(Some(ai)) = open.algorithm_information() { - for line in ai.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.algorithm(line.to_string()); - } - } - - // 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("."); - output.firmware_version(ver); - } - - println!("{}", output.print(format, output_version)?); - - Ok(()) -} - fn print_ssh( format: OutputFormat, output_version: OutputVersion, From 660ba2d3bb4054ce364b315c8128b41cddd61bac Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 11:31:29 +0200 Subject: [PATCH 04/17] opgpcard: Extract ssh command into module --- tools/src/bin/opgpcard/cli.rs | 5 +-- tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/commands/ssh.rs | 55 ++++++++++++++++++++++++++ tools/src/bin/opgpcard/main.rs | 36 +---------------- 4 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/ssh.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index c22125a..248050e 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -48,10 +48,7 @@ pub enum Command { Info(commands::info::InfoCommand), /// Display a card's authentication key as an SSH public key - Ssh { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - }, + Ssh(commands::ssh::SshCommand), /// Export the key data on a card as an OpenPGP public key Pubkey { diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index 328ce90..67deb6e 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -3,4 +3,5 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pub mod info; +pub mod ssh; pub mod status; diff --git a/tools/src/bin/opgpcard/commands/ssh.rs b/tools/src/bin/opgpcard/commands/ssh.rs new file mode 100644 index 0000000..8110da7 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/ssh.rs @@ -0,0 +1,55 @@ +// 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::Result; +use clap::Parser; + +use openpgp_card_sequoia::card::Card; +use openpgp_card_sequoia::types::KeyType; + +use crate::output; +use crate::pick_card_for_reading; +use crate::util; +use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; + +#[derive(Parser, Debug)] +pub struct SshCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + pub ident: Option, +} + +pub fn print_ssh( + format: OutputFormat, + output_version: OutputVersion, + command: SshCommand, +) -> Result<()> { + let mut output = output::Ssh::default(); + + let ident = command.ident; + + let backend = pick_card_for_reading(ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + let ident = open.application_identifier()?.ident(); + output.ident(ident.clone()); + + // Print fingerprint of authentication subkey + let fps = open.fingerprints()?; + + if let Some(fp) = fps.authentication() { + output.authentication_key_fingerprint(fp.to_string()); + } + + // 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) { + output.ssh_public_key(ssh); + } + } + + println!("{}", output.print(format, output_version)?); + Ok(()) +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 18067f2..b06d6fe 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -57,8 +57,8 @@ fn main() -> Result<(), Box> { cli::Command::Info(cmd) => { commands::info::print_info(cli.output_format, cli.output_version, cmd)?; } - cli::Command::Ssh { ident } => { - print_ssh(cli.output_format, cli.output_version, ident)?; + cli::Command::Ssh(cmd) => { + commands::ssh::print_ssh(cli.output_format, cli.output_version, cmd)?; } cli::Command::Pubkey { ident, @@ -607,38 +607,6 @@ fn pick_card_for_reading(ident: Option) -> Result, -) -> Result<()> { - let mut output = output::Ssh::default(); - - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - let ident = open.application_identifier()?.ident(); - output.ident(ident.clone()); - - // Print fingerprint of authentication subkey - let fps = open.fingerprints()?; - - if let Some(fp) = fps.authentication() { - output.authentication_key_fingerprint(fp.to_string()); - } - - // 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) { - output.ssh_public_key(ssh); - } - } - - println!("{}", output.print(format, output_version)?); - Ok(()) -} - fn print_pubkey( format: OutputFormat, output_version: OutputVersion, From 3ff4127fff8193ff4809702185711f5d1a195456 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 11:48:16 +0200 Subject: [PATCH 05/17] opgpcard: Extract pubkey command into module --- tools/src/bin/opgpcard/cli.rs | 12 +-- tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/commands/pubkey.rs | 102 ++++++++++++++++++++++ tools/src/bin/opgpcard/main.rs | 89 +------------------ 4 files changed, 107 insertions(+), 97 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/pubkey.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 248050e..7ed6696 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -51,17 +51,7 @@ pub enum Command { Ssh(commands::ssh::SshCommand), /// Export the key data on a card as an OpenPGP public key - Pubkey { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] - user_pin: Option, - - /// User ID to add to the exported certificate representation - #[clap(name = "User ID", short = 'u', long = "userid")] - user_id: Vec, - }, + Pubkey(commands::pubkey::PubkeyCommand), /// Administer data on a card (including keys and metadata) Admin { diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index 67deb6e..590a30a 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -3,5 +3,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pub mod info; +pub mod pubkey; pub mod ssh; pub mod status; diff --git a/tools/src/bin/opgpcard/commands/pubkey.rs b/tools/src/bin/opgpcard/commands/pubkey.rs new file mode 100644 index 0000000..9673643 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/pubkey.rs @@ -0,0 +1,102 @@ +// 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::Result; +use clap::Parser; + +use std::path::PathBuf; + +use sequoia_openpgp::serialize::SerializeInto; + +use openpgp_card_sequoia::card::Card; +use openpgp_card_sequoia::types::KeyType; +use openpgp_card_sequoia::util::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 { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + user_pin: Option, + + /// User ID to add to the exported certificate representation + #[clap(name = "User ID", short = 'u', long = "userid")] + user_ids: Vec, +} + +pub fn print_pubkey( + format: OutputFormat, + output_version: OutputVersion, + command: PubkeyCommand, +) -> Result<()> { + let mut output = output::PublicKey::default(); + + let backend = pick_card_for_reading(command.ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + let ident = open.application_identifier()?.ident(); + output.ident(ident); + + let user_pin = util::get_pin(&mut open, command.user_pin, crate::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 = crate::get_cert( + &mut open, + key_sig, + key_dec, + key_aut, + user_pin.as_deref(), + &command.user_ids, + &|| println!("Enter User PIN on card reader pinpad."), + )?; + + let armored = String::from_utf8(cert.armored().to_vec()?)?; + output.public_key(armored); + + println!("{}", output.print(format, output_version)?); + Ok(()) +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index b06d6fe..5e8e848 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -19,9 +19,7 @@ use sequoia_openpgp::Cert; use openpgp_card_sequoia::card::{Admin, Card, Open}; use openpgp_card_sequoia::types::{AlgoSimple, CardBackend, KeyType, TouchPolicy}; -use openpgp_card_sequoia::util::{ - make_cert, public_key_material_and_fp_to_key, public_key_material_to_key, -}; +use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key}; use openpgp_card_sequoia::{sq_util, PublicKey}; use crate::util::{load_pin, print_gnuk_note}; @@ -60,18 +58,8 @@ fn main() -> Result<(), Box> { cli::Command::Ssh(cmd) => { commands::ssh::print_ssh(cli.output_format, cli.output_version, cmd)?; } - cli::Command::Pubkey { - ident, - user_pin, - user_id, - } => { - print_pubkey( - cli.output_format, - cli.output_version, - ident, - user_pin, - user_id, - )?; + cli::Command::Pubkey(cmd) => { + commands::pubkey::print_pubkey(cli.output_format, cli.output_version, cmd)?; } cli::Command::SetIdentity { ident, id } => { set_identity(&ident, id)?; @@ -607,77 +595,6 @@ fn pick_card_for_reading(ident: Option) -> Result, - user_pin: Option, - user_ids: Vec, -) -> Result<()> { - let mut output = output::PublicKey::default(); - - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - let ident = open.application_identifier()?.ident(); - output.ident(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()?)?; - output.public_key(armored); - - println!("{}", output.print(format, output_version)?); - Ok(()) -} - fn decrypt( ident: &str, pin_file: Option, From 9e5e30cea4a6dc7331bfe16516a1f6c0ce91dc0e Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 12:01:39 +0200 Subject: [PATCH 06/17] opgpcard: Extract decrypt command into module --- tools/src/bin/opgpcard/cli.rs | 12 +---- tools/src/bin/opgpcard/commands/decrypt.rs | 56 ++++++++++++++++++++++ tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/main.rs | 40 ++-------------- 4 files changed, 61 insertions(+), 48 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/decrypt.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 7ed6696..8328fd5 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -75,17 +75,7 @@ pub enum Command { }, /// Decrypt data using a card - Decrypt { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] - user_pin: Option, - - /// Input file (stdin if unset) - #[clap(name = "input")] - input: Option, - }, + Decrypt(commands::decrypt::DecryptCommand), /// Sign data using a card Sign { diff --git a/tools/src/bin/opgpcard/commands/decrypt.rs b/tools/src/bin/opgpcard/commands/decrypt.rs new file mode 100644 index 0000000..34c7d56 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/decrypt.rs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2022 Nora Widdecke +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::{anyhow, Result}; +use clap::Parser; + +use std::path::PathBuf; + +use sequoia_openpgp::{ + parse::{stream::DecryptorBuilder, Parse}, + policy::StandardPolicy, +}; + +use openpgp_card_sequoia::card::Card; + +use crate::util; + +#[derive(Parser, Debug)] +pub struct DecryptCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: String, + + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + pin_file: Option, + + /// Input file (stdin if unset) + #[clap(name = "input")] + input: Option, +} + +pub fn decrypt(command: DecryptCommand) -> Result<(), Box> { + let p = StandardPolicy::new(); + + let input = util::open_or_stdin(command.input.as_deref())?; + + let backend = util::open_card(&command.ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + if open.fingerprints()?.decryption().is_none() { + return Err(anyhow!("Can't decrypt: this card has no key in the decryption slot.").into()); + } + + let user_pin = util::get_pin(&mut open, command.pin_file, crate::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(()) +} diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index 590a30a..7746aec 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 +pub mod decrypt; pub mod info; pub mod pubkey; pub mod ssh; diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 5e8e848..4087f98 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -10,7 +10,7 @@ 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::parse::Parse; use sequoia_openpgp::policy::{Policy, StandardPolicy}; use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer}; use sequoia_openpgp::serialize::SerializeInto; @@ -64,12 +64,8 @@ fn main() -> Result<(), Box> { 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::Decrypt(cmd) => { + commands::decrypt::decrypt(cmd)?; } cli::Command::Sign { ident, @@ -595,36 +591,6 @@ fn pick_card_for_reading(ident: Option) -> Result, - input: Option<&Path>, -) -> Result<(), Box> { - let p = StandardPolicy::new(); - - let input = util::open_or_stdin(input)?; - - let backend = util::open_card(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - if open.fingerprints()?.decryption().is_none() { - return Err(anyhow!("Can't decrypt: this card has no key in the decryption slot.").into()); - } - - 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, From 361508706598a7ddc3e9b593177c9aeba6c528ca Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 12:25:51 +0200 Subject: [PATCH 07/17] opgpcard: Extract sign command into module --- tools/src/bin/opgpcard/cli.rs | 16 +----- tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/commands/sign.rs | 68 +++++++++++++++++++++++++ tools/src/bin/opgpcard/main.rs | 47 ++--------------- 4 files changed, 73 insertions(+), 59 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/sign.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 8328fd5..714ca23 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -78,21 +78,7 @@ pub enum Command { Decrypt(commands::decrypt::DecryptCommand), /// Sign data using a card - Sign { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - - /// User PIN file - #[clap(short = 'p', long = "user-pin")] - user_pin: Option, - - #[clap(name = "detached", short = 'd', long = "detached")] - detached: bool, - - /// Input file (stdin if unset) - #[clap(name = "input")] - input: Option, - }, + Sign(commands::sign::SignCommand), /// Attestation management (Yubico) Attestation { diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index 7746aec..b692df3 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -5,5 +5,6 @@ pub mod decrypt; pub mod info; pub mod pubkey; +pub mod sign; pub mod ssh; pub mod status; diff --git a/tools/src/bin/opgpcard/commands/sign.rs b/tools/src/bin/opgpcard/commands/sign.rs new file mode 100644 index 0000000..807fac9 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/sign.rs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2022 Nora Widdecke +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::{anyhow, Result}; +use clap::Parser; + +use std::path::{Path, PathBuf}; + +use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer}; + +use openpgp_card_sequoia::card::Card; + +use crate::util; + +#[derive(Parser, Debug)] +pub struct SignCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + pub ident: String, + + /// User PIN file + #[clap(short = 'p', long = "user-pin")] + pub user_pin: Option, + + #[clap(name = "detached", short = 'd', long = "detached")] + pub detached: bool, + + /// Input file (stdin if unset) + #[clap(name = "input")] + pub input: Option, +} + +pub fn sign(command: SignCommand) -> Result<(), Box> { + if command.detached { + sign_detached(&command.ident, command.user_pin, command.input.as_deref()) + } else { + Err(anyhow::anyhow!("Only detached signatures are supported for now").into()) + } +} + +pub fn sign_detached( + ident: &str, + pin_file: Option, + input: Option<&Path>, +) -> Result<(), Box> { + let mut input = util::open_or_stdin(input)?; + + let backend = util::open_card(ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + if open.fingerprints()?.signature().is_none() { + return Err(anyhow!("Can't sign: this card has no key in the signing slot.").into()); + } + + let user_pin = util::get_pin(&mut open, pin_file, crate::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(()) +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 4087f98..8e24c7d 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -5,14 +5,13 @@ use anyhow::{anyhow, Result}; use clap::Parser; use cli::BaseKeySlot; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; 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, StandardPolicy}; -use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer}; use sequoia_openpgp::serialize::SerializeInto; use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; @@ -67,19 +66,8 @@ fn main() -> Result<(), Box> { cli::Command::Decrypt(cmd) => { commands::decrypt::decrypt(cmd)?; } - 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::Sign(cmd) => { + commands::sign::sign(cmd)?; } cli::Command::Attestation { cmd } => match cmd { cli::AttCommand::Cert { ident } => { @@ -591,35 +579,6 @@ fn pick_card_for_reading(ident: Option) -> Result, - input: Option<&Path>, -) -> Result<(), Box> { - let mut input = util::open_or_stdin(input)?; - - let backend = util::open_card(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - if open.fingerprints()?.signature().is_none() { - return Err(anyhow!("Can't sign: this card has no key in the signing slot.").into()); - } - - 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)?; From d0ad41c9f5caa6e7d06ed54442284668991712fb Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 12:37:59 +0200 Subject: [PATCH 08/17] opgpcard: Extract factory_reset command into module --- tools/src/bin/opgpcard/cli.rs | 5 +--- .../bin/opgpcard/commands/factory_reset.rs | 25 +++++++++++++++++++ tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/main.rs | 13 ++-------- 4 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/factory_reset.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 714ca23..11ba087 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -87,10 +87,7 @@ pub enum Command { }, /// Completely reset a card (deletes all data, including the keys on the card!) - FactoryReset { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - }, + FactoryReset(commands::factory_reset::FactoryResetCommand), /// Change identity (applies only to Nitrokey Start) SetIdentity { diff --git a/tools/src/bin/opgpcard/commands/factory_reset.rs b/tools/src/bin/opgpcard/commands/factory_reset.rs new file mode 100644 index 0000000..f472a21 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/factory_reset.rs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2022 Nora Widdecke +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::{anyhow, Result}; +use clap::Parser; + +use openpgp_card_sequoia::card::Card; + +use crate::util; + +#[derive(Parser, Debug)] +pub struct FactoryResetCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: String, +} + +pub fn factory_reset(command: FactoryResetCommand) -> Result<()> { + println!("Resetting Card {}", command.ident); + let card = util::open_card(&command.ident)?; + let mut card = Card::new(card); + + let mut open = card.transaction()?; + open.factory_reset().map_err(|e| anyhow!(e)) +} diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index b692df3..45a2364 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pub mod decrypt; +pub mod factory_reset; pub mod info; pub mod pubkey; pub mod sign; diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 8e24c7d..0c554cc 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -147,8 +147,8 @@ fn main() -> Result<(), Box> { } } }, - cli::Command::FactoryReset { ident } => { - factory_reset(&ident)?; + cli::Command::FactoryReset(cmd) => { + commands::factory_reset::factory_reset(cmd)?; } cli::Command::Admin { ident, @@ -579,15 +579,6 @@ fn pick_card_for_reading(ident: Option) -> Result 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, From 9b7e614772cabfa26ea36d3e33a87ee017e68d1c Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 12:46:03 +0200 Subject: [PATCH 09/17] opgpcard: Extract set_identity command into module --- tools/src/bin/opgpcard/cli.rs | 28 +---------- tools/src/bin/opgpcard/commands/mod.rs | 1 + .../src/bin/opgpcard/commands/set_identity.rs | 48 +++++++++++++++++++ tools/src/bin/opgpcard/main.rs | 13 +---- 4 files changed, 52 insertions(+), 38 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/set_identity.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 11ba087..5eca759 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -90,13 +90,7 @@ pub enum Command { FactoryReset(commands::factory_reset::FactoryResetCommand), /// Change identity (applies only to Nitrokey Start) - SetIdentity { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - - #[clap(name = "identity", value_enum)] - id: SetIdentityId, - }, + SetIdentity(commands::set_identity::SetIdentityCommand), } #[derive(Parser, Debug)] @@ -307,26 +301,6 @@ impl From for openpgp_card_sequoia::types::TouchPolicy { } } -#[derive(ValueEnum, Debug, Clone)] -pub enum SetIdentityId { - #[clap(name = "0")] - Zero, - #[clap(name = "1")] - One, - #[clap(name = "2")] - Two, -} - -impl From for u8 { - fn from(id: SetIdentityId) -> Self { - match id { - SetIdentityId::Zero => 0, - SetIdentityId::One => 1, - SetIdentityId::Two => 2, - } - } -} - #[derive(ValueEnum, Debug, Clone)] #[clap(rename_all = "lower")] pub enum AdminGenerateAlgo { diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index 45a2364..6bbe9e7 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -6,6 +6,7 @@ pub mod decrypt; pub mod factory_reset; pub mod info; pub mod pubkey; +pub mod set_identity; pub mod sign; pub mod ssh; pub mod status; diff --git a/tools/src/bin/opgpcard/commands/set_identity.rs b/tools/src/bin/opgpcard/commands/set_identity.rs new file mode 100644 index 0000000..efb6cef --- /dev/null +++ b/tools/src/bin/opgpcard/commands/set_identity.rs @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2022 Nora Widdecke +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::Result; +use clap::{Parser, ValueEnum}; + +use openpgp_card_sequoia::card::Card; + +use crate::util; + +#[derive(Parser, Debug)] +pub struct SetIdentityCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: String, + + #[clap(name = "identity", value_enum)] + id: SetIdentityId, +} + +#[derive(ValueEnum, Debug, Clone)] +pub enum SetIdentityId { + #[clap(name = "0")] + Zero, + #[clap(name = "1")] + One, + #[clap(name = "2")] + Two, +} + +impl From for u8 { + fn from(id: SetIdentityId) -> Self { + match id { + SetIdentityId::Zero => 0, + SetIdentityId::One => 1, + SetIdentityId::Two => 2, + } + } +} + +pub fn set_identity(command: SetIdentityCommand) -> Result<(), Box> { + let backend = util::open_card(&command.ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + open.set_identity(u8::from(command.id))?; + Ok(()) +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 0c554cc..87263b3 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -60,8 +60,8 @@ fn main() -> Result<(), Box> { cli::Command::Pubkey(cmd) => { commands::pubkey::print_pubkey(cli.output_format, cli.output_version, cmd)?; } - cli::Command::SetIdentity { ident, id } => { - set_identity(&ident, id)?; + cli::Command::SetIdentity(cmd) => { + commands::set_identity::set_identity(cmd)?; } cli::Command::Decrypt(cmd) => { commands::decrypt::decrypt(cmd)?; @@ -548,15 +548,6 @@ fn list_cards(format: OutputFormat, output_version: OutputVersion) -> Result<()> Ok(()) } -fn set_identity(ident: &str, id: cli::SetIdentityId) -> Result<(), Box> { - let backend = util::open_card(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - open.set_identity(u8::from(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> { From b6dfa08d5244f05bb451af50c59b43976a6a529e Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 13:24:15 +0200 Subject: [PATCH 10/17] opgpcard: Extract pin command into module --- tools/src/bin/opgpcard/cli.rs | 56 +----- tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/commands/pin.rs | 267 +++++++++++++++++++++++++ tools/src/bin/opgpcard/main.rs | 207 +------------------ 4 files changed, 271 insertions(+), 260 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/pin.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 5eca759..8e0a2f8 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -66,13 +66,7 @@ pub enum Command { }, /// PIN management (change PINs, reset blocked PINs) - Pin { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - - #[clap(subcommand)] - cmd: PinCommand, - }, + Pin(commands::pin::PinCommand), /// Decrypt data using a card Decrypt(commands::decrypt::DecryptCommand), @@ -155,54 +149,6 @@ pub enum AdminCommand { }, } -#[derive(Parser, Debug)] -pub enum PinCommand { - /// Set User PIN - SetUser { - #[clap(name = "User PIN file old", short = 'p', long = "user-pin-old")] - user_pin_old: Option, - - #[clap(name = "User PIN file new", short = 'q', long = "user-pin-new")] - user_pin_new: Option, - }, - - /// Set Admin PIN - SetAdmin { - #[clap(name = "Admin PIN file old", short = 'P', long = "admin-pin-old")] - admin_pin_old: Option, - - #[clap(name = "Admin PIN file new", short = 'Q', long = "admin-pin-new")] - admin_pin_new: Option, - }, - - /// Reset User PIN with Admin PIN - ResetUser { - #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] - admin_pin: Option, - - #[clap(name = "User PIN file new", short = 'p', long = "user-pin-new")] - user_pin_new: Option, - }, - - /// Set Resetting Code - SetReset { - #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] - admin_pin: Option, - - #[clap(name = "Resetting code file", short = 'r', long = "reset-code")] - reset_code: Option, - }, - - /// Reset User PIN with 'Resetting Code' - ResetUserRc { - #[clap(name = "Resetting Code file", short = 'r', long = "reset-code")] - reset_code: Option, - - #[clap(name = "User PIN file new", short = 'p', long = "user-pin-new")] - user_pin_new: Option, - }, -} - #[derive(Parser, Debug)] pub enum AttCommand { /// Print the card's "Attestation Certificate" diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index 6bbe9e7..be1cbe3 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -5,6 +5,7 @@ pub mod decrypt; pub mod factory_reset; pub mod info; +pub mod pin; pub mod pubkey; pub mod set_identity; pub mod sign; diff --git a/tools/src/bin/opgpcard/commands/pin.rs b/tools/src/bin/opgpcard/commands/pin.rs new file mode 100644 index 0000000..c1a67ae --- /dev/null +++ b/tools/src/bin/opgpcard/commands/pin.rs @@ -0,0 +1,267 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2022 Nora Widdecke +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::path::PathBuf; + +use anyhow::Result; +use clap::Parser; + +use openpgp_card_sequoia::card::Card; + +use crate::util; +use crate::util::{load_pin, print_gnuk_note}; +use crate::{ENTER_ADMIN_PIN, ENTER_USER_PIN}; + +#[derive(Parser, Debug)] +pub struct PinCommand { + #[clap(name = "card ident", short = 'c', long = "card")] + pub ident: String, + + #[clap(subcommand)] + pub cmd: PinSubCommand, +} + +#[derive(Parser, Debug)] +pub enum PinSubCommand { + /// Set User PIN + SetUser { + #[clap(name = "User PIN file old", short = 'p', long = "user-pin-old")] + user_pin_old: Option, + + #[clap(name = "User PIN file new", short = 'q', long = "user-pin-new")] + user_pin_new: Option, + }, + + /// Set Admin PIN + SetAdmin { + #[clap(name = "Admin PIN file old", short = 'P', long = "admin-pin-old")] + admin_pin_old: Option, + + #[clap(name = "Admin PIN file new", short = 'Q', long = "admin-pin-new")] + admin_pin_new: Option, + }, + + /// Reset User PIN with Admin PIN + ResetUser { + #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] + admin_pin: Option, + + #[clap(name = "User PIN file new", short = 'p', long = "user-pin-new")] + user_pin_new: Option, + }, + + /// Set Resetting Code + SetReset { + #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] + admin_pin: Option, + + #[clap(name = "Resetting code file", short = 'r', long = "reset-code")] + reset_code: Option, + }, + + /// Reset User PIN with 'Resetting Code' + ResetUserRc { + #[clap(name = "Resetting Code file", short = 'r', long = "reset-code")] + reset_code: Option, + + #[clap(name = "User PIN file new", short = 'p', long = "user-pin-new")] + user_pin_new: Option, + }, +} + +pub fn pin(ident: &str, cmd: PinSubCommand) -> Result<()> { + let backend = util::open_card(&ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + let pinpad_modify = open.feature_pinpad_modify(); + + match cmd { + PinSubCommand::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."); + } + } + PinSubCommand::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."); + } + + PinSubCommand::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."); + } + } + + PinSubCommand::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()); + }; + } + + PinSubCommand::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(()) +} diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 87263b3..fd0e381 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -21,7 +21,6 @@ use openpgp_card_sequoia::types::{AlgoSimple, CardBackend, KeyType, TouchPolicy} use openpgp_card_sequoia::util::{make_cert, 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; @@ -313,210 +312,8 @@ fn main() -> Result<(), Box> { } } } - cli::Command::Pin { ident, cmd } => { - let backend = util::open_card(&ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - let pinpad_modify = open.feature_pinpad_modify(); - - 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."), - } - } - } + cli::Command::Pin(cmd) => { + commands::pin::pin(&cmd.ident, cmd.cmd)?; } } From 1ecaf396c703b2b40c2b22ac7ed1781b60055f58 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 13:48:32 +0200 Subject: [PATCH 11/17] opgpcard: Reorganize pin --- tools/src/bin/opgpcard/commands/pin.rs | 371 ++++++++++++++----------- 1 file changed, 204 insertions(+), 167 deletions(-) diff --git a/tools/src/bin/opgpcard/commands/pin.rs b/tools/src/bin/opgpcard/commands/pin.rs index c1a67ae..7cfc28c 100644 --- a/tools/src/bin/opgpcard/commands/pin.rs +++ b/tools/src/bin/opgpcard/commands/pin.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use anyhow::Result; use clap::Parser; -use openpgp_card_sequoia::card::Card; +use openpgp_card_sequoia::card::{Card, Open}; use crate::util; use crate::util::{load_pin, print_gnuk_note}; @@ -71,197 +71,234 @@ pub enum PinSubCommand { } pub fn pin(ident: &str, cmd: PinSubCommand) -> Result<()> { - let backend = util::open_card(&ident)?; + let backend = util::open_card(ident)?; let mut card = Card::new(backend); let mut open = card.transaction()?; let pinpad_modify = open.feature_pinpad_modify(); + // TODO de-complicate return, remove question marks match cmd { PinSubCommand::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"); + } => set_user(user_pin_old, user_pin_new, pinpad_modify, open)?, - // 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."); - } - } PinSubCommand::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."); - } + } => set_admin(admin_pin_old, admin_pin_new, pinpad_modify, open)?, + // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::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."); - } - } + } => reset_user(admin_pin, user_pin_new, open)?, + // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::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()); - }; - } + } => set_reset(admin_pin, reset_code, open)?, + // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::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."), - } - } + } => reset_user_rc(reset_code, user_pin_new, open)?, + } + Ok(()) +} + +fn set_user( + user_pin_old: Option, + user_pin_new: Option, + pinpad_modify: bool, + mut open: Open, +) -> Result<()> { + 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."); + } + Ok(()) +} + +fn set_admin( + admin_pin_old: Option, + admin_pin_new: Option, + pinpad_modify: bool, + mut open: Open, +) -> Result<()> { + 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."); + Ok(()) +} + +fn reset_user( + admin_pin: Option, + user_pin_new: Option, + mut open: Open, +) -> Result<()> { + // 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."); + } + Ok(()) +} + +fn set_reset( + admin_pin: Option, + reset_code: Option, + mut open: Open, +) -> Result<()> { + // 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."); + Ok(()) + } else { + Err(anyhow::anyhow!("Failed to use card in admin-mode.").into()) + } +} + +fn reset_user_rc( + reset_code: Option, + user_pin_new: Option, + mut open: Open, +) -> Result<()> { + // 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(()) } From 56d70e3218668a20d51d1978bc9ded3e1c42bb72 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 14:06:40 +0200 Subject: [PATCH 12/17] opgpcard: Clean up returns --- tools/src/bin/opgpcard/commands/pin.rs | 45 ++++++++++++-------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/tools/src/bin/opgpcard/commands/pin.rs b/tools/src/bin/opgpcard/commands/pin.rs index 7cfc28c..6b7d253 100644 --- a/tools/src/bin/opgpcard/commands/pin.rs +++ b/tools/src/bin/opgpcard/commands/pin.rs @@ -77,37 +77,35 @@ pub fn pin(ident: &str, cmd: PinSubCommand) -> Result<()> { let pinpad_modify = open.feature_pinpad_modify(); - // TODO de-complicate return, remove question marks match cmd { PinSubCommand::SetUser { user_pin_old, user_pin_new, - } => set_user(user_pin_old, user_pin_new, pinpad_modify, open)?, + } => set_user(user_pin_old, user_pin_new, pinpad_modify, open), PinSubCommand::SetAdmin { admin_pin_old, admin_pin_new, - } => set_admin(admin_pin_old, admin_pin_new, pinpad_modify, open)?, + } => set_admin(admin_pin_old, admin_pin_new, pinpad_modify, open), // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::ResetUser { admin_pin, user_pin_new, - } => reset_user(admin_pin, user_pin_new, open)?, + } => reset_user(admin_pin, user_pin_new, open), // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::SetReset { admin_pin, reset_code, - } => set_reset(admin_pin, reset_code, open)?, + } => set_reset(admin_pin, reset_code, open), // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::ResetUserRc { reset_code, user_pin_new, - } => reset_user_rc(reset_code, user_pin_new, open)?, + } => reset_user_rc(reset_code, user_pin_new, open), } - Ok(()) } fn set_user( @@ -142,15 +140,13 @@ fn set_user( }) }; - if res.is_err() { - println!("\nFailed to change the User PIN!"); - println!("{:?}", res); - - if let Err(err) = res { + match res { + Err(err) => { + println!("\nFailed to change the User PIN!"); + println!("{:?}", err); print_gnuk_note(err, &open)?; } - } else { - println!("\nUser PIN has been set."); + Ok(_) => println!("\nUser PIN has been set."), } Ok(()) } @@ -220,16 +216,15 @@ fn reset_user( 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()); + return Err(anyhow::anyhow!("Failed to use card in admin-mode.")); }; - if res.is_err() { - println!("\nFailed to change the User PIN!"); - if let Err(err) = res { + match res { + Err(err) => { + println!("\nFailed to change the User PIN!"); print_gnuk_note(err, &open)?; } - } else { - println!("\nUser PIN has been set."); + Ok(_) => println!("\nUser PIN has been set."), } Ok(()) } @@ -265,7 +260,7 @@ fn set_reset( println!("\nResetting code has been set."); Ok(()) } else { - Err(anyhow::anyhow!("Failed to use card in admin-mode.").into()) + Err(anyhow::anyhow!("Failed to use card in admin-mode.")) } } @@ -296,9 +291,11 @@ fn reset_user_rc( match open.reset_user_pin(&rst, &pin) { Err(err) => { println!("\nFailed to change the User PIN!"); - print_gnuk_note(err, &open)?; + print_gnuk_note(err, &open) + } + Ok(_) => { + println!("\nUser PIN has been set."); + Ok(()) } - Ok(_) => println!("\nUser PIN has been set."), } - Ok(()) } From 25ae73711df36e9968752482e891cf27b9cb6379 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 14:08:35 +0200 Subject: [PATCH 13/17] opgpcard: Read pinpad_modify only when needed --- tools/src/bin/opgpcard/commands/pin.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tools/src/bin/opgpcard/commands/pin.rs b/tools/src/bin/opgpcard/commands/pin.rs index 6b7d253..97dac49 100644 --- a/tools/src/bin/opgpcard/commands/pin.rs +++ b/tools/src/bin/opgpcard/commands/pin.rs @@ -73,34 +73,29 @@ pub enum PinSubCommand { pub fn pin(ident: &str, cmd: PinSubCommand) -> Result<()> { let backend = util::open_card(ident)?; let mut card = Card::new(backend); - let mut open = card.transaction()?; - - let pinpad_modify = open.feature_pinpad_modify(); + let open = card.transaction()?; match cmd { PinSubCommand::SetUser { user_pin_old, user_pin_new, - } => set_user(user_pin_old, user_pin_new, pinpad_modify, open), + } => set_user(user_pin_old, user_pin_new, open), PinSubCommand::SetAdmin { admin_pin_old, admin_pin_new, - } => set_admin(admin_pin_old, admin_pin_new, pinpad_modify, open), + } => set_admin(admin_pin_old, admin_pin_new, open), - // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::ResetUser { admin_pin, user_pin_new, } => reset_user(admin_pin, user_pin_new, open), - // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::SetReset { admin_pin, reset_code, } => set_reset(admin_pin, reset_code, open), - // TODO: this doesn't use pinpad_modify, maybe don't compute it before this? PinSubCommand::ResetUserRc { reset_code, user_pin_new, @@ -111,9 +106,10 @@ pub fn pin(ident: &str, cmd: PinSubCommand) -> Result<()> { fn set_user( user_pin_old: Option, user_pin_new: Option, - pinpad_modify: bool, mut open: Open, ) -> Result<()> { + let pinpad_modify = open.feature_pinpad_modify(); + let res = if !pinpad_modify { // get current user pin let user_pin1 = util::get_pin(&mut open, user_pin_old, ENTER_USER_PIN) @@ -154,9 +150,10 @@ fn set_user( fn set_admin( admin_pin_old: Option, admin_pin_new: Option, - pinpad_modify: bool, mut open: Open, ) -> Result<()> { + let pinpad_modify = open.feature_pinpad_modify(); + if !pinpad_modify { // get current admin pin let admin_pin1 = util::get_pin(&mut open, admin_pin_old, ENTER_ADMIN_PIN) From 1be21cfc7fb206421cee6f73a0986b4813964db3 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 14:26:04 +0200 Subject: [PATCH 14/17] opgpcard: Extract attestation command into module --- tools/src/bin/opgpcard/cli.rs | 55 +----- .../src/bin/opgpcard/commands/attestation.rs | 167 ++++++++++++++++++ tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/main.rs | 82 +-------- 4 files changed, 172 insertions(+), 133 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/attestation.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 8e0a2f8..25f2ff4 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -75,10 +75,7 @@ pub enum Command { Sign(commands::sign::SignCommand), /// Attestation management (Yubico) - Attestation { - #[clap(subcommand)] - cmd: AttCommand, - }, + Attestation(commands::attestation::AttestationCommand), /// Completely reset a card (deletes all data, including the keys on the card!) FactoryReset(commands::factory_reset::FactoryResetCommand), @@ -149,56 +146,6 @@ pub enum AdminCommand { }, } -#[derive(Parser, Debug)] -pub enum AttCommand { - /// Print the card's "Attestation Certificate" - Cert { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - }, - - /// Generate "Attestation Statement" for one of the key slots on the card - Generate { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - - #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] - key: BaseKeySlot, - - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] - user_pin: Option, - }, - - /// Print a "cardholder certificate" from the card. - /// This shows the "Attestation Statement", if one has been generated. - Statement { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: Option, - - #[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 for openpgp_card_sequoia::types::KeyType { - fn from(ks: BaseKeySlot) -> Self { - use openpgp_card_sequoia::types::KeyType; - match ks { - BaseKeySlot::Sig => KeyType::Signing, - BaseKeySlot::Dec => KeyType::Decryption, - BaseKeySlot::Aut => KeyType::Authentication, - } - } -} - #[derive(ValueEnum, Debug, Clone)] #[clap(rename_all = "UPPER")] pub enum BasePlusAttKeySlot { diff --git a/tools/src/bin/opgpcard/commands/attestation.rs b/tools/src/bin/opgpcard/commands/attestation.rs new file mode 100644 index 0000000..a6bae12 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/attestation.rs @@ -0,0 +1,167 @@ +// 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 std::path::PathBuf; + +use anyhow::Result; +use clap::{Parser, ValueEnum}; + +use openpgp_card_sequoia::card::Card; +use openpgp_card_sequoia::types::KeyType; + +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" + Cert { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + }, + + /// Generate "Attestation Statement" for one of the key slots on the card + Generate { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: String, + + #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] + key: BaseKeySlot, + + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + user_pin: Option, + }, + + /// Print a "cardholder certificate" from the card. + /// This shows the "Attestation Statement", if one has been generated. + Statement { + #[clap(name = "card ident", short = 'c', long = "card")] + ident: Option, + + #[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 for openpgp_card_sequoia::types::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> { + 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, +) -> Result<(), Box> { + let mut output = output::AttestationCert::default(); + + let backend = pick_card_for_reading(ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + output.ident(open.application_identifier()?.ident()); + + if let Ok(ac) = open.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, +) -> Result<(), Box> { + let backend = util::open_card(ident)?; + let mut card = Card::new(backend); + let mut open = card.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 = KeyType::from(key); + sign.generate_attestation(kt, &|| { + println!("Touch confirmation needed to generate an attestation") + })?; + Ok(()) +} + +fn statement(ident: Option, key: BaseKeySlot) -> Result<(), Box> { + let backend = pick_card_for_reading(ident)?; + let mut card = Card::new(backend); + let mut open = card.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 { + BaseKeySlot::Aut => open.select_data(0, &[0x7F, 0x21], select_data_workaround)?, + BaseKeySlot::Dec => open.select_data(1, &[0x7F, 0x21], select_data_workaround)?, + BaseKeySlot::Sig => open.select_data(2, &[0x7F, 0x21], select_data_workaround)?, + }; + + // 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"); + } + Ok(()) +} diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index be1cbe3..b2027f8 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 +pub mod attestation; pub mod decrypt; pub mod factory_reset; pub mod info; diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index fd0e381..bedda34 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -4,7 +4,6 @@ use anyhow::{anyhow, Result}; use clap::Parser; -use cli::BaseKeySlot; use std::path::PathBuf; use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; @@ -68,84 +67,9 @@ fn main() -> Result<(), Box> { cli::Command::Sign(cmd) => { commands::sign::sign(cmd)?; } - cli::Command::Attestation { cmd } => match cmd { - cli::AttCommand::Cert { ident } => { - let mut output = output::AttestationCert::default(); - - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.transaction()?; - - output.ident(open.application_identifier()?.ident()); - - if let Ok(ac) = open.attestation_certificate() { - let pem = util::pem_encode(ac); - output.attestation_cert(pem); - } - - println!("{}", output.print(cli.output_format, cli.output_version)?); - } - cli::AttCommand::Generate { - ident, - key, - user_pin, - } => { - let backend = util::open_card(&ident)?; - let mut card = Card::new(backend); - let mut open = card.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 = KeyType::from(key); - sign.generate_attestation(kt, &|| { - println!("Touch confirmation needed to generate an attestation") - })?; - } - cli::AttCommand::Statement { ident, key } => { - let backend = pick_card_for_reading(ident)?; - let mut card = Card::new(backend); - let mut open = card.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 { - BaseKeySlot::Aut => { - open.select_data(0, &[0x7F, 0x21], select_data_workaround)? - } - BaseKeySlot::Dec => { - open.select_data(1, &[0x7F, 0x21], select_data_workaround)? - } - BaseKeySlot::Sig => { - open.select_data(2, &[0x7F, 0x21], select_data_workaround)? - } - }; - - // 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::Attestation(cmd) => { + commands::attestation::attestation(cli.output_format, cli.output_version, cmd)?; + } cli::Command::FactoryReset(cmd) => { commands::factory_reset::factory_reset(cmd)?; } From 72f8a1994bfcdf5fc9a26502ace507c74161803b Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 15:19:59 +0200 Subject: [PATCH 15/17] opgpcard: Extract admin command into module --- tools/src/bin/opgpcard/cli.rs | 152 +------ tools/src/bin/opgpcard/commands/admin.rs | 549 +++++++++++++++++++++++ tools/src/bin/opgpcard/commands/mod.rs | 1 + tools/src/bin/opgpcard/main.rs | 325 +------------- 4 files changed, 559 insertions(+), 468 deletions(-) create mode 100644 tools/src/bin/opgpcard/commands/admin.rs diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 25f2ff4..8c73614 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -2,8 +2,7 @@ // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 -use clap::{AppSettings, Parser, ValueEnum}; -use std::path::PathBuf; +use clap::{AppSettings, Parser}; use crate::commands; use crate::{OutputFormat, OutputVersion}; @@ -54,16 +53,7 @@ pub enum Command { Pubkey(commands::pubkey::PubkeyCommand), /// Administer data on a card (including keys and metadata) - Admin { - #[clap(name = "card ident", short = 'c', long = "card")] - ident: String, - - #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] - admin_pin: Option, - - #[clap(subcommand)] - cmd: AdminCommand, - }, + Admin(commands::admin::AdminCommand), /// PIN management (change PINs, reset blocked PINs) Pin(commands::pin::PinCommand), @@ -83,141 +73,3 @@ pub enum Command { /// Change identity (applies only to Nitrokey Start) SetIdentity(commands::set_identity::SetIdentityCommand), } - -#[derive(Parser, Debug)] -pub enum AdminCommand { - /// 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 = "auth-fp")] - auth_fp: Option, - }, - - /// Generate a Key. - /// - /// A signing key is always created, decryption and authentication keys - /// are optional. - Generate { - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] - user_pin: Option, - - /// Output file (stdout if unset) - #[clap(name = "output", long = "output", short = 'o')] - output: Option, - - #[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_id: Vec, - }, - - /// 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(ValueEnum, Debug, Clone)] -#[clap(rename_all = "UPPER")] -pub enum BasePlusAttKeySlot { - Sig, - Dec, - Aut, - Att, -} - -impl From for openpgp_card_sequoia::types::KeyType { - fn from(ks: BasePlusAttKeySlot) -> Self { - use openpgp_card_sequoia::types::KeyType; - 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 AdminGenerateAlgo { - Rsa2048, - Rsa3072, - Rsa4096, - Nistp256, - Nistp384, - Nistp521, - Curve25519, -} - -impl From for openpgp_card_sequoia::types::AlgoSimple { - fn from(aga: AdminGenerateAlgo) -> Self { - use openpgp_card_sequoia::types::AlgoSimple; - - match aga { - AdminGenerateAlgo::Rsa2048 => AlgoSimple::RSA2k, - AdminGenerateAlgo::Rsa3072 => AlgoSimple::RSA3k, - AdminGenerateAlgo::Rsa4096 => AlgoSimple::RSA4k, - AdminGenerateAlgo::Nistp256 => AlgoSimple::NIST256, - AdminGenerateAlgo::Nistp384 => AlgoSimple::NIST384, - AdminGenerateAlgo::Nistp521 => AlgoSimple::NIST521, - AdminGenerateAlgo::Curve25519 => AlgoSimple::Curve25519, - } - } -} diff --git a/tools/src/bin/opgpcard/commands/admin.rs b/tools/src/bin/opgpcard/commands/admin.rs new file mode 100644 index 0000000..461cff1 --- /dev/null +++ b/tools/src/bin/opgpcard/commands/admin.rs @@ -0,0 +1,549 @@ +// 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}; +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 = "auth-fp")] + auth_fp: Option, + }, + + /// Generate a Key. + /// + /// A signing key is always created, decryption and authentication keys + /// are optional. + Generate { + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + user_pin: Option, + + /// Output file (stdout if unset) + #[clap(name = "output", long = "output", short = 'o')] + output: Option, + + #[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_id: Vec, + }, + + /// 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(ValueEnum, Debug, Clone)] +#[clap(rename_all = "UPPER")] +pub enum BasePlusAttKeySlot { + Sig, + Dec, + Aut, + Att, +} + +impl From for openpgp_card_sequoia::types::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, + Curve25519, +} + +impl From for openpgp_card_sequoia::types::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::Curve25519 => AlgoSimple::Curve25519, + } + } +} + +pub fn admin( + output_format: OutputFormat, + output_version: OutputVersion, + command: AdminCommand, +) -> Result<(), Box> { + let backend = util::open_card(&command.ident)?; + let mut card = Card::new(backend); + let mut open = card.transaction()?; + + let admin_pin = util::get_pin(&mut open, command.admin_pin, ENTER_ADMIN_PIN); + + match command.cmd { + AdminSubCommand::Name { name } => { + name_command(&name, open, admin_pin.as_deref())?; + } + AdminSubCommand::Url { url } => { + url_command(&url, open, admin_pin.as_deref())?; + } + AdminSubCommand::Import { + keyfile, + sig_fp, + dec_fp, + auth_fp, + } => { + import_command(keyfile, sig_fp, dec_fp, auth_fp, open, admin_pin.as_deref())?; + } + AdminSubCommand::Generate { + user_pin, + output, + decrypt, + auth, + algo, + user_id, + } => { + generate_command( + output_format, + output_version, + open, + admin_pin.as_deref(), + user_pin, + output, + decrypt, + auth, + algo, + user_id, + )?; + } + AdminSubCommand::Touch { key, policy } => { + touch_command(open, 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, + 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)?]) +} + +#[allow(clippy::too_many_arguments)] +fn generate_keys( + format: OutputFormat, + version: OutputVersion, + mut open: Open, + admin_pin: Option<&[u8]>, + user_pin: Option<&[u8]>, + output_file: Option, + decrypt: bool, + auth: bool, + algo: Option, + user_ids: Vec, +) -> Result<()> { + let mut output = output::AdminGenerate::default(); + output.ident(open.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. + + 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 open, admin_pin) { + gen_subkeys(&mut admin, decrypt, 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 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()?)?; + output.public_key(armored); + + // Write armored certificate to the output file (or stdout) + let mut handle = util::open_or_stdout(output_file.as_deref())?; + handle.write_all(output.print(format, version)?.as_bytes())?; + let _ = handle.write(b"\n")?; + + 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)) +} + +fn name_command( + name: &str, + mut open: Open, + admin_pin: Option<&[u8]>, +) -> Result<(), Box> { + let mut admin = util::verify_to_admin(&mut open, admin_pin)?; + + admin.set_name(name)?; + Ok(()) +} + +fn url_command( + url: &str, + mut open: Open, + admin_pin: Option<&[u8]>, +) -> Result<(), Box> { + let mut admin = util::verify_to_admin(&mut open, admin_pin)?; + + admin.set_url(url)?; + Ok(()) +} + +fn import_command( + keyfile: PathBuf, + sig_fp: Option, + dec_fp: Option, + auth_fp: Option, + mut open: Open, + 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, &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)?; + + 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 open: Open, + + admin_pin: Option<&[u8]>, + + user_pin: Option, + output: Option, + decrypt: bool, + auth: bool, + algo: Option, + user_id: Vec, +) -> Result<()> { + let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); + + generate_keys( + output_format, + output_version, + open, + admin_pin.as_deref(), + user_pin.as_deref(), + output, + decrypt, + auth, + algo.map(AlgoSimple::from), + user_id, + ) +} + +fn touch_command( + mut open: Open, + 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 open, admin_pin)?; + + admin.set_uif(kt, pol)?; + Ok(()) +} diff --git a/tools/src/bin/opgpcard/commands/mod.rs b/tools/src/bin/opgpcard/commands/mod.rs index b2027f8..ba21c9a 100644 --- a/tools/src/bin/opgpcard/commands/mod.rs +++ b/tools/src/bin/opgpcard/commands/mod.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 +pub mod admin; pub mod attestation; pub mod decrypt; pub mod factory_reset; diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index bedda34..401fc50 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -2,25 +2,15 @@ // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 -use anyhow::{anyhow, Result}; +use anyhow::Result; use clap::Parser; -use std::path::PathBuf; -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, StandardPolicy}; -use sequoia_openpgp::serialize::SerializeInto; -use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; -use openpgp_card_sequoia::card::{Admin, Card, Open}; -use openpgp_card_sequoia::types::{AlgoSimple, CardBackend, KeyType, TouchPolicy}; -use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key}; -use openpgp_card_sequoia::{sq_util, PublicKey}; - -use std::io::Write; +use openpgp_card_sequoia::card::{Card, Open}; +use openpgp_card_sequoia::types::CardBackend; +use openpgp_card_sequoia::util::make_cert; +use openpgp_card_sequoia::PublicKey; mod cli; mod commands; @@ -73,168 +63,8 @@ fn main() -> Result<(), Box> { cli::Command::FactoryReset(cmd) => { commands::factory_reset::factory_reset(cmd)?; } - cli::Command::Admin { - ident, - admin_pin, - cmd, - } => { - let backend = util::open_card(&ident)?; - let mut card = Card::new(backend); - let mut open = card.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, - decrypt, - auth, - algo, - user_id, - } => { - let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); - - generate_keys( - cli.output_format, - cli.output_version, - open, - admin_pin.as_deref(), - user_pin.as_deref(), - output, - decrypt, - auth, - algo.map(AlgoSimple::from), - user_id, - )?; - } - cli::AdminCommand::Touch { key, policy } => { - let kt = KeyType::from(key); - - let pol = TouchPolicy::from(policy); - - let mut admin = util::verify_to_admin(&mut open, admin_pin.as_deref())?; - - admin.set_uif(kt, pol)?; - } - } + cli::Command::Admin(cmd) => { + commands::admin::admin(cli.output_format, cli.output_version, cmd)?; } cli::Command::Pin(cmd) => { commands::pin::pin(&cmd.ident, cmd.cmd)?; @@ -291,34 +121,6 @@ fn pick_card_for_reading(ident: Option) -> Result( - 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, @@ -346,116 +148,3 @@ fn get_cert( user_ids, ) } - -#[allow(clippy::too_many_arguments)] -fn generate_keys( - format: OutputFormat, - version: OutputVersion, - mut open: Open, - admin_pin: Option<&[u8]>, - user_pin: Option<&[u8]>, - output_file: Option, - decrypt: bool, - auth: bool, - algo: Option, - user_ids: Vec, -) -> Result<()> { - let mut output = output::AdminGenerate::default(); - output.ident(open.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. - - 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 open, admin_pin) { - gen_subkeys(&mut admin, decrypt, 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 = 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()?)?; - output.public_key(armored); - - // Write armored certificate to the output file (or stdout) - let mut handle = util::open_or_stdout(output_file.as_deref())?; - handle.write_all(output.print(format, version)?.as_bytes())?; - let _ = handle.write(b"\n")?; - - 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)) -} From 00d40e940b2071553c79da219255fc0d2eda1fb5 Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 16:29:27 +0200 Subject: [PATCH 16/17] opgpcard: refactor admin generate --- tools/src/bin/opgpcard/commands/admin.rs | 206 +++++++++-------------- 1 file changed, 80 insertions(+), 126 deletions(-) diff --git a/tools/src/bin/opgpcard/commands/admin.rs b/tools/src/bin/opgpcard/commands/admin.rs index 461cff1..629fd60 100644 --- a/tools/src/bin/opgpcard/commands/admin.rs +++ b/tools/src/bin/opgpcard/commands/admin.rs @@ -69,28 +69,7 @@ pub enum AdminSubCommand { /// /// A signing key is always created, decryption and authentication keys /// are optional. - Generate { - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] - user_pin: Option, - - /// Output file (stdout if unset) - #[clap(name = "output", long = "output", short = 'o')] - output: Option, - - #[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_id: Vec, - }, + Generate(AdminGenerateCommand), /// Set touch policy Touch { @@ -102,6 +81,30 @@ pub enum AdminSubCommand { }, } +#[derive(Parser, Debug)] +pub struct AdminGenerateCommand { + #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + user_pin: Option, + + /// Output file (stdout if unset) + #[clap(name = "output", long = "output", short = 'o')] + output_file: Option, + + #[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 { @@ -201,26 +204,8 @@ pub fn admin( } => { import_command(keyfile, sig_fp, dec_fp, auth_fp, open, admin_pin.as_deref())?; } - AdminSubCommand::Generate { - user_pin, - output, - decrypt, - auth, - algo, - user_id, - } => { - generate_command( - output_format, - output_version, - open, - admin_pin.as_deref(), - user_pin, - output, - decrypt, - auth, - algo, - user_id, - )?; + AdminSubCommand::Generate(cmd) => { + generate_command(output_format, output_version, open, admin_pin.as_deref(), cmd)?; } AdminSubCommand::Touch { key, policy } => { touch_command(open, admin_pin.as_deref(), key, policy)?; @@ -257,72 +242,6 @@ fn keys_pick_explicit<'a>( Ok([key_by_fp(sig_fp)?, key_by_fp(dec_fp)?, key_by_fp(auth_fp)?]) } -#[allow(clippy::too_many_arguments)] -fn generate_keys( - format: OutputFormat, - version: OutputVersion, - mut open: Open, - admin_pin: Option<&[u8]>, - user_pin: Option<&[u8]>, - output_file: Option, - decrypt: bool, - auth: bool, - algo: Option, - user_ids: Vec, -) -> Result<()> { - let mut output = output::AdminGenerate::default(); - output.ident(open.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. - - 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 open, admin_pin) { - gen_subkeys(&mut admin, decrypt, 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 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()?)?; - output.public_key(armored); - - // Write armored certificate to the output file (or stdout) - let mut handle = util::open_or_stdout(output_file.as_deref())?; - handle.write_all(output.print(format, version)?.as_bytes())?; - let _ = handle.write(b"\n")?; - - Ok(()) -} - fn gen_subkeys( admin: &mut Admin, decrypt: bool, @@ -509,27 +428,62 @@ fn generate_command( admin_pin: Option<&[u8]>, - user_pin: Option, - output: Option, - decrypt: bool, - auth: bool, - algo: Option, - user_id: Vec, + cmd: AdminGenerateCommand, ) -> Result<()> { - let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN); + let user_pin = util::get_pin(&mut open, cmd.user_pin, ENTER_USER_PIN); - generate_keys( - output_format, - output_version, - open, - admin_pin.as_deref(), + let mut output = output::AdminGenerate::default(); + output.ident(open.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 open, 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 open, + key_sig, + key_dec, + key_aut, user_pin.as_deref(), - output, - decrypt, - auth, - algo.map(AlgoSimple::from), - user_id, - ) + &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 (or stdout) + let mut handle = util::open_or_stdout(cmd.output_file.as_deref())?; + handle.write_all(output.print(output_format, output_version)?.as_bytes())?; + let _ = handle.write(b"\n")?; + + Ok(()) } fn touch_command( From 77ed66bde7158c96d71e14e6cbd96b9e3f9853ed Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 26 Oct 2022 18:59:55 +0200 Subject: [PATCH 17/17] opgpcard: Add Lars to license header --- tools/src/bin/opgpcard/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 401fc50..dbc8af5 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2022 Lars Wirzenius // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0