diff --git a/.gitignore b/.gitignore index 6de91e93..c4acec59 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .dir-locals.el /*.html /*.pdf +writelock \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 12068dd0..82990793 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -803,6 +803,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "dircpy" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8466f8d28ca6da4c9dfbbef6ad4bff6f2fdd5e412d821025b0d3f0a9d74a8c1e" +dependencies = [ + "jwalk", + "log", + "walkdir", +] + [[package]] name = "dirs" version = "5.0.1" @@ -1783,6 +1794,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" +dependencies = [ + "crossbeam", + "rayon", +] + [[package]] name = "lalrpop" version = "0.20.0" @@ -2755,7 +2776,7 @@ dependencies = [ "anyhow", "serde", "serde_yaml 0.8.26", - "textwrap", + "textwrap 0.15.2", "thiserror", ] @@ -3081,6 +3102,7 @@ dependencies = [ "chrono", "clap", "clap_complete", + "dircpy", "dirs", "dot-writer", "fehler", @@ -3104,7 +3126,7 @@ dependencies = [ "tempfile", "termcolor", "terminal_size", - "textwrap", + "textwrap 0.16.0", "tokio", ] @@ -3575,6 +3597,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.56" diff --git a/Cargo.toml b/Cargo.toml index 3f1e1233..8f919827 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,10 +63,12 @@ serde = { version = "1.0.137", features = ["derive"] } sequoia-openpgp = { version = "1.17", default-features = false } sequoia-net = { version = "0.28", default-features = false } subplot-build = { version = ">=0.7, <0.10", optional = true } +textwrap = "0.16" cfg-if = "1" terminal_size = ">=0.2.6, <0.4" [dev-dependencies] +dircpy = "0.3" subplotlib = ">=0.7, <0.10" fehler = "1.0.0" assert_cmd = "2" diff --git a/src/cli/cert/export.rs b/src/cli/cert/export.rs index 6c0be02d..73c67d6e 100644 --- a/src/cli/cert/export.rs +++ b/src/cli/cert/export.rs @@ -3,6 +3,70 @@ use clap::Parser; use sequoia_openpgp as openpgp; use openpgp::KeyHandle; +use crate::cli::examples; +use examples::Action; +use examples::Actions; +use examples::Example; + +const EXAMPLES: Actions = Actions { + actions: &[ + Action::Example(Example { + comment: "Exports all certificates.", + command: &[ + "sq", "cert", "export", + ], + }), + Action::Example(Example { + comment: "\ +Exports certificates with a matching User ID packet. The binding \ +signatures are checked, but the User IDs are not authenticated. \ +Note: this check is case sensitive.", + command: &[ + "sq", "cert", "export", + "--userid", "Alice ", + ], + }), + Action::Example(Example { + comment: "\ +Exports certificates with a User ID containing the email address. \ +The binding signatures are checked, but the User IDs are not \ +authenticated. Note: this check is case insensitive.", + command: &[ + "sq", "cert", "export", "--email", "alice@example.org", + ], + }), + Action::Example(Example { + comment: "\ +Exports certificates where the certificate (i.e., the primary key) \ +has the specified Key ID.", + command: &[ + "sq", "cert", "export", "--cert", "6F0073F60FD0CBF0", + ], + }), + Action::Example(Example { + comment: "\ +Exports certificates where the primary key or a subkey matches the \ +specified Key ID.", + command: &[ + "sq", "cert", "export", "--key", "24F3955B0B8DECC8", + ], + }), + Action::Example(Example { + comment: "\ +Exports certificates that contain a User ID with *either* (not both!) \ +email address. Note: this check is case insensitive.", + command: &[ + "sq", "cert", "export", + "--email", "alice@example.org", + "--email", "bob@example.org", + ], + }), + ], +}; + +test_examples!(sq_cert_export, EXAMPLES); + + #[derive(Parser, Debug)] #[clap( name = "export", @@ -25,34 +89,7 @@ all certificates. Fails if search criteria are specified and none of them matches any certificates. Note: this means if the certificate store is empty and no search criteria are specified, then this will return success.", - after_help = -"EXAMPLES: - -# Exports all certificates. -$ sq cert export > all.pgp - -# Exports certificates with a matching User ID packet. The binding -# signatures are checked, but the User IDs are not authenticated. -# Note: this check is case sensitive. -$ sq cert export --userid 'Alice ' - -# Exports certificates with a User ID containing the email address. -# The binding signatures are checked, but the User IDs are not -# authenticated. Note: this check is case insensitive. -$ sq cert export --email 'alice@example.org' - -# Exports certificates where the certificate (i.e., the primary key) -# has the specified Key ID. -$ sq cert export --cert 1234567812345678 - -# Exports certificates where the primary key or a subkey matches the -# specified Key ID. -$ sq cert export --key 1234567812345678 - -# Exports certificates that contain a User ID with *either* (not -# both!) email address. Note: this check is case insensitive. -$ sq cert export --email alice@example.org --email bob@example.org -", + after_help = EXAMPLES, )] pub struct Command { #[clap( diff --git a/src/cli/cert/import.rs b/src/cli/cert/import.rs index 06be6473..fc4d824e 100644 --- a/src/cli/cert/import.rs +++ b/src/cli/cert/import.rs @@ -2,6 +2,24 @@ use std::path::PathBuf; use clap::Parser; +use crate::cli::examples; +use examples::Action; +use examples::Actions; +use examples::Example; + +const EXAMPLES: Actions = Actions { + actions: &[ + Action::Example(Example { + comment: "Imports a certificate.", + command: &[ + "sq", "cert", "import", "juliet.pgp", + ], + }), + ] +}; + +test_examples!(sq_cert_import, EXAMPLES); + #[derive(Parser, Debug)] #[clap( name = "import", @@ -9,12 +27,7 @@ use clap::Parser; long_about = "Imports certificates into the local certificate store ", - after_help = -"EXAMPLES: - -# Imports a certificate. -$ sq cert import < juliet.pgp -", + after_help = EXAMPLES, )] pub struct Command { #[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")] diff --git a/src/cli/examples.rs b/src/cli/examples.rs new file mode 100644 index 00000000..af75fba9 --- /dev/null +++ b/src/cli/examples.rs @@ -0,0 +1,176 @@ +//! A framework to format and check examples. +//! +//! The help text for subcommands includes examples. That's great. +//! But, it is even better when they are tested. This module defines +//! data structures to describe the examples, mechanisms to format the +//! examples, and infrastructure to execute the examples. + +use clap::builder::IntoResettable; +use clap::builder::Resettable; + +/// A command that is executed by the integration test, but not shown +/// in the manual pages. +pub struct Setup<'a> { + pub command: &'a [ &'a str ], +} + +/// A command that is executed by the integration test, and shown in +/// the manual pages. +pub struct Example<'a> { + // A human-readable comment. + pub comment: &'a str, + pub command: &'a [ &'a str ], +} + +/// An action to execute. +#[allow(dead_code)] +pub enum Action<'a> { + /// A command that is executed by the integration test, but not + /// shown in the manual pages. + Setup(Setup<'a>), + + /// A command that is executed by the integration test, and shown + /// in the manual pages. + Example(Example<'a>), +} + +impl<'a> Action<'a> { + /// Return the action's command, if any. + #[allow(dead_code)] + pub fn command(&self) -> Option<&'a [ &'a str ]> { + match self { + Action::Setup(Setup { command, .. }) => Some(command), + Action::Example(Example { command, .. }) => Some(command), + } + } +} + +/// A sequence of actions to execute. +pub struct Actions<'a> { + pub actions: &'a [Action<'a>], +} + +impl<'a> IntoResettable for Actions<'a> { + fn into_resettable(self) -> Resettable { + // Default width when we aren't connected to a terminal. + let default_width = 72; + + let terminal_size = terminal_size::terminal_size(); + let width = if let Some((width, _height)) = terminal_size { + let width = width.0 as usize; + + if width < 40 { + // If the terminal is too narrow, then give up and use + // the default. + default_width + } else { + std::cmp::max(40, width) + } + } else { + 72 + }; + + let mut lines = vec![ "EXAMPLES:".to_string() ]; + + lines.extend(self.actions + .iter() + .filter_map(|action| { + let example = if let Action::Example(example) = action { + example + } else { + return None; + }; + + let comment = textwrap::indent( + &textwrap::wrap(example.comment, width).join("\n"), + "# "); + + let command = example.command.iter() + .fold(vec!["$".to_string()], |mut s, arg| { + // Quote the argument, if necessary. + let arg = if arg.contains(&[ + '\"', + ]) { + format!("'{}'", arg) + } else if arg.chars().any(char::is_whitespace) + || arg.contains(&[ + '`', '#', '$', '&', '*', '(', ')', + '\\', '|', '[', ']', '{', '}', + ';', '\'', '<', '>', '?', '!', + ]) + { + format!("\"{}\"", arg) + } else { + arg.to_string() + }; + + let last = s.last_mut().expect("have one"); + + let last_chars = last.chars().count(); + let arg_chars = arg.chars().count(); + + // Our manpage generate complains if an + // example is too long: + // + // warning: Command in example exceeds 64 chars: + if last_chars + 1 + arg_chars <= width.min(64) { + *last = format!("{} {}", last, arg); + } else { + *last = format!("{} \\", last); + s.push(format!(" {}", arg)); + } + + s + }) + .join("\n"); + + Some(format!("{}\n{}", comment, command)) + })); + + let text = lines.join("\n\n").into(); + + Resettable::Value(text) + } +} + +macro_rules! test_examples { + ($ident:ident, $actions:expr) => { + #[test] + fn $ident() { + use std::path::PathBuf; + + use tempfile::TempDir; + use assert_cmd::Command; + + + let fixtures = PathBuf::from(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/data/examples")); + + let tmp_dir = TempDir::new().unwrap(); + + dircpy::copy_dir(&fixtures, &tmp_dir) + .expect(&format!("Copying {:?} to {:?}", + fixtures, &tmp_dir)); + + let cert_store = tmp_dir.path().join("cert-store"); + + for action in $actions.actions { + let command = if let Some(command) = action.command() { + command + } else { + continue; + }; + + eprintln!("Executing: {:?}", command); + + Command::cargo_bin(command[0]).unwrap() + .current_dir(&tmp_dir) + .env("SQ_CERT_STORE", &cert_store) + .args(&command[1..]) + .assert() + .success(); + } + } + }; +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c602434a..dd6a7a1d 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -86,6 +86,9 @@ pub mod autocrypt; use sequoia_openpgp as openpgp; use openpgp::Fingerprint; +#[macro_use] +pub mod examples; + pub mod cert; pub mod decrypt; pub mod encrypt; diff --git a/src/cli/pki.rs b/src/cli/pki.rs index 2ced5ed5..6231bd99 100644 --- a/src/cli/pki.rs +++ b/src/cli/pki.rs @@ -14,6 +14,12 @@ use crate::cli::types::TrustAmount; pub mod certify; pub mod link; +use crate::cli::examples; +use examples::Action; +use examples::Setup; +use examples::Actions; +use examples::Example; + #[derive(Debug, Parser)] #[clap( name = "pki", @@ -54,6 +60,38 @@ pub enum Subcommands { Path(PathCommand), } +const AUTHENTICATE_EXAMPLES: Actions = Actions { + actions: &[ + // Link Alice's certificate. + Action::Setup(Setup { + command: &[ + "sq", "pki", "link", "add", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--all", + ], + }), + Action::Example(Example { + comment: "\ +Authenticate a specific binding.", + command: &[ + "sq", "pki", "authenticate", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", + "Alice ", + ] + }), + Action::Example(Example { + comment: "\ +Check whether we can authenticate any user ID with the specified email \ +address for the given certificate.", + command: &[ + "sq", "pki", "authenticate", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", + "--email", "alice@example.org", + ], + }), + ] +}; +test_examples!(sq_pki_authenticate, AUTHENTICATE_EXAMPLES); + /// Authenticate a binding. /// /// Authenticate a binding (a certificate and User ID) by looking @@ -72,20 +110,8 @@ pub enum Subcommands { #[derive(Parser, Debug)] #[clap( name = "authenticate", - after_help = -"EXAMPLES: - -# Authenticate a binding. -$ sq pki authenticate --partial \\ - C7966E3E7CE67DBBECE5FC154E2AD944CFC78C86 \\ - 'Alice ' - -# Try and authenticate each binding where the User ID has the -# specified email address. -$ sq pki authenticate \\ - C7966E3E7CE67DBBECE5FC154E2AD944CFC78C86 \\ - --email alice@example.org -")] + after_help = AUTHENTICATE_EXAMPLES, +)] pub struct AuthenticateCommand { #[command(flatten)] pub email: EmailArg, @@ -106,6 +132,34 @@ pub struct AuthenticateCommand { pub userid: UserIDArg, } +const LOOKUP_EXAMPLES: Actions = Actions { + actions: &[ + // Link Alice's certificate. + Action::Setup(Setup { + command: &[ + "sq", "pki", "link", "add", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--all", + ], + }), + Action::Example(Example { + comment: "\ +Lookup certificates that can be authenticated for the given user ID.", + command: &[ + "sq", "pki", "lookup", "Alice " + ], + }), + Action::Example(Example { + comment: "\ +Lookup certificates that have a user ID with the specified email \ +address, and that user ID can be authenticated.", + command: &[ + "sq", "pki", "lookup", "--email", "alice@example.org", + ], + }), + ] +}; +test_examples!(sq_pki_lookup, LOOKUP_EXAMPLES); + /// Lookup the certificates associated with a User ID. /// /// Identifies authenticated bindings (User ID and certificate @@ -121,15 +175,8 @@ pub struct AuthenticateCommand { #[derive(Parser, Debug)] #[clap( name = "lookup", - after_help = -"EXAMPLES: - -# Lookup a certificate with the given User ID. -$ sq pki lookup --partial 'Alice ' - -# Lookup a certificate with the given email address. -$ sq pki lookup --email alice@example.org -")] + after_help = LOOKUP_EXAMPLES, +)] pub struct LookupCommand { #[command(flatten)] pub email: EmailArg, @@ -147,6 +194,35 @@ pub struct LookupCommand { pub userid: UserIDArg, } +const IDENTIFY_EXAMPLES: Actions = Actions { + actions: &[ + // Link Alice's certificate. + Action::Setup(Setup { + command: &[ + "sq", "pki", "link", "add", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--all", + ], + }), + Action::Example(Example { + comment: "\ +Identify the user IDs that can be authenticated for the certificate.", + command: &[ + "sq", "pki", "identify", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", + ], + }), + Action::Example(Example { + comment: "\ +List all user IDs that have that have been certified by anyone.", + command: &[ + "sq", "pki", "identify", "--gossip", + "511257EBBF077B7AEDAE5D093F68CB84CE537C9A", + ], + }), + ] +}; +test_examples!(sq_pki_identify, IDENTIFY_EXAMPLES); + /// Identify a certificate. /// /// Identify a certificate by finding authenticated bindings (User @@ -162,17 +238,8 @@ pub struct LookupCommand { #[derive(Parser, Debug)] #[clap( name = "identify", - after_help = -"EXAMPLES: - -# Identify a certificate. -$ sq pki identify --partial \\ - C7B1406CD2F612E9CE2136156F2DA183236153AE - -# Get gossip about a certificate. -$ sq pki identify --gossip \\ - 3217C509292FC67076ECD75C7614269BDDF73B36 -")] + after_help = IDENTIFY_EXAMPLES, +)] pub struct IdentifyCommand { #[command(flatten)] pub gossip: GossipArg, @@ -187,6 +254,26 @@ pub struct IdentifyCommand { pub cert: CertArg, } +const LIST_EXAMPLES: Actions = Actions { + actions: &[ + Action::Setup(Setup { + command: &[ + "sq", "pki", "link", "add", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--all", + ], + }), + Action::Example(Example { + comment: "\ +List all bindings for user IDs containing an email address from \ +example.org, and that can be authenticated.", + command: &[ + "sq", "pki", "list", "@example.org", + ], + }) + ] +}; +test_examples!(sq_pki_list, LIST_EXAMPLES); + /// List all authenticated bindings (User ID and certificate /// pairs). /// @@ -204,13 +291,8 @@ pub struct IdentifyCommand { #[derive(Parser, Debug)] #[clap( name = "list", - after_help = -"EXAMPLES: - -# List all bindings for example.org that are at least partially -# authenticated. -$ sq pki list --partial @example.org -")] + after_help = LIST_EXAMPLES, +)] pub struct ListCommand { #[command(flatten)] pub email: EmailArg, @@ -233,6 +315,22 @@ pub struct ListCommand { pub pattern: Option, } +const PATH_EXAMPLES: Actions = Actions { + actions: &[ + Action::Example(Example { + comment: "\ +Verify that Alice ceritified a particular User ID for Bob's certificate.", + command: &[ + "sq", "pki", "path", + "EB28F26E2739A4870ECC47726F0073F60FD0CBF0", + "511257EBBF077B7AEDAE5D093F68CB84CE537C9A", + "Bob ", + ], + }) + ], +}; +test_examples!(sq_pki_path, PATH_EXAMPLES); + /// Verify the specified path. /// /// A path is a sequence of certificates starting at the root, and @@ -248,15 +346,8 @@ pub struct ListCommand { #[derive(Parser, Debug)] #[clap( name = "path", - after_help = -"EXAMPLES: - -# Verify that Neal ceritified Justus's certificate for a particular User ID. -$ sq pki path \\ - 8F17777118A33DDA9BA48E62AACB3243630052D9 \\ - CBCD8F030588653EEDD7E2659B7DD433F254904A \\ - 'Justus Winter ' -")] + after_help = PATH_EXAMPLES, +)] pub struct PathCommand { #[command(flatten)] pub gossip: GossipArg, diff --git a/tests/data/examples/README.md b/tests/data/examples/README.md new file mode 100644 index 00000000..c2e03042 --- /dev/null +++ b/tests/data/examples/README.md @@ -0,0 +1,29 @@ +# Introduction + +This directory contains data for the examples. + +The test suite executes each subcommand's examples. Each subcommand +has its own context (temporary directory), which is set to the current +working directory. The contents of this directory are copied into +that directory. If a subcommand has multiple examples, they are +execute after each other in the same context. + +By using static data, we can use known fingerprints in the examples. + +# Contents + +- alice-secret.pgp: A general-purpose certificate for Alice + . + + - Imported into the cert store. + +- bob-secret.pgp: A general-purpose certificate for Bob + . + + - Imported into the cert store. + - Certified by Alice. + +- juliet.pgp: A general-purpose certificate for Juliet Capulet + . + + - NOT imported into the cert store. diff --git a/tests/data/examples/alice-secret.pgp b/tests/data/examples/alice-secret.pgp new file mode 100644 index 00000000..7d7ee82d --- /dev/null +++ b/tests/data/examples/alice-secret.pgp @@ -0,0 +1,43 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: EB28 F26E 2739 A487 0ECC 4772 6F00 73F6 0FD0 CBF0 +Comment: Alice + +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 +=3xfI +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/data/examples/alice.pgp.rev b/tests/data/examples/alice.pgp.rev new file mode 100644 index 00000000..3df7e062 --- /dev/null +++ b/tests/data/examples/alice.pgp.rev @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Revocation certificate for +Comment: EB28 F26E 2739 A487 0ECC 4772 6F00 73F6 0FD0 CBF0 +Comment: Alice + +xjMEZcT3URYJKwYBBAHaRw8BAQdASgo8UO9lxwMTZkkBrqzyUMjF4flY8YtRT+pk +iSI2jlPCwAoEIBYKAH0FgmXE91EJEG8Ac/YP0MvwRxQAAAAAAB4AIHNhbHRAbm90 +YXRpb25zLnNlcXVvaWEtcGdwLm9yZ1F95aiZHzvfQFFbhbE0SsQy3vMeM+nfRves +qRkHca4nDR0AVW5zcGVjaWZpZWQWIQTrKPJuJzmkhw7MR3JvAHP2D9DL8AAALnYA +/24KMLbwtB17x6wo3b1dUu1WODBxQCJj28+hC+IBMhIaAPjYZbEum4myHuOPg/2v +Y5dSMQaYBpYYcYRu7g5UfugE +=+8np +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/data/examples/bob-secret.pgp b/tests/data/examples/bob-secret.pgp new file mode 100644 index 00000000..e763efab --- /dev/null +++ b/tests/data/examples/bob-secret.pgp @@ -0,0 +1,43 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 5112 57EB BF07 7B7A EDAE 5D09 3F68 CB84 CE53 7C9A +Comment: Bob + +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 +=qucl +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/data/examples/bob.pgp.rev b/tests/data/examples/bob.pgp.rev new file mode 100644 index 00000000..5fefde17 --- /dev/null +++ b/tests/data/examples/bob.pgp.rev @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Revocation certificate for +Comment: 5112 57EB BF07 7B7A EDAE 5D09 3F68 CB84 CE53 7C9A +Comment: Bob + +xjMEZcT3SRYJKwYBBAHaRw8BAQdAlN1milRLfSk/XwgEuKeZ27kK1sZcfRroOqjZ ++E+OJFLCwAsEIBYKAH0FgmXE90kJED9oy4TOU3yaRxQAAAAAAB4AIHNhbHRAbm90 +YXRpb25zLnNlcXVvaWEtcGdwLm9yZ4Bk7ktrmF8plMFxGXxG/UF8Ne49zSqaitah +szsKnEQcDR0AVW5zcGVjaWZpZWQWIQRRElfrvwd7eu2uXQk/aMuEzlN8mgAAmB0A +/0F03E1UJ8/iWMtc0U8a2KP0nKHLXQvdzZFTXYGQs+zBAQC/wldPhJ51fJucKrOh +QpVC2oxC1IkcBewTXG9yhl4oCg== +=iw4M +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/data/examples/cert-store/51/1257ebbf077b7aedae5d093f68cb84ce537c9a b/tests/data/examples/cert-store/51/1257ebbf077b7aedae5d093f68cb84ce537c9a new file mode 100644 index 00000000..d3b9f890 Binary files /dev/null and b/tests/data/examples/cert-store/51/1257ebbf077b7aedae5d093f68cb84ce537c9a differ diff --git a/tests/data/examples/cert-store/eb/28f26e2739a4870ecc47726f0073f60fd0cbf0 b/tests/data/examples/cert-store/eb/28f26e2739a4870ecc47726f0073f60fd0cbf0 new file mode 100644 index 00000000..6a87dbc2 Binary files /dev/null and b/tests/data/examples/cert-store/eb/28f26e2739a4870ecc47726f0073f60fd0cbf0 differ diff --git a/tests/data/examples/juliet-secret.pgp b/tests/data/examples/juliet-secret.pgp new file mode 100644 index 00000000..18ee168f --- /dev/null +++ b/tests/data/examples/juliet-secret.pgp @@ -0,0 +1,44 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: 7A58 B15E 3B94 5948 3D9F FA8D 40E2 99AC 5F2B 0872 +Comment: Juliet Capulet + +xVgEZcT8ehYJKwYBBAHaRw8BAQdA3lxROHivYEc3bXTGDtoMuhY3rPN9zitmSEsQ +3uMawTcAAQDdqSYdugT8ZYiXwmQupew5ioqT2q+K72pViJjuwLg4tRIBwsALBB8W +CgB9BYJlxPx6AwsJBwkQQOKZrF8rCHJHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu +c2VxdW9pYS1wZ3Aub3JnjVASiFqRzmkGe1LPiY4olToBnIdQ8Xx3b4kTwjJPj7kD +FQoIApsBAh4BFiEEelixXjuUWUg9n/qNQOKZrF8rCHIAAP4TAPsG6HdFuXr9LEUk +38Nms2/d38hoCdPfwsPnkZYg1bOK1QEAkJah86jp9JVSyAfBO5csKdNEf6R1dXY7 +9xu1Sw67xwjNI0p1bGlldCBDYXB1bGV0IDxqdWxpZXRAZXhhbXBsZS5vcmc+wsAO +BBMWCgCABYJlxPx6AwsJBwkQQOKZrF8rCHJHFAAAAAAAHgAgc2FsdEBub3RhdGlv +bnMuc2VxdW9pYS1wZ3Aub3Jn1FqFnmGhxznlWBLFoRa61r8XigiWHC5KrzWmtJ59 +u0oDFQoIApkBApsBAh4BFiEEelixXjuUWUg9n/qNQOKZrF8rCHIAACMeAQDZ/w4T +bbmIkxk/BCaRMbnayahiHhfF0c3JjyTXgFdwpQEAvpr686uEUv6IAfLZ6hQHnYdm +xbE+6lfSsEZDF/wiUQLHWARlxPx6FgkrBgEEAdpHDwEBB0A8syceHf01lFn79GP4 +MnatpXkNXtQYeH4FoDmzJ06oygABAKOJCYTCFGd/FuhSYS7Ft/9vvg5w+DBZotGq +mJEY+afZENPCwL8EGBYKATEFgmXE/HoJEEDimaxfKwhyRxQAAAAAAB4AIHNhbHRA +bm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ4sTm8Sq4aSpl0VRIrZiftDohY9IikmW +S6oMj2oFC1BuApsCvqAEGRYKAG8FgmXE/HoJEIMEhL2szEMlRxQAAAAAAB4AIHNh +bHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZyjznJGJYDmjWouq93Q9UX+2FV6d +siqY+4pueKLbnY4aFiEEzb/YfSE60fznV0XtgwSEvazMQyUAAG2+AP4u3zNN3FbO +dh/s0qEomEKIxJjRzyh2IzSomjTpvubGSQD/clAoUe02I7OpI4va44Zpw05ggHDz +fhdlJyEVeCY0+gQWIQR6WLFeO5RZSD2f+o1A4pmsXysIcgAAfXQA/jJw9jurz03j +ZPRY6iHsKYCWsh9WHxUFLD1I4ToS2sqjAP49kjsXhsdp9TrIzmfmQc8hI03Nnmb7 +MmrGNu1QkUbrAsdYBGXE/HoWCSsGAQQB2kcPAQEHQP3mXvoWQpYwbd6xWnL8HpfU +7gNra50ZxQ2h4pskKi3SAAEA0cWm7zsZgoNxyiSdK8oLZOQB0+bpYZXSkv5/ra+f +nxgR9cLAvwQYFgoBMQWCZcT8egkQQOKZrF8rCHJHFAAAAAAAHgAgc2FsdEBub3Rh +dGlvbnMuc2VxdW9pYS1wZ3Aub3Jnc0DfMNymRnpmuM1MysdH+VZ/X91dnrPjRqtR +n1qM1tACmyC+oAQZFgoAbwWCZcT8egkQdruOXnrPkTxHFAAAAAAAHgAgc2FsdEBu +b3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JngrW/Tm58TweTDzESH30JAXNgDlm109eV +FdZj+Cn0YfQWIQTf1CEOTUC++4f87+l2u45ees+RPAAAzcsBAPwPxVqIG1KvJ0ds +h8RoYYUCef+R3Ax5vSsnObb3FXscAQDSgesDtTZMw+9NLA0jhK+AUxyqzBdaMlbx +McG6gWuGDRYhBHpYsV47lFlIPZ/6jUDimaxfKwhyAAAr0wD/UVJZoEQNGYRfZ4X1 +1thogaYcem4o/ek1PxWUqn44eBQBAJguCPgRAU8xc1WaTTWXAKU12+jg/tfcEkBk +dfP/Ru8Ox10EZcT8ehIKKwYBBAGXVQEFAQEHQLljeYVLH9L60OcEfd1zhUpRoWKf +g2EoDZCtRTEcxLhWAwEIBwAA/2xY5dNDiFdUOu1ankI4u9sL6c0BSPU3HrenQtHO +3hOoEUzCwAAEGBYKAHIFgmXE/HoJEEDimaxfKwhyRxQAAAAAAB4AIHNhbHRAbm90 +YXRpb25zLnNlcXVvaWEtcGdwLm9yZ5w4sR1zgA6dC58p7zNfpE3vdqfDkCl3qbbr +UquoseL9ApsMFiEEelixXjuUWUg9n/qNQOKZrF8rCHIAAMBRAP4rF1TtU5MWY13d +LDJIsrmrVhaVFO8eGkk11bu4DsGjsAD/TAeeh3DTER/zyUssCRFd2ihvLSWk+laL +PCdvFL+YTwY= +=iIGf +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/data/examples/juliet.pgp b/tests/data/examples/juliet.pgp new file mode 100644 index 00000000..b9464c4f --- /dev/null +++ b/tests/data/examples/juliet.pgp @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: 7A58 B15E 3B94 5948 3D9F FA8D 40E2 99AC 5F2B 0872 +Comment: Juliet Capulet + +xjMEZcT8ehYJKwYBBAHaRw8BAQdA3lxROHivYEc3bXTGDtoMuhY3rPN9zitmSEsQ +3uMawTfCwAsEHxYKAH0FgmXE/HoDCwkHCRBA4pmsXysIckcUAAAAAAAeACBzYWx0 +QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeNUBKIWpHOaQZ7Us+JjiiVOgGch1Dx +fHdviRPCMk+PuQMVCggCmwECHgEWIQR6WLFeO5RZSD2f+o1A4pmsXysIcgAA/hMA ++wbod0W5ev0sRSTfw2azb93fyGgJ09/Cw+eRliDVs4rVAQCQlqHzqOn0lVLIB8E7 +lywp00R/pHV1djv3G7VLDrvHCM0jSnVsaWV0IENhcHVsZXQgPGp1bGlldEBleGFt +cGxlLm9yZz7CwA4EExYKAIAFgmXE/HoDCwkHCRBA4pmsXysIckcUAAAAAAAeACBz +YWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfUWoWeYaHHOeVYEsWhFrrWvxeK +CJYcLkqvNaa0nn27SgMVCggCmQECmwECHgEWIQR6WLFeO5RZSD2f+o1A4pmsXysI +cgAAIx4BANn/DhNtuYiTGT8EJpExudrJqGIeF8XRzcmPJNeAV3ClAQC+mvrzq4RS +/ogB8tnqFAedh2bFsT7qV9KwRkMX/CJRAs4zBGXE/HoWCSsGAQQB2kcPAQEHQDyz +Jx4d/TWUWfv0Y/gydq2leQ1e1Bh4fgWgObMnTqjKwsC/BBgWCgExBYJlxPx6CRBA +4pmsXysIckcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeL +E5vEquGkqZdFUSK2Yn7Q6IWPSIpJlkuqDI9qBQtQbgKbAr6gBBkWCgBvBYJlxPx6 +CRCDBIS9rMxDJUcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5v +cmco85yRiWA5o1qLqvd0PVF/thVenbIqmPuKbnii252OGhYhBM2/2H0hOtH851dF +7YMEhL2szEMlAABtvgD+Lt8zTdxWznYf7NKhKJhCiMSY0c8odiM0qJo06b7mxkkA +/3JQKFHtNiOzqSOL2uOGacNOYIBw834XZSchFXgmNPoEFiEEelixXjuUWUg9n/qN +QOKZrF8rCHIAAH10AP4ycPY7q89N42T0WOoh7CmAlrIfVh8VBSw9SOE6EtrKowD+ +PZI7F4bHafU6yM5n5kHPISNNzZ5m+zJqxjbtUJFG6wLOMwRlxPx6FgkrBgEEAdpH +DwEBB0D95l76FkKWMG3esVpy/B6X1O4Da2udGcUNoeKbJCot0sLAvwQYFgoBMQWC +ZcT8egkQQOKZrF8rCHJHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1w +Z3Aub3Jnc0DfMNymRnpmuM1MysdH+VZ/X91dnrPjRqtRn1qM1tACmyC+oAQZFgoA +bwWCZcT8egkQdruOXnrPkTxHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9p +YS1wZ3Aub3JngrW/Tm58TweTDzESH30JAXNgDlm109eVFdZj+Cn0YfQWIQTf1CEO +TUC++4f87+l2u45ees+RPAAAzcsBAPwPxVqIG1KvJ0dsh8RoYYUCef+R3Ax5vSsn +Obb3FXscAQDSgesDtTZMw+9NLA0jhK+AUxyqzBdaMlbxMcG6gWuGDRYhBHpYsV47 +lFlIPZ/6jUDimaxfKwhyAAAr0wD/UVJZoEQNGYRfZ4X11thogaYcem4o/ek1PxWU +qn44eBQBAJguCPgRAU8xc1WaTTWXAKU12+jg/tfcEkBkdfP/Ru8OzjgEZcT8ehIK +KwYBBAGXVQEFAQEHQLljeYVLH9L60OcEfd1zhUpRoWKfg2EoDZCtRTEcxLhWAwEI +B8LAAAQYFgoAcgWCZcT8egkQQOKZrF8rCHJHFAAAAAAAHgAgc2FsdEBub3RhdGlv +bnMuc2VxdW9pYS1wZ3Aub3JnnDixHXOADp0LnynvM1+kTe92p8OQKXeptutSq6ix +4v0CmwwWIQR6WLFeO5RZSD2f+o1A4pmsXysIcgAAwFEA/isXVO1TkxZjXd0sMkiy +uatWFpUU7x4aSTXVu7gOwaOwAP9MB56HcNMRH/PJSywJEV3aKG8tJaT6Vos8J28U +v5hPBg== +=K2JW +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/data/examples/juliet.pgp.rev b/tests/data/examples/juliet.pgp.rev new file mode 100644 index 00000000..e1144aa4 --- /dev/null +++ b/tests/data/examples/juliet.pgp.rev @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Revocation certificate for +Comment: 7A58 B15E 3B94 5948 3D9F FA8D 40E2 99AC 5F2B 0872 +Comment: Juliet Capulet + +xjMEZcT8ehYJKwYBBAHaRw8BAQdA3lxROHivYEc3bXTGDtoMuhY3rPN9zitmSEsQ +3uMawTfCwAsEIBYKAH0FgmXE/HoJEEDimaxfKwhyRxQAAAAAAB4AIHNhbHRAbm90 +YXRpb25zLnNlcXVvaWEtcGdwLm9yZxxCMSrMAAjxv4PmtEkn5JBHwnxAVs3ncsRF +3/T+EHBpDR0AVW5zcGVjaWZpZWQWIQR6WLFeO5RZSD2f+o1A4pmsXysIcgAAj+0B +AK7rgH7a6EVFGJaKHEQ2bs4fBlZdb8kPH+8/88k74DExAQDIUvW6MgRaGttYe/jG +W6J9FAXu+9cIkroYwCREOJYaCg== +=TEVF +-----END PGP PUBLIC KEY BLOCK-----