sequoia-sq 1.4.0

Command-line frontends for Sequoia
use std::time::Duration;

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

use crate::cli::types::ClapData;
use crate::cli::types::FileOrCertStore;
use crate::cli::types::FileOrStdout;
use crate::cli::types::cert_designator::{
    CertDesignators,
    CertUserIDEmailFileArgs,
    CertOrAll,
    CertPrefix,
};

use crate::cli::examples::*;

#[derive(Parser, Debug)]
#[clap(
    name = "dane",
    about = "Retrieve and publishes certificates via DANE",
    long_about =
"Retrieve and publishes certificates via DANE

DNS-Based Authentication of Named Entities (DANE) is a method for \
publishing and retrieving certificates in DNS as specified in RFC \
7929.
",

    subcommand_required = true,
    arg_required_else_help = true,
    disable_help_subcommand = true,
)]
pub struct Command {
    #[clap(subcommand)]
    pub subcommand: Subcommands,
}

#[derive(Debug, Subcommand)]
pub enum Subcommands {
    Search(SearchCommand),
    Generate(GenerateCommand),
}

const GENERATE_EXAMPLES: Actions = Actions {
    actions: &[
        Action::setup().command(&[
            "sq", "cert", "import", "juliet.pgp",
        ]).build(),

        Action::setup().command(&[
            "sq", "pki", "link", "add",
            "--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
            "--userid=Alice <alice@example.org>",
        ]).build(),

        Action::example().comment(
            "Generate DANE records from juliet.pgp for example.org.",
        ).command(&[
            "sq", "network", "dane", "generate",
            "--domain=example.org",
            "--cert-file=juliet.pgp",
        ]).build(),

        Action::example().comment(
            "Generate DANE records for all certs with an authenticated \
             user ID in example.org.",
        ).command(&[
            "sq", "network", "dane", "generate",
            "--domain=example.org",
            "--all",
        ]).build(),
    ],
};
test_examples!(sq_network_dane_generate, GENERATE_EXAMPLES);

#[derive(Debug, Args)]
#[clap(
    about = "Generate DANE records for the given domain and certs",
    long_about =
"Generate DANE records for the given domain and certs

The certificates are minimized, and one record per email address is \
emitted.  If multiple user IDs map to one email address, then all \
matching user IDs are included in the emitted certificates.

By default, OPENPGPKEY resource records are emitted.  If your DNS \
server doesn't understand those, use `--type generic` to emit generic \
records instead.
",
    after_help = GENERATE_EXAMPLES,
)]
pub struct GenerateCommand {
    #[command(flatten)]
    pub certs: CertDesignators<CertUserIDEmailFileArgs,
                               CertPrefix,
                               CertOrAll>,

    #[clap(
        long = "all",
        help = "Publish authenticated certs with a user ID matching domain",
        long_help = "\
Publish authenticated certs with a user ID matching domain

Use all certificates that have a user ID matching the domain given \
to the `--domain` parameter that can be fully authenticated.",
    )]
    pub all: bool,

    #[clap(
        long = "domain",
        value_name = "FQDN",
        help = "Generate DANE records for this domain name",
    )]
    pub domain: String,

    #[clap(
        long = "ttl",
        value_name = "DURATION",
        value_parser = |arg: &str| -> Result<Duration, std::num::ParseIntError>
            { Ok(Duration::from_secs(arg.parse()?)) },
        default_value = "10800",
        help = "Set the TTL (maximum cache duration) of the resource records",
    )]
    pub ttl: Duration,
    #[clap(
        long = "size-limit",
        value_name = "BYTES",
        default_value = "12288",
        help = "Try to shrink the certificates to this size",
    )]
    pub size_limit: usize,

    #[clap(
        long = "type",
        value_name = "TYPE",
        default_value = "openpgp",
        help = "Change the emitted resource record type",
    )]
    pub typ: ResourceRecordType,
}

#[derive(clap::ValueEnum, Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ResourceRecordType {
    #[default]
    #[clap(name = "openpgp")]
    OpenPGP,
    Generic,
}

#[derive(Debug, Args)]
#[clap(
    about = "Retrieve certificates using DANE",
    long_about =
"Retrieve certificates using DANE

By default, any returned certificates are stored in the local \
certificate store.  This can be overridden by using `--output` \
option.

When a certificate is retrieved using DANE, and imported into the \
local certificate store, any User IDs with the email address that was \
looked up are certificated with a local DANE-specific key.  That proxy \
certificate is in turn certified as a minimally trusted CA (trust \
amount: 1 of 120) by the local trust root.  How much the DANE proxy CA \
is trusted can be tuned using `sq pki link add` or `sq pki link retract` \
in the usual way.
",
    after_help = SEARCH_EXAMPLES,
)]
pub struct SearchCommand {
    #[clap(
        long,
        conflicts_with = "addresses",
        help = "Fetch updates for all known certificates",
    )]
    pub all: bool,

    #[clap(
        value_name = "ADDRESS",
        required = true,
        help = "Retrieve certificate(s) for ADDRESS",
    )]
    pub addresses: Vec<String>,

    #[clap(
        help = FileOrCertStore::HELP_OPTIONAL,
        long,
        value_name = FileOrCertStore::VALUE_NAME,
    )]
    pub output: Option<FileOrStdout>,
}

const SEARCH_EXAMPLES: Actions = Actions {
    actions: &[
        Action::example().comment(
            "Retrieve Alice's certificate over DANE."
        ).command(&[
            "sq", "network", "dane", "search", "alice@example.org",
        ]).syntax_check(),

        Action::example().comment(
            "Retrieve updates for all known certificates over DANE."
        ).command(&[
            "sq", "network", "dane", "search", "--all",
        ]).syntax_check(),
    ],
};
test_examples!(sq_network_dane_search, SEARCH_EXAMPLES);