//! Command-line parser for `sq key`.

use std::path::PathBuf;

use clap::{ValueEnum, ArgGroup, Args, Parser, Subcommand};

use sequoia_openpgp as openpgp;
use openpgp::cert::CipherSuite as SqCipherSuite;
use openpgp::KeyHandle;
use openpgp::types::ReasonForRevocation as OpenPGPRevocationReason;

use crate::cli::KEY_VALIDITY_DURATION;
use crate::cli::KEY_VALIDITY_IN_YEARS;
use crate::cli::types::ClapData;
use crate::cli::types::EncryptPurpose;
use crate::cli::types::Expiry;
use crate::cli::types::FileOrStdin;
use crate::cli::types::FileOrStdout;
use crate::cli::types::Time;

/// The revocation reason for a certificate or subkey
#[derive(ValueEnum, Clone, Debug)]
pub enum RevocationReason {
    Compromised,
    Superseded,
    Retired,
    Unspecified
}

impl From<RevocationReason> for OpenPGPRevocationReason {
    fn from(rr: RevocationReason) -> Self {
        match rr {
            RevocationReason::Compromised => OpenPGPRevocationReason::KeyCompromised,
            RevocationReason::Superseded => OpenPGPRevocationReason::KeySuperseded,
            RevocationReason::Retired => OpenPGPRevocationReason::KeyRetired,
            RevocationReason::Unspecified => OpenPGPRevocationReason::Unspecified,
        }
    }
}

/// The revocation reason for a UserID
#[derive(ValueEnum, Clone, Debug)]
pub enum UseridRevocationReason {
    Retired,
    Unspecified
}

impl From<UseridRevocationReason> for OpenPGPRevocationReason {
    fn from(rr: UseridRevocationReason) -> Self {
        match rr {
            UseridRevocationReason::Retired => OpenPGPRevocationReason::UIDRetired,
            UseridRevocationReason::Unspecified => OpenPGPRevocationReason::Unspecified,
        }
    }
}
#[derive(Parser, Debug)]
#[clap(
    name = "key",
    about = "Manages keys",
    long_about =
"Manages keys

We use the term \"key\" to refer to OpenPGP keys that do contain
secrets.  This subcommand provides primitives to generate and
otherwise manipulate keys.

Conversely, we use the term \"certificate\", or \"cert\" for short, to refer
to OpenPGP keys that do not contain secrets.  See `sq keyring` for
operations on certificates.
",
    subcommand_required = true,
    arg_required_else_help = true,
)]
pub struct Command {
    #[clap(subcommand)]
    pub subcommand: Subcommands,
}

#[derive(Debug, Subcommand)]
pub enum Subcommands {
    Generate(GenerateCommand),
    Password(PasswordCommand),
    Revoke(RevokeCommand),
    #[clap(subcommand)]
    Userid(UseridCommand),
    #[clap(subcommand)]
    Subkey(SubkeyCommand),
    ExtractCert(ExtractCertCommand),
    AttestCertifications(AttestCertificationsCommand),
    Adopt(AdoptCommand),
}

#[derive(Debug, Args)]
#[clap(
    about = "Generates a new key",
    long_about = format!(
"Generates a new key

Generating a key is the prerequisite to receiving encrypted messages
and creating signatures.  There are a few parameters to this process,
but we provide reasonable defaults for most users.

When generating a key, we also generate a revocation certificate.
This can be used in case the key is superseded, lost, or compromised.
It is a good idea to keep a copy of this in a safe place.

After generating a key, use `sq key extract-cert` to get the
certificate corresponding to the key.  The key must be kept secure,
while the certificate should be handed out to correspondents, e.g. by
uploading it to a key server.

By default a key expires after {} years.
Using the `--expiry=` argument specific validity periods may be defined.
It allows for providing a point in time for validity to end or a validity
duration.

`sq key generate` respects the reference time set by the top-level
`--time` argument.  It sets the creation time of the key, any
subkeys, and the binding signatures to the reference time.
",
        KEY_VALIDITY_IN_YEARS,
    ),
    after_help =
"EXAMPLES:

# First, generate a key
$ sq key generate --userid '<juliet@example.org>' \\
     --output juliet.key.pgp

# Then, extract the certificate for distribution
$ sq key extract-cert --output juliet.cert.pgp juliet.key.pgp

