// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-FileCopyrightText: 2022 Nora Widdecke // SPDX-License-Identifier: MIT OR Apache-2.0 use clap::{AppSettings, Parser, ValueEnum}; 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)]; #[derive(Parser, Debug)] #[clap( name = "opgpcard", author = "Heiko Schäfer ", version, global_setting(AppSettings::DeriveDisplayOrder), about = "A tool for inspecting and configuring OpenPGP cards." )] pub struct Cli { /// Produce output in the chosen format. #[clap(long, value_enum, default_value = "text")] pub output_format: OutputFormat, /// Pick output version to use, for non-textual formats. #[clap(long, default_value = DEFAULT_OUTPUT_VERSION)] pub output_version: OutputVersion, #[clap(subcommand)] pub cmd: Command, } #[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 {}, /// 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, }, /// Show technical details about a card Info { #[clap(name = "card ident", short = 'c', long = "card")] ident: Option, }, /// Display a card's authentication key as an SSH public key Ssh { #[clap(name = "card ident", short = 'c', long = "card")] ident: Option, }, /// 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, }, /// 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, }, /// PIN management (change PINs, reset blocked PINs) Pin { #[clap(name = "card ident", short = 'c', long = "card")] ident: String, #[clap(subcommand)] cmd: PinCommand, }, /// 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, }, /// 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, }, /// Attestation management (Yubico) Attestation { #[clap(subcommand)] cmd: AttCommand, }, /// Completely reset a card (deletes all data, including the keys on the card!) FactoryReset { #[clap(name = "card ident", short = 'c', long = "card")] ident: String, }, /// 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, }, } #[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(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" 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 { 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)] 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 { 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, } } }