diff --git a/tools/Cargo.toml b/tools/Cargo.toml index d13a000..6ae2c81 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -28,6 +28,7 @@ serde = { version = "1.0.145", features = ["derive"] } semver = "1.0.14" serde_yaml = "0.9.13" thiserror = "1.0.37" +indoc = "1" [build-dependencies] subplot-build = "0.5.0" diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 8c73614..01e6221 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -33,10 +33,6 @@ pub struct Cli { #[derive(Parser, Debug)] pub enum Command { - /// Show all output versions that are supported. Mark the - /// currently chosen one with a star. - OutputVersions {}, - /// Enumerate available OpenPGP cards List {}, @@ -46,7 +42,7 @@ pub enum Command { /// Show technical details about a card Info(commands::info::InfoCommand), - /// Display a card's authentication key as an SSH public key + /// Show a card's authentication key as an SSH public key Ssh(commands::ssh::SshCommand), /// Export the key data on a card as an OpenPGP public key @@ -56,20 +52,60 @@ pub enum Command { Admin(commands::admin::AdminCommand), /// PIN management (change PINs, reset blocked PINs) + #[clap( + long_about = indoc::indoc! { " + PIN management (change PINs, reset blocked PINs) + + OpenPGP cards use PINs (numerical passwords) to verify that a user is allowed to \ + perform an operation. There are two PINs for regular operation, User PIN and Admin \ + PIN, and one optional Resetting Code. + + The User PIN is required to use cryptographic operations on a card (such as \ + decryption or signing). + The Admin PIN is needed to configure a card (for example to import an OpenPGP key \ + into the card) or to unblock the User PIN. + The Resetting Code only allows unblocking the User PIN. This is useful if the user \ + doesn't have access to the Admin PIN. + + By default, on unconfigured (or factory reset) cards, the User PIN is typically set to + 123456, and the Admin PIN is set to 12345678." + }, + )] Pin(commands::pin::PinCommand), /// Decrypt data using a card Decrypt(commands::decrypt::DecryptCommand), /// Sign data using a card + /// + /// Currently, only detached signatures are supported. Sign(commands::sign::SignCommand), - /// Attestation management (Yubico) + /// Attestation management (Yubico only) + /// + /// Yubico implements a proprietary extension to the OpenPGP card standard to + /// cryptographically certify that a certain asymmetric key has been generated on device, and + /// not imported. + /// + /// This feature is available on YubiKey 5 devices with firmware version 5.2 or newer. Attestation(commands::attestation::AttestationCommand), - /// Completely reset a card (deletes all data, including the keys on the card!) + /// Completely reset a card (deletes all data including keys!) FactoryReset(commands::factory_reset::FactoryResetCommand), - /// Change identity (applies only to Nitrokey Start) + /// Change identity (Nitrokey Start only) + /// + /// A Nitrokey Start device contains three distinct virtual OpenPGP cards, select the identity + /// of the virtual card to activate. SetIdentity(commands::set_identity::SetIdentityCommand), + + /// Show supported output format versions + #[clap( + long_about = indoc::indoc! { " + Show supported output format versions for JSON and YAML output. + + Mark the currently chosen one with a star." + } + )] + OutputVersions {}, } diff --git a/tools/src/bin/opgpcard/commands/admin.rs b/tools/src/bin/opgpcard/commands/admin.rs index 5aa7867..55215bf 100644 --- a/tools/src/bin/opgpcard/commands/admin.rs +++ b/tools/src/bin/opgpcard/commands/admin.rs @@ -30,10 +30,20 @@ use crate::{output, util, ENTER_ADMIN_PIN, ENTER_USER_PIN}; #[derive(Parser, Debug)] pub struct AdminCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] pub ident: String, - #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] + #[clap( + name = "Admin PIN file", + short = 'P', + long = "admin-pin", + help = "Optionally, get Admin PIN from a file" + )] pub admin_pin: Option, #[clap(subcommand)] @@ -43,66 +53,118 @@ pub struct AdminCommand { #[derive(Parser, Debug)] pub enum AdminSubCommand { /// Set cardholder name - Name { name: String }, + Name { + #[clap(help = "cardholder name to set on the card")] + name: String, + }, - /// Set cardholder URL - Url { url: String }, + /// Set certificate URL + Url { + #[clap(help = "URL that provides the certificate for the key material on this card")] + url: String, + }, - /// Import a Key. + /// Import a Key onto the card. /// - /// 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. + /// Most keys can be imported without specifying subkey fingerprints. However, if the key + /// contins more than one signing, decryption or authentication capable subkey, subkeys must be + /// explicitly selected. + /// + /// If any of the options is given, only the selected subkeys are imported into the selected + /// slots. + /// + /// Subkey capabilities must match the slot the key is imported into. The DEC slot can + /// only be used for encryption capable subkeys. The SIG and AUT slots can be used for signing, + /// certification and authentication capable subkeys. Import { + #[clap(help = "File that contains the PGP private key")] keyfile: PathBuf, - #[clap(name = "Signature key fingerprint", short = 's', long = "sig-fp")] + /// Optionally, select the subkey to import in the SIG slot + #[clap(name = "SIG subkey fingerprint", short = 's', long = "sig-fp")] sig_fp: Option, - #[clap(name = "Decryption key fingerprint", short = 'd', long = "dec-fp")] + /// Optionally, select the subkey to import in the DEC slot + #[clap(name = "DEC subkey fingerprint", short = 'd', long = "dec-fp")] dec_fp: Option, - #[clap(name = "Authentication key fingerprint", short = 'a', long = "aut-fp")] + /// Optionally, select the subkey to import in the AUT slot + #[clap(name = "AUT subkey fingerprint", short = 'a', long = "aut-fp")] aut_fp: Option, }, - /// Generate a Key. + /// Generate a Key on the card. /// /// A signing key is always created, decryption and authentication keys /// are optional. Generate(AdminGenerateCommand), - /// Set touch policy + /// Set the card's touch policy (if supported) + /// + /// A touch policy defines if cryptographic operations on the card require user interaction + /// with the card, for example by touching a button on the card. + /// + /// Only some cards support this feature at all, not all cards support all policies. + /// + /// Caution: Setting the ATT slot to Fixed or Cached-Fixed is permanent. Even a factory reset does + /// not undo this setting. Touch { + /// Key slot to set the touch policy for #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] key: BasePlusAttKeySlot, - #[clap(name = "Policy", short = 'p', long = "policy", value_enum)] + /// Touch policy to set on this key slot + #[clap( + name = "Policy", + short = 'p', + long = "policy", + value_enum, + long_help = "Touch policy to set on this key slot + +Off: No touch confirmation required. +On: Touch confirmation required for each operation. +Fixed: Like 'On', but the policy can only be changed by a reset. +Cached: Like 'On', but touch confirmation is valid for 15 seconds. +Cached-Fixed: Combines 'Cached' and 'Fixed'." + )] policy: TouchPolicy, }, } #[derive(Parser, Debug)] pub struct AdminGenerateCommand { - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] - user_pin: Option, - /// Output file #[clap(name = "output", long = "output", short = 'o')] output_file: PathBuf, - #[clap(long = "no-decrypt", action = clap::ArgAction::SetFalse)] + /// Do not create a key in the DEC slot + #[clap(long = "no-dec", action = clap::ArgAction::SetFalse)] decrypt: bool, - #[clap(long = "no-auth", action = clap::ArgAction::SetFalse)] + /// Do not create a key in the AUT slot + #[clap(long = "no-aut", action = clap::ArgAction::SetFalse)] auth: bool, - /// Algorithm - #[clap(value_enum)] + /// Choose the algorithm for the key material to generate on the card. + /// + /// If the parameter is not given, use the algorithm currently set on the card. + /// + /// Specific cards support a set of algorithms that can differ between models. On modern cards, + /// use 'opgpcard info' to see the list of supported algorithms. + #[clap(name = "algorithm", value_enum)] algo: Option, /// User ID to add to the exported certificate representation #[clap(name = "User ID", short = 'u', long = "userid")] user_ids: Vec, + + #[clap( + name = "User PIN file", + short = 'p', + long = "user-pin", + help = "Optionally, get User PIN from a file" + )] + user_pin: Option, } #[derive(ValueEnum, Debug, Clone)] diff --git a/tools/src/bin/opgpcard/commands/attestation.rs b/tools/src/bin/opgpcard/commands/attestation.rs index 2bce249..8e0b76d 100644 --- a/tools/src/bin/opgpcard/commands/attestation.rs +++ b/tools/src/bin/opgpcard/commands/attestation.rs @@ -23,30 +23,59 @@ pub struct AttestationCommand { #[derive(Parser, Debug)] pub enum AttSubCommand { - /// Print the card's "Attestation Certificate" + /// Print the card's attestation certificate + /// + /// New YubiKeys are preloaded with an attestation certificate issued by the Yubico CA. Cert { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] ident: Option, }, - /// Generate "Attestation Statement" for one of the key slots on the card + /// Generate attestation statement for one of the key slots on the card + /// + /// An attestation statement can only be generated for key slots that contain keys that were + /// generated by the card. See 'opgpcard admin generate' and 'opgpcard status -v'. Generate { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] ident: String, + /// Key slot to use #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] key: BaseKeySlot, - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + #[clap( + name = "User PIN file", + short = 'p', + long = "user-pin", + help = "Optionally, get User PIN from a file" + )] user_pin: Option, }, - /// Print a "cardholder certificate" from the card. - /// This shows the "Attestation Statement", if one has been generated. + /// Print the attestation statement for one of the key slots on the card + /// + /// An attestation statement can only be printed after generating it. See 'opgpcard attestation + /// generate'. Statement { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to reset" + )] ident: Option, + /// Key slot to use #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] key: BaseKeySlot, }, diff --git a/tools/src/bin/opgpcard/commands/decrypt.rs b/tools/src/bin/opgpcard/commands/decrypt.rs index 7acc0c8..462cba2 100644 --- a/tools/src/bin/opgpcard/commands/decrypt.rs +++ b/tools/src/bin/opgpcard/commands/decrypt.rs @@ -17,10 +17,20 @@ use crate::util; #[derive(Parser, Debug)] pub struct DecryptCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] ident: String, - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + #[clap( + name = "User PIN file", + short = 'p', + long = "user-pin", + help = "Optionally, get User PIN from a file" + )] pin_file: Option, /// Input file (stdin if unset) diff --git a/tools/src/bin/opgpcard/commands/factory_reset.rs b/tools/src/bin/opgpcard/commands/factory_reset.rs index b68ac95..dcff395 100644 --- a/tools/src/bin/opgpcard/commands/factory_reset.rs +++ b/tools/src/bin/opgpcard/commands/factory_reset.rs @@ -10,7 +10,12 @@ use crate::util; #[derive(Parser, Debug)] pub struct FactoryResetCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] ident: String, } diff --git a/tools/src/bin/opgpcard/commands/info.rs b/tools/src/bin/opgpcard/commands/info.rs index 261dbdd..6bcbbd4 100644 --- a/tools/src/bin/opgpcard/commands/info.rs +++ b/tools/src/bin/opgpcard/commands/info.rs @@ -13,7 +13,12 @@ use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; #[derive(Parser, Debug)] pub struct InfoCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] pub ident: Option, } diff --git a/tools/src/bin/opgpcard/commands/pin.rs b/tools/src/bin/opgpcard/commands/pin.rs index a881f51..94139c8 100644 --- a/tools/src/bin/opgpcard/commands/pin.rs +++ b/tools/src/bin/opgpcard/commands/pin.rs @@ -15,7 +15,12 @@ use crate::{ENTER_ADMIN_PIN, ENTER_USER_PIN}; #[derive(Parser, Debug)] pub struct PinCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] pub ident: String, #[clap(subcommand)] @@ -25,49 +30,111 @@ pub struct PinCommand { #[derive(Parser, Debug)] pub enum PinSubCommand { /// Set User PIN + /// + /// Set a new User PIN by providing the current User PIN. SetUser { - #[clap(name = "User PIN file old", short = 'p', long = "user-pin-old")] + #[clap( + name = "User PIN file old", + short = 'p', + long = "user-pin-old", + help = "Optionally, get old User PIN from a file" + )] user_pin_old: Option, - #[clap(name = "User PIN file new", short = 'q', long = "user-pin-new")] + #[clap( + name = "User PIN file new", + short = 'q', + long = "user-pin-new", + help = "Optionally, get new User PIN from a file" + )] user_pin_new: Option, }, /// Set Admin PIN + /// + /// Set a new Admin PIN by providing the current Admin PIN. SetAdmin { - #[clap(name = "Admin PIN file old", short = 'P', long = "admin-pin-old")] + #[clap( + name = "Admin PIN file old", + short = 'P', + long = "admin-pin-old", + help = "Optionally, get old Admin PIN from a file" + )] admin_pin_old: Option, - #[clap(name = "Admin PIN file new", short = 'Q', long = "admin-pin-new")] + #[clap( + name = "Admin PIN file new", + short = 'Q', + long = "admin-pin-new", + help = "Optionally, get new Admin PIN from a file" + )] admin_pin_new: Option, }, /// Reset User PIN with Admin PIN + /// + /// Set a new User PIN by providing the Admin PIN. This can also be used if the User PIN has + /// been blocked. ResetUser { - #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] + #[clap( + name = "Admin PIN file", + short = 'P', + long = "admin-pin", + help = "Optionally, get Admin PIN from a file" + )] admin_pin: Option, - #[clap(name = "User PIN file new", short = 'p', long = "user-pin-new")] + #[clap( + name = "User PIN file new", + short = 'p', + long = "user-pin-new", + help = "Optionally, get new User PIN from a file" + )] + user_pin_new: Option, + }, + + /// Reset User PIN with Resetting Code + /// + /// Set a new User PIN by providing the Resetting Code. This can also be used if the User PIN + /// has been blocked. + ResetUserRc { + #[clap( + name = "Resetting Code file", + short = 'r', + long = "reset-code", + help = "Optionally, get the Resetting Code from a file" + )] + reset_code: Option, + + #[clap( + name = "User PIN file new", + short = 'p', + long = "user-pin-new", + help = "Optionally, get new User PIN from a file" + )] user_pin_new: Option, }, /// Set Resetting Code + /// + /// Set a Resetting Code by providing the Admin PIN. SetReset { - #[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")] + #[clap( + name = "Admin PIN file", + short = 'P', + long = "admin-pin", + help = "Optionally, get Admin PIN from a file" + )] admin_pin: Option, - #[clap(name = "Resetting code file", short = 'r', long = "reset-code")] + #[clap( + name = "Resetting Code file", + short = 'r', + long = "reset-code", + help = "Optionally, get the Resetting Code from a file" + )] 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<()> { diff --git a/tools/src/bin/opgpcard/commands/pubkey.rs b/tools/src/bin/opgpcard/commands/pubkey.rs index 8b93b80..2dcfbb8 100644 --- a/tools/src/bin/opgpcard/commands/pubkey.rs +++ b/tools/src/bin/opgpcard/commands/pubkey.rs @@ -21,10 +21,20 @@ use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; #[derive(Parser, Debug)] pub struct PubkeyCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] ident: Option, - #[clap(name = "User PIN file", short = 'p', long = "user-pin")] + #[clap( + name = "User PIN file", + short = 'p', + long = "user-pin", + help = "Optionally, get User PIN from a file" + )] user_pin: Option, /// User ID to add to the exported certificate representation diff --git a/tools/src/bin/opgpcard/commands/set_identity.rs b/tools/src/bin/opgpcard/commands/set_identity.rs index 0eef31c..6fc61b0 100644 --- a/tools/src/bin/opgpcard/commands/set_identity.rs +++ b/tools/src/bin/opgpcard/commands/set_identity.rs @@ -10,9 +10,15 @@ use crate::util; #[derive(Parser, Debug)] pub struct SetIdentityCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] ident: String, + /// Identity of the virtual card to activate #[clap(name = "identity", value_enum)] id: SetIdentityId, } diff --git a/tools/src/bin/opgpcard/commands/sign.rs b/tools/src/bin/opgpcard/commands/sign.rs index a2b8315..f891b61 100644 --- a/tools/src/bin/opgpcard/commands/sign.rs +++ b/tools/src/bin/opgpcard/commands/sign.rs @@ -14,14 +14,28 @@ use crate::util; #[derive(Parser, Debug)] pub struct SignCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] pub ident: String, - /// User PIN file - #[clap(short = 'p', long = "user-pin")] + #[clap( + name = "User PIN file", + short = 'p', + long = "user-pin", + help = "Optionally, get User PIN from a file" + )] pub user_pin: Option, - #[clap(name = "detached", short = 'd', long = "detached")] + #[clap( + name = "detached", + short = 'd', + long = "detached", + help = "Create a detached signature" + )] pub detached: bool, /// Input file (stdin if unset) diff --git a/tools/src/bin/opgpcard/commands/ssh.rs b/tools/src/bin/opgpcard/commands/ssh.rs index d7323f2..a974a32 100644 --- a/tools/src/bin/opgpcard/commands/ssh.rs +++ b/tools/src/bin/opgpcard/commands/ssh.rs @@ -16,7 +16,12 @@ use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; #[derive(Parser, Debug)] pub struct SshCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] pub ident: Option, } diff --git a/tools/src/bin/opgpcard/commands/status.rs b/tools/src/bin/opgpcard/commands/status.rs index 8717b65..0331b54 100644 --- a/tools/src/bin/opgpcard/commands/status.rs +++ b/tools/src/bin/opgpcard/commands/status.rs @@ -15,10 +15,20 @@ use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; #[derive(Parser, Debug)] pub struct StatusCommand { - #[clap(name = "card ident", short = 'c', long = "card")] + #[clap( + name = "card ident", + short = 'c', + long = "card", + help = "Identifier of the card to use" + )] pub ident: Option, - #[clap(name = "verbose", short = 'v', long = "verbose")] + #[clap( + name = "verbose", + short = 'v', + long = "verbose", + help = "Use verbose output" + )] pub verbose: bool, /// Print public key material for each key slot