# Generate a key protecting it with a password
$ sq key generate --userid '<juliet@example.org>' --with-password

# Generate a key with multiple userids
$ sq key generate --userid '<juliet@example.org>' \\
     --userid 'Juliet Capulet'

# Generate a key whose creation time is June 9, 2011 at midnight UTC
$ sq key generate --time 20110609 --userid Noam \\
     --output noam.pgp
",
)]
#[clap(group(ArgGroup::new("cap-sign").args(&["can_sign", "cannot_sign"])))]
#[clap(group(ArgGroup::new("cap-authenticate").args(&["can_authenticate", "cannot_authenticate"])))]
#[clap(group(ArgGroup::new("cap-encrypt").args(&["can_encrypt", "cannot_encrypt"])))]
pub struct GenerateCommand {
    #[clap(
        short = 'u',
        long = "userid",
        value_name = "EMAIL",
        help = "Adds a userid to the key"
    )]
    pub userid: Vec<String>,
    #[clap(
        short = 'c',
        long = "cipher-suite",
        value_name = "CIPHER-SUITE",
        default_value_t = CipherSuite::Cv25519,
        help = "Selects the cryptographic algorithms for the key",
        value_enum,
    )]
    pub cipher_suite: CipherSuite,
    #[clap(
        long = "with-password",
        help = "Protects the key with a password",
    )]
    pub with_password: bool,
    #[clap(
        long = "expiry",
        value_name = "EXPIRY",
        default_value_t = Expiry::Duration(KEY_VALIDITY_DURATION),
        help =
            "Defines EXPIRY for the key as ISO 8601 formatted string or \
            custom duration.",
        long_help =
            "Defines EXPIRY for the key as ISO 8601 formatted string or \
            custom duration. \
            If an ISO 8601 formatted string is provided, the validity period \
            reaches from the reference time (may be set using `--time`) to \
            the provided time. \
            Custom durations starting from the reference time may be set using \
            `N[ymwds]`, for N years, months, weeks, days, or seconds. \
            The special keyword `never` sets an unlimited expiry.",
    )]
    pub expiry: Expiry,
    #[clap(
        long = "can-sign",
        help ="Adds a signing-capable subkey (default)",
    )]
    pub can_sign: bool,
    #[clap(
        long = "cannot-sign",
        help = "Adds no signing-capable subkey",
    )]
    pub cannot_sign: bool,
    #[clap(
        long = "can-authenticate",
        help = "Adds an authentication-capable subkey (default)",
    )]
    pub can_authenticate: bool,
    #[clap(
        long = "cannot-authenticate",
        help = "Adds no authentication-capable subkey",
    )]
    pub cannot_authenticate: bool,
    #[clap(
        long = "can-encrypt",
        value_name = "PURPOSE",
        help = "Adds an encryption-capable subkey [default: universal]",
        long_help =
            "Adds an encryption-capable subkey. \
            Encryption-capable subkeys can be marked as \
            suitable for transport encryption, storage \
            encryption, or both, i.e., universal. \
            [default: universal]",
        value_enum,
    )]
    pub can_encrypt: Option<EncryptPurpose>,
    #[clap(
        long = "cannot-encrypt",
        help = "Adds no encryption-capable subkey",
    )]
    pub cannot_encrypt: bool,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    #[clap(
        long = "rev-cert",
        value_name = "FILE or -",
        required_if_eq("output", "-"),
        help = "Writes the revocation certificate to FILE",
        long_help =
            "Writes the revocation certificate to FILE. \
            mandatory if OUTFILE is `-`. \
            [default: <OUTFILE>.rev]",
    )]
    pub rev_cert: Option<PathBuf>
}

#[derive(ValueEnum, Clone, Debug)]
pub enum CipherSuite {
    Rsa3k,
    Rsa4k,
    Cv25519
}

impl CipherSuite {

    /// Return a matching `sequoia_openpgp::cert::CipherSuite`
    pub fn as_ciphersuite(&self) -> SqCipherSuite {
        match self {
            CipherSuite::Rsa3k => SqCipherSuite::RSA3k,
            CipherSuite::Rsa4k => SqCipherSuite::RSA4k,
            CipherSuite::Cv25519 => SqCipherSuite::Cv25519,
        }
    }
}

#[derive(Debug, Args)]
#[clap(
    name = "password",
    about = "Changes password protecting secrets",
    long_about = 
"Changes password protecting secrets

Secret key material in keys can be protected by a password.  This
subcommand changes or clears this encryption password.

To emit the key with unencrypted secrets, either use `--clear` or
supply a zero-length password when prompted for the new password.
",
    after_help =
"EXAMPLES:

# First, generate a key
$ sq key generate --userid '<juliet@example.org>' \\
     --output juliet.key.pgp

# Then, encrypt the secrets in the key with a password.
$ sq key password < juliet.key.pgp > juliet.encrypted_key.pgp

# And remove the password again.
$ sq key password --clear < juliet.encrypted_key.pgp \\
     > juliet.decrypted_key.pgp
",
)]
pub struct PasswordCommand {
    #[clap(
        default_value_t = FileOrStdin::default(),
        help = FileOrStdin::HELP_OPTIONAL,
        value_name = FileOrStdin::VALUE_NAME,
    )]
    pub input: FileOrStdin,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    /// File containing password to decrypt key
    ///
    /// Note that the entire key file will be used as the password, including
    /// surrounding whitespace like for example a trailing newline
    #[clap(long)]
    pub old_password_file: Vec<PathBuf>,
    /// File containing password to encrypt key
    ///
    /// Note that the entire key file will be used as the password, including
    /// surrounding whitespace like for example a trailing newline
    #[clap(long)]
    pub new_password_file: Option<PathBuf>,
    #[clap(
        long = "clear",
        help = "Emit a key with unencrypted secrets",
    )]
    pub clear: bool,
    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

#[derive(Debug, Args)]
#[clap(
    about = "Revoke a certificate",
    long_about =
"Revokes a certificate

Creates a revocation certificate for the certificate.

If `--revocation-file` is provided, then that key is used to create
the signature.  If that key is different from the certificate being
revoked, this creates a third-party revocation.  This is normally only
useful if the owner of the certificate designated the key to be a
designated revoker.

If `--revocation-file` is not provided, then the certificate must
include a certification-capable key.

`sq key revoke` respects the reference time set by the top-level \
`--time` argument.  When set, it uses the specified time instead of \
the current time, when determining what keys are valid, and it sets \
the revocation certificate's creation time to the reference time \
instead of the current time.
",
)]
pub struct RevokeCommand {
    #[clap(
        value_name = "FILE",
        long = "certificate-file",
        alias = "cert-file",
        help = "The certificate to revoke",
        long_help =
"Reads the certificate to revoke from FILE or stdin, if omitted.  It is \
an error for the file to contain more than one certificate.",
    )]
    pub input: Option<PathBuf>,

    #[clap(
        long = "revocation-file",
        value_name = "KEY_FILE",
        help = "Signs the revocation certificate using the key in KEY_FILE",
        long_help =
"Signs the revocation certificate using the key in KEY_FILE.  If the key is \
different from the certificate, this creates a third-party revocation.  If \
this option is not provided, and the certificate includes secret key material, \
then that key is used to sign the revocation certificate.",
    )]
    pub secret_key_file: Option<PathBuf>,

    #[clap(
        long = "private-key-store",
        value_name = "KEY_STORE",
        help = "Provides parameters for private key store",
    )]
    pub private_key_store: Option<String>,

    #[clap(
        value_name = "REASON",
        required = true,
        help = "The reason for the revocation",
        long_help =
"The reason for the revocation.  This must be either: `compromised`,
`superseded`, `retired`, or `unspecified`:

  - `compromised` means that the secret key material may have been
    compromised.  Prefer this value if you suspect that the secret
    key has been leaked.

  - `superseded` means that the owner of the certificate has replaced
    it with a new certificate.  Prefer `compromised` if the secret
    key material has been compromised even if the certificate is also
    being replaced!  You should include the fingerprint of the new
    certificate in the message.

  - `retired` means that this certificate should not be used anymore,
    and there is no replacement.  This is appropriate when someone
    leaves an organisation.  Prefer `compromised` if the secret key
    material has been compromised even if the certificate is also
    being retired!  You should include how to contact the owner, or
    who to contact instead in the message.

  - `unspecified` means that none of the three other three reasons
    apply.  OpenPGP implementations conservatively treat this type
    of revocation similar to a compromised key.

If the reason happened in the past, you should specify that using the
`--time` argument.  This allows OpenPGP implementations to more
accurately reason about objects whose validity depends on the validity
of the certificate.",
    value_enum,
    )]
    pub reason: RevocationReason,

    #[clap(
        value_name = "MESSAGE",
        help = "A short, explanatory text",
        long_help =
"A short, explanatory text that is shown to a viewer of the revocation \
certificate.  It explains why the certificate has been revoked.  For \
instance, if Alice has created a new key, she would generate a \
`superseded` revocation certificate for her old key, and might include \
the message `I've created a new certificate, FINGERPRINT, please use
that in the future.`",
    )]
    pub message: String,

    #[clap(
        long,
        value_names = &["NAME", "VALUE"],
        number_of_values = 2,
        help = "Adds a notation to the certification.",
        long_help = "Adds a notation to the certification.  \
            A user-defined notation's name must be of the form \
            `name@a.domain.you.control.org`. If the notation's name starts \
            with a `!`, then the notation is marked as being critical.  If a \
            consumer of a signature doesn't understand a critical notation, \
            then it will ignore the signature.  The notation is marked as \
            being human readable."
    )]
    pub notation: Vec<String>,

    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,

    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

#[derive(Debug, Args)]
#[clap(
    name = "extract-cert",
    about = "Converts a key to a cert",
    long_about =
"Converts a key to a cert

After generating a key, use this command to get the certificate
corresponding to the key.  The key must be kept secure, while the
certificate should be handed out to correspondents, e.g. by uploading
it to a key server.
",
    after_help = "EXAMPLES:

# First, generate a key
$ sq key generate --userid '<juliet@example.org>' \\
     --output juliet.key.pgp

# Then, extract the certificate for distribution
$ sq key extract-cert --output juliet.cert.pgp juliet.key.pgp
",
)]
pub struct ExtractCertCommand {
    #[clap(
        default_value_t = FileOrStdin::default(),
        help = FileOrStdin::HELP_OPTIONAL,
        value_name = FileOrStdin::VALUE_NAME,
    )]
    pub input: FileOrStdin,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

#[derive(Debug, Subcommand)]
#[clap(
    name = "userid",
    about = "Manages User IDs",
    long_about =
"Manages User IDs

Add User IDs to, or strip User IDs from a key.
",
    subcommand_required = true,
    arg_required_else_help = true,
)]
pub enum UseridCommand {
    Add(UseridAddCommand),
    Revoke(UseridRevokeCommand),
    Strip(UseridStripCommand),
}

#[derive(Debug, Args)]
#[clap(
    about = "Adds a User ID",
    long_about =
"Adds a User ID

A User ID can contain a name, like `Juliet` or an email address, like
`<juliet@example.org>`.  Historically, a name and email address were often
combined as a single User ID, like `Juliet <juliet@example.org>`.

`sq userid add` respects the reference time set by the top-level
`--time` argument.  It sets the creation time of the User ID's
binding signature to the specified time.
",
    after_help =
"EXAMPLES:

# First, generate a key:
$ sq key generate --userid '<juliet@example.org>' \\
     --output juliet.key.pgp

# Then, add a User ID:
$ sq key userid add --userid Juliet juliet.key.pgp \\
  --output juliet-new.key.pgp

# Or, add a User ID whose creation time is set to June 28, 2022 at
# midnight UTC:
$ sq key userid add --userid Juliet --creation-time 20210628 \\
   juliet.key.pgp --output juliet-new.key.pgp
",
)]
pub struct UseridAddCommand {
    #[clap(
        help = FileOrStdin::HELP_REQUIRED,
        value_name = FileOrStdin::VALUE_NAME,
    )]
    pub input: FileOrStdin,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    #[clap(
        value_name = "USERID",
        required = true,
        help = "User ID to add",
    )]
    pub userid: Vec<String>,
    #[clap(
        long = "private-key-store",
        value_name = "KEY_STORE",
        help = "Provides parameters for private key store",
    )]
    pub private_key_store: Option<String>,
    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

#[derive(Debug, Args)]
#[clap(
    about = "Revoke a User ID",
    long_about =
"Revokes a User ID

Creates a revocation certificate for a User ID.

If `--revocation-key` is provided, then that key is used to create \
the signature.  If that key is different from the certificate being \
revoked, this creates a third-party revocation.  This is normally only \
useful if the owner of the certificate designated the key to be a \
designated revoker.

If `--revocation-key` is not provided, then the certificate must \
include a certification-capable key.

`sq key userid revoke` respects the reference time set by the top-level \
`--time` argument.  When set, it uses the specified time instead of \
the current time, when determining what keys are valid, and it sets \
the revocation certificate's creation time to the reference time \
instead of the current time.
",)]
pub struct UseridRevokeCommand {
    #[clap(
        value_name = "CERT_FILE",
        long = "certificate-file",
        alias = "cert-file",
        help = "The certificate containing the User ID to revoke",
        long_help =
"Reads the certificate to revoke from CERT_FILE or stdin, \
if omitted.  It is an error for the file to contain more than one \
certificate."
    )]
    pub input: Option<PathBuf>,

    #[clap(
        long = "revocation-file",
        value_name = "KEY_FILE",
        help = "Signs the revocation certificate using the key in KEY_FILE",
        long_help =
"Signs the revocation certificate using the key in KEY_FILE.  If the key is \
different from the certificate, this creates a third-party revocation.  If \
this option is not provided, and the certificate includes secret key material, \
then that key is used to sign the revocation certificate.",
    )]
    pub secret_key_file: Option<PathBuf>,

    #[clap(
        long = "private-key-store",
        value_name = "KEY_STORE",
        help = "Provides parameters for private key store",
    )]
    pub private_key_store: Option<String>,

    #[clap(
        value_name = "USERID",
        help = "The User ID to revoke",
        long_help =
"The User ID to revoke.  By default, this must exactly match a \
self-signed User ID.  Use `--force` to generate a revocation certificate \
for a User ID, which is not self signed."
    )]
    pub userid: String,

    #[clap(
        value_enum,
        value_name = "REASON",
        help = "The reason for the revocation",
        long_help =
"The reason for the revocation.  This must be either: `retired`, or
`unspecified`:

  - `retired` means that this User ID is no longer valid.  This is
    appropriate when someone leaves an organisation, and the
    organisation does not have their secret key material.  For
    instance, if someone was part of Debian and retires, they would
    use this to indicate that a Debian-specific User ID is no longer
    valid.

  - `unspecified` means that a different reason applies.

If the reason happened in the past, you should specify that using the \
`--time` argument.  This allows OpenPGP implementations to more
accurately reason about objects whose validity depends on the validity \
of a User ID."
    )]
    pub reason: UseridRevocationReason,

    #[clap(
        value_name = "MESSAGE",
        help = "A short, explanatory text",
        long_help =
"A short, explanatory text that is shown to a viewer of the revocation \
certificate.  It explains why the certificate has been revoked.  For \
instance, if Alice has created a new key, she would generate a \
`superseded` revocation certificate for her old key, and might include \
the message `I've created a new certificate, FINGERPRINT, please use
that in the future.`",
    )]
    pub message: String,

    #[clap(
        long,
        value_names = &["NAME", "VALUE"],
        number_of_values = 2,
        help = "Adds a notation to the certification.",
        long_help = "Adds a notation to the certification.  \
            A user-defined notation's name must be of the form \
            `name@a.domain.you.control.org`. If the notation's name starts \
            with a `!`, then the notation is marked as being critical.  If a \
            consumer of a signature doesn't understand a critical notation, \
            then it will ignore the signature.  The notation is marked as \
            being human readable."
    )]
    pub notation: Vec<String>,

    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,

    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

#[derive(Debug, Args)]
#[clap(
    about = "Strips a User ID",
    long_about =
"Strips a User ID

Note that this operation does not reliably remove User IDs from a
certificate that has already been disseminated! (OpenPGP software
typically appends new information it receives about a certificate
to its local copy of that certificate.  Systems that have obtained
a copy of your certificate with the User ID that you are trying to
strip will not drop that User ID from their copy.)

In most cases, you will want to use the 'sq key userid revoke' operation
instead.  That issues a revocation for a User ID, which can be used to mark
the User ID as invalidated.

However, this operation can be useful in very specific cases, in particular:
to remove a mistakenly added User ID before it has been uploaded to key
servers or otherwise shared.

Stripping a User ID may change how a certificate is interpreted.  This
is because information about the certificate like algorithm preferences,
the primary key's key flags, etc. is stored in the User ID's binding
signature.
",
    after_help =
"EXAMPLES:

# First, generate a key:
$ sq key generate --userid '<juliet@example.org>' \\
     --output juliet.key.pgp

# Then, strip a User ID:
$ sq key userid strip --userid '<juliet@example.org>' \\
     --output juliet-new.key.pgp juliet.key.pgp
",
)]
pub struct UseridStripCommand {
    #[clap(
        default_value_t = FileOrStdin::default(),
        help = FileOrStdin::HELP_OPTIONAL,
        value_name = FileOrStdin::VALUE_NAME,
    )]
    pub input: FileOrStdin,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    #[clap(
        value_name = "USERID",
        short,
        long,
        help = "User IDs to strip",
        long_help = "The User IDs to strip.  Values must exactly match a \
User ID."
    )]
    pub userid: Vec<String>,
    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

#[derive(Debug, Args)]
#[clap(
    name = "adopt",
    about = "Binds keys from one certificate to another",
    long_about =
"Binds keys from one certificate to another

This command allows one to transfer primary keys and subkeys into an
existing certificate.  Say you want to transition to a new
certificate, but have an authentication subkey on your current
certificate.  You want to keep the authentication subkey because it
allows access to SSH servers and updating their configuration is not
feasible.
",
    after_help =
"EXAMPLES:

# Adopt an subkey into the new cert
$ sq key adopt --keyring juliet-old.pgp --key 0123456789ABCDEF \\
     juliet-new.pgp
",
)]
pub struct AdoptCommand {
    #[clap(
        short = 'k',
        long = "key",
        value_name = "KEY",
        required(true),
        help = "Adds the key or subkey KEY to the TARGET-KEY",
    )]
    pub key: Vec<KeyHandle>,
    #[clap(
        long = "expire",
        value_name = "KEY-EXPIRATION-TIME",
        help = "Makes adopted subkeys expire at the given time",
    )]
    pub expire: Option<Time>,
    #[clap(
        long = "allow-broken-crypto",
        help = "Allows adopting keys from certificates \
            using broken cryptography",
    )]
    pub allow_broken_crypto: bool,
    #[clap(
        default_value_t = FileOrStdin::default(),
        value_name = "TARGET-KEY",
        help = "Adds keys to TARGET-KEY or reads keys from stdin if omitted",
    )]
    pub certificate: FileOrStdin,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

#[derive(Debug, Args)]
#[clap(
    name = "attest-certifications",
    about = "Attests to third-party certifications",
    long_about =
"Attests to third-party certifications allowing for their distribution

To prevent certificate flooding attacks, modern key servers prevent
uncontrolled distribution of third-party certifications on
certificates.  To make the key holder the sovereign over the
information over what information is distributed with the certificate,
the key holder needs to explicitly attest to third-party
certifications.

After the attestation has been created, the certificate has to be
distributed, e.g. by uploading it to a key server.
",
    after_help =
"EXAMPLES:

# Attest to all certifications present on the key
$ sq key attest-certifications juliet.pgp

# Retract prior attestations on the key
$ sq key attest-certifications --none juliet.pgp
",
)]
pub struct AttestCertificationsCommand {
    #[clap(
        long = "none",
        conflicts_with = "all",
        help = "Removes all prior attestations",
    )]
    pub none: bool,
    #[clap(
        long = "all",
        conflicts_with = "none",
        help = "Attests to all certifications [default]",
    )]
    pub all: bool,
    #[clap(
        default_value_t = FileOrStdin::default(),
        value_name = "KEY",
        help = "Changes attestations on KEY or reads from stdin if omitted",
    )]
    pub key: FileOrStdin,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,

}

#[derive(Debug, Subcommand)]
#[clap(
    name = "subkey",
    about = "Manages Subkeys",
    long_about =
"Manages Subkeys

Add new subkeys to an existing key.
",
    subcommand_required = true,
    arg_required_else_help = true,
)]
#[non_exhaustive]
pub enum SubkeyCommand {
    Add(SubkeyAddCommand),
    Revoke(SubkeyRevokeCommand),
}

#[derive(Debug, Args)]
#[clap(
    about = "Adds a newly generated Subkey",
    long_about =
"Adds a newly generated Subkey

A subkey has one or more flags. `--can-sign` sets the signing flag,
and means that the key may be used for signing. `--can-authenticate`
sets the authentication flags, and means that the key may be used for
authentication (e.g., as an SSH key). These two flags may be combined.

`--can-encrypt=storage` sets the storage encryption flag, and means that the key
may be used for storage encryption. `--can-encrypt=transport` sets the transport
encryption flag, and means that the key may be used for transport encryption.
`--can-encrypt=universal` sets both the storage and the transport encryption
flag, and means that the key may be used for both storage and transport
encryption. Only one of the encryption flags may be used and it can not be
combined with the signing or authentication flag.

At least one flag must be chosen.

When using `--with-password`, `sq` prompts the user for a password, that is
used to encrypt the subkey.
The password for the subkey may be different from that of the primary key.

Furthermore the subkey may use one of several available cipher suites, that can
be selected using `--cipher-suite`.

By default a new subkey never expires. However, its validity period is limited
by that of the primary key it is added for.
Using the `--expiry` argument specific validity periods may be defined.
It allows for providing a point in time for validity to end or a validity
duration.

`sq key subkey add` respects the reference time set by the top-level
`--time` argument. It sets the creation time of the subkey to the specified
time.
",
    after_help =
"EXAMPLES:

# First, generate a key
$ sq key generate --userid '<juliet@example.org>' \\
     --output juliet.key.pgp

# Add a new Subkey for universal encryption which expires at the same
# time as the primary key
$ sq key subkey add --output juliet-new.key.pgp \\
     --can-encrypt universal juliet.key.pgp

# Add a new Subkey for signing using the rsa3k cipher suite which
# expires in five days
$ sq key subkey add --output juliet-new.key.pgp --can-sign \\
     --expiry 5d --cipher-suite rsa3k juliet.key.pgp
",
)]
#[clap(group(ArgGroup::new("authentication-group").args(&["can_authenticate", "can_encrypt"])))]
#[clap(group(ArgGroup::new("sign-group").args(&["can_sign", "can_encrypt"])))]
#[clap(group(ArgGroup::new("required-group").args(&["can_authenticate", "can_sign", "can_encrypt"]).required(true)))]
pub struct SubkeyAddCommand {
    #[clap(
        default_value_t = FileOrStdin::default(),
        help = FileOrStdin::HELP_OPTIONAL,
        value_name = FileOrStdin::VALUE_NAME,
    )]
    pub input: FileOrStdin,
    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,
    #[clap(
        long = "private-key-store",
        value_name = "KEY_STORE",
        help = "Provides parameters for private key store",
    )]
    pub private_key_store: Option<String>,
    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
    #[clap(
        short = 'c',
        long = "cipher-suite",
        value_name = "CIPHER-SUITE",
        default_value_t = CipherSuite::Cv25519,
        help = "Selects the cryptographic algorithms for the subkey",
        value_enum,
    )]
    pub cipher_suite: CipherSuite,
    #[clap(
        long = "expiry",
        value_name = "EXPIRY",
        default_value_t = Expiry::Never,
        help =
            "Defines EXPIRY for the subkey as ISO 8601 formatted string or \
            custom duration.",
        long_help =
            "Defines EXPIRY for the subkey as ISO 8601 formatted string or \
            custom duration. \
            If an ISO 8601 formatted string is provided, the validity period \
            reaches from the reference time (may be set using `--time`) to \
            the provided time. \
            Custom durations starting from the reference time may be set using \
            `N[ymwds]`, for N years, months, weeks, days, or seconds. \
            The special keyword `never` sets an unlimited expiry.",
    )]
    pub expiry: Expiry,
    #[clap(
        long = "can-sign",
        help = "Adds signing capability to subkey",
    )]
    pub can_sign: bool,
    #[clap(
        long = "can-authenticate",
        help = "Adds authentication capability to subkey",
    )]
    pub can_authenticate: bool,
    #[clap(
        long = "can-encrypt",
        value_name = "PURPOSE",
        help = "Adds an encryption capability to subkey [default: universal]",
        long_help =
            "Adds an encryption capability to subkey. \
            Encryption-capable subkeys can be marked as \
            suitable for transport encryption, storage \
            encryption, or both, i.e., universal. \
            [default: universal]",
        value_enum,
    )]
    pub can_encrypt: Option<EncryptPurpose>,
    #[clap(
        long = "with-password",
        help = "Protects the subkey with a password",
    )]
    pub with_password: bool,
}

#[derive(Debug, Args)]
#[clap(
    about = "Revoke a subkey",
    long_about =
"Revokes a subkey

Creates a revocation certificate for a subkey.

If `--revocation-file` is provided, then that key is used to \
create the signature.  If that key is different from the certificate \
being revoked, this creates a third-party revocation.  This is \
normally only useful if the owner of the certificate designated the \
key to be a designated revoker.

If `--revocation-file` is not provided, then the certificate \
must include a certification-capable key.

`sq key subkey revoke` respects the reference time set by the top-level \
`--time` argument.  When set, it uses the specified time instead of \
the current time, when determining what keys are valid, and it sets \
the revocation certificate's creation time to the reference time \
instead of the current time.
",
)]
pub struct SubkeyRevokeCommand {
    #[clap(
        value_name = "FILE",
        long = "certificate-file",
        alias = "cert-file",
        help = "The certificate containing the subkey to revoke",
        long_help =
"Reads the certificate containing the subkey to revoke from FILE or stdin, \
if omitted.  It is an error for the file to contain more than one \
certificate."
    )]
    pub input: Option<PathBuf>,

    #[clap(
        long = "revocation-file",
        value_name = "KEY_FILE",
        help = "Signs the revocation certificate using the key in KEY_FILE",
        long_help =

"Signs the revocation certificate using the key in KEY_FILE.  If the key \
is different from the certificate, this creates a third-party revocation.  \
If this option is not provided, and the certificate includes secret key \
material, then that key is used to sign the revocation certificate.",
    )]
    pub secret_key_file: Option<PathBuf>,

    #[clap(
        long = "private-key-store",
        value_name = "KEY_STORE",
        help = "Provides parameters for private key store",
    )]
    pub private_key_store: Option<String>,

    #[clap(
        value_name = "SUBKEY",
        help = "The subkey to revoke",
        long_help =
"The subkey to revoke.  This must either be the subkey's Key ID or its \
fingerprint.",
    )]
    pub subkey: KeyHandle,

    #[clap(
        value_name = "REASON",
        required = true,
        help = "The reason for the revocation",
        long_help =
"The reason for the revocation.  This must be either: `compromised`,
`superseded`, `retired`, or `unspecified`:

  - `compromised` means that the secret key material may have been
    compromised.  Prefer this value if you suspect that the secret
    key has been leaked.

  - `superseded` means that the owner of the certificate has replaced
    it with a new certificate.  Prefer `compromised` if the secret
    key material has been compromised even if the certificate is also
    being replaced!  You should include the fingerprint of the new
    certificate in the message.

  - `retired` means that this certificate should not be used anymore,
    and there is no replacement.  This is appropriate when someone
    leaves an organisation.  Prefer `compromised` if the secret key
    material has been compromised even if the certificate is also
    being retired!  You should include how to contact the owner, or
    who to contact instead in the message.

  - `unspecified` means that none of the three other three reasons
    apply.  OpenPGP implementations conservatively treat this type
    of revocation similar to a compromised key.

If the reason happened in the past, you should specify that using the
`--time` argument.  This allows OpenPGP implementations to more
accurately reason about objects whose validity depends on the validity
of the certificate.",
    value_enum,
    )]
    pub reason: RevocationReason,

    #[clap(
        value_name = "MESSAGE",
        help = "A short, explanatory text",
        long_help =
"A short, explanatory text that is shown to a viewer of the revocation \
certificate.  It explains why the subkey has been revoked.  For \
instance, if Alice has created a new key, she would generate a \
`superseded` revocation certificate for her old key, and might include \
the message `I've created a new subkey, please refresh the certificate.`"
    )]
    pub message: String,

    #[clap(
        long,
        value_names = &["NAME", "VALUE"],
        number_of_values = 2,
        help = "Adds a notation to the certification.",
        long_help = "Adds a notation to the certification.  \
            A user-defined notation's name must be of the form \
            `name@a.domain.you.control.org`. If the notation's name starts \
            with a `!`, then the notation is marked as being critical.  If a \
            consumer of a signature doesn't understand a critical notation, \
            then it will ignore the signature.  The notation is marked as \
            being human readable."
    )]
    pub notation: Vec<String>,

    #[clap(
        default_value_t = FileOrStdout::default(),
        help = FileOrStdout::HELP_OPTIONAL,
        long,
        short,
        value_name = FileOrStdout::VALUE_NAME,
    )]
    pub output: FileOrStdout,

    #[clap(
        short = 'B',
        long,
        help = "Emits binary data",
    )]
    pub binary: bool,
}

