summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2020-04-15 10:36:40 +0200
committerJustus Winter <justus@sequoia-pgp.org>2020-04-21 17:10:27 +0200
commit8af5e4357c7348fe231b70a0fbe19788137caaa3 (patch)
treee8c28ca95583b4e4272cc95f7b2f4f1997dab6ac
parent50c6526133ae72adc26800f3cc8e9884dab88518 (diff)
sop: An implementation of the Stateless OpenPGP CLI using Sequoia.
- This adds a new frontend to Sequoia that implements the Stateless OpenPGP Command Line Interface. - Compared to sq, sop has a much smaller feature set and hence a smaller set of dependencies. It is less opinionated, and tries to faithfully implement the SOP protocol. We will use it to test Sequoia using the OpenPGP Interoperability Test Suite.
-rw-r--r--Cargo.lock94
-rw-r--r--Cargo.toml1
-rw-r--r--Makefile2
-rw-r--r--sop/Cargo.toml30
-rw-r--r--sop/Makefile24
-rw-r--r--sop/README.md7
-rw-r--r--sop/src/cli.rs315
-rw-r--r--sop/src/dates.rs95
-rw-r--r--sop/src/errors.rs101
-rw-r--r--sop/src/main.rs780
10 files changed, 1449 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 353d0346..9d2adb6c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -695,6 +695,14 @@ dependencies = [
]
[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "hermit-abi"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1253,6 +1261,30 @@ dependencies = [
]
[[package]]
+name = "proc-macro-error"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1589,6 +1621,7 @@ dependencies = [
"sequoia-net 0.16.0",
"sequoia-openpgp 0.16.0",
"sequoia-openpgp-ffi 0.16.0",
+ "sequoia-sop 0.16.0",
"sequoia-sqv 0.16.0",
"sequoia-store 0.16.0",
"sequoia-tool 0.16.0",
@@ -1737,6 +1770,17 @@ dependencies = [
]
[[package]]
+name = "sequoia-sop"
+version = "0.16.0"
+dependencies = [
+ "anyhow 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sequoia-openpgp 0.16.0",
+ "structopt 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thiserror 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "sequoia-sqv"
version = "0.16.0"
dependencies = [
@@ -1913,6 +1957,28 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "structopt"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "structopt-derive 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "syn"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1923,6 +1989,16 @@ dependencies = [
]
[[package]]
+name = "syn-mid"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "synstructure"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2236,6 +2312,11 @@ dependencies = [
]
[[package]]
+name = "unicode-segmentation"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2276,6 +2357,11 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "version_check"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "want"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2418,6 +2504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462"
+"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e"
"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0"
"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d"
@@ -2479,6 +2566,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
"checksum prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e"
+"checksum proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678"
+"checksum proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53"
"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
"checksum quickcheck 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f"
"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
@@ -2532,7 +2621,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
+"checksum structopt 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "ff6da2e8d107dfd7b74df5ef4d205c6aebee0706c647f6bc6a2d5789905c00fb"
+"checksum structopt-derive 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a489c87c08fbaf12e386665109dd13470dcc9c4583ea3e10dd2b4523e5ebd9ac"
"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
+"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42"
@@ -2561,6 +2653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
+"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
@@ -2568,6 +2661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
diff --git a/Cargo.toml b/Cargo.toml
index 169a2e72..3a10467c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,6 +34,7 @@ sequoia-ipc = { path = "ipc", version = "0.16" }
sequoia-net = { path = "net", version = "0.16" }
sequoia-store = { path = "store", version = "0.16" }
sequoia-tool = { path = "tool", version = "0.16" }
+sequoia-sop = { path = "sop", version = "0.16" }
sequoia-sqv = { path = "sqv", version = "0.16" }
sequoia-guide = { path = "guide", version = "0.16" }
diff --git a/Makefile b/Makefile
index cdf4c1ed..a9c4ad14 100644
--- a/Makefile
+++ b/Makefile
@@ -83,6 +83,7 @@ build-release:
$(MAKE) -Copenpgp-ffi build-release
$(MAKE) -Cffi build-release
$(MAKE) -Csqv build-release
+ $(MAKE) -Csop build-release
.PHONY: install
install: build-release
@@ -95,6 +96,7 @@ install: build-release
$(MAKE) -Copenpgp-ffi install
$(MAKE) -Cffi install
$(MAKE) -Csqv install
+ $(MAKE) -Csop install
$(INSTALL) -d $(DESTDIR)$(PREFIX)/share/zsh/site-functions
$(INSTALL) -t $(DESTDIR)$(PREFIX)/share/zsh/site-functions \
$(CARGO_TARGET_DIR)/_sq
diff --git a/sop/Cargo.toml b/sop/Cargo.toml
new file mode 100644
index 00000000..4e30997f
--- /dev/null
+++ b/sop/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "sequoia-sop"
+description = "An implementation of the Stateless OpenPGP Command Line Interface using Sequoia"
+version = "0.16.0"
+authors = [
+ "Justus Winter <justus@sequoia-pgp.org>",
+]
+documentation = "https://docs.sequoia-pgp.org/0.16.0/sop"
+homepage = "https://sequoia-pgp.org/"
+repository = "https://gitlab.com/sequoia-pgp/sequoia"
+readme = "README.md"
+keywords = ["cryptography", "openpgp", "pgp", "sop", "stateless-openpgp-protocol"]
+categories = ["cryptography", "command-line-utilities"]
+license = "GPL-2.0-or-later"
+edition = "2018"
+
+[badges]
+gitlab = { repository = "sequoia-pgp/sequoia" }
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+sequoia-openpgp = { path = "../openpgp", version = "0.16" }
+anyhow = "1"
+chrono = "0.4"
+structopt = { version = "0.3", default-features = false }
+thiserror = "1"
+
+[[bin]]
+name = "sqop"
+path = "src/main.rs"
diff --git a/sop/Makefile b/sop/Makefile
new file mode 100644
index 00000000..5ffb9b45
--- /dev/null
+++ b/sop/Makefile
@@ -0,0 +1,24 @@
+# Configuration.
+CARGO_TARGET_DIR ?= $(shell pwd)/../target
+# We currently only support absolute paths.
+CARGO_TARGET_DIR := $(abspath $(CARGO_TARGET_DIR))
+SOP ?= $(CARGO_TARGET_DIR)/debug/sqop
+
+# Tools.
+CARGO ?= cargo
+ifeq ($(shell uname -s), Darwin)
+ INSTALL ?= ginstall
+else
+ INSTALL ?= install
+endif
+
+# Installation.
+.PHONY: build-release
+build-release:
+ CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
+ $(CARGO) build $(CARGO_FLAGS) --release --package sequoia-sop
+
+.PHONY: install
+install: build-release
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -t $(DESTDIR)$(PREFIX)/bin $(CARGO_TARGET_DIR)/release/sqop
diff --git a/sop/README.md b/sop/README.md
new file mode 100644
index 00000000..0b00731a
--- /dev/null
+++ b/sop/README.md
@@ -0,0 +1,7 @@
+An implementation of the Stateless OpenPGP Command Line Interface
+using Sequoia.
+
+This implements a subset of the [Stateless OpenPGP Command Line
+Interface] using the Sequoia OpenPGP implementation.
+
+ [Stateless OpenPGP Command Line Interface]: https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/
diff --git a/sop/src/cli.rs b/sop/src/cli.rs
new file mode 100644
index 00000000..58bbdd2d
--- /dev/null
+++ b/sop/src/cli.rs
@@ -0,0 +1,315 @@
+use std::fmt;
+use std::path::Path;
+
+use anyhow::Context;
+use chrono::{DateTime, offset::Utc};
+use structopt::StructOpt;
+
+use sequoia_openpgp as openpgp;
+use openpgp::{
+ Cert,
+ crypto::{
+ Password,
+ },
+ types::{
+ SignatureType,
+ DataFormat,
+ },
+ parse::Parse,
+};
+
+use super::{
+ dates,
+ Error,
+ Result,
+};
+
+#[derive(StructOpt)]
+#[structopt(about = "An implementation of the \
+ Stateless OpenPGP Command Line Interface \
+ using Sequoia")]
+pub enum SOP {
+ /// Prints version information.
+ Version {
+ },
+ /// Generates a Secret Key.
+ GenerateKey {
+ /// Don't ASCII-armor output.
+ #[structopt(long)]
+ no_armor: bool,
+ /// UserIDs for the generated key.
+ userids: Vec<String>,
+ },
+ /// Extracts a Certificate from a Secret Key.
+ ExtractCert {
+ /// Don't ASCII-armor output.
+ #[structopt(long)]
+ no_armor: bool,
+ },
+ /// Creates Detached Signatures.
+ Sign {
+ /// Don't ASCII-armor output.
+ #[structopt(long)]
+ no_armor: bool,
+ /// Sign binary data or UTF-8 text.
+ #[structopt(default_value = "binary", long = "as")]
+ as_: SignAs,
+ /// Keys for signing.
+ keys: Vec<String>,
+ },
+ /// Verifies Detached Signatures.
+ Verify {
+ /// Consider signatures before this date invalid.
+ #[structopt(long, parse(try_from_str = dates::parse_bound_round_down))]
+ not_before: Option<DateTime<Utc>>,
+ /// Consider signatures after this date invalid.
+ #[structopt(long, parse(try_from_str = dates::parse_bound_round_up))]
+ not_after: Option<DateTime<Utc>>,
+ /// Signatures to verify.
+ signatures: String,
+ /// Certs for verification.
+ certs: Vec<String>,
+ },
+ /// Encrypts a Message.
+ Encrypt {
+ /// Don't ASCII-armor output.
+ #[structopt(long)]
+ no_armor: bool,
+ /// Encrypt binary data, UTF-8 text, or MIME data.
+ #[structopt(default_value = "binary", long = "as")]
+ as_: EncryptAs,
+ /// Encrypt with passwords.
+ #[structopt(long)]
+ with_password: Vec<String>,
+ /// Keys for signing.
+ #[structopt(long)]
+ sign_with: Vec<String>,
+ /// Encrypt for these certs.
+ certs: Vec<String>,
+ },
+ /// Decrypts a Message.
+ Decrypt {
+ /// Write the session key here.
+ #[structopt(long)]
+ session_key_out: Option<String>,
+ /// Try to decrypt with this session key.
+ #[structopt(long)]
+ with_session_key: Vec<String>,
+ /// Try to decrypt with this password.
+ #[structopt(long)]
+ with_password: Vec<String>,
+ /// Write verification result here.
+ #[structopt(long)]
+ verify_out: Option<String>,
+ /// Certs for verification.
+ #[structopt(long)]
+ verify_with: Vec<String>,
+ /// Consider signatures before this date invalid.
+ #[structopt(long, parse(try_from_str = dates::parse_bound_round_down))]
+ verify_not_before: Option<DateTime<Utc>>,
+ /// Consider signatures after this date invalid.
+ #[structopt(long, parse(try_from_str = dates::parse_bound_round_up))]
+ verify_not_after: Option<DateTime<Utc>>,
+ /// Try to decrypt with this key.
+ key: Vec<String>,
+ },
+ /// Converts binary OpenPGP data to ASCII
+ Armor {
+ /// Indicates the kind of data
+ #[structopt(long, default_value = "auto")]
+ label: ArmorKind,
+ },
+ /// Converts ASCII OpenPGP data to binary
+ Dearmor {
+ },
+ /// Unsupported subcommand.
+ #[structopt(external_subcommand)]
+ Unsupported(Vec<String>),
+}
+
+#[derive(Clone, Copy)]
+pub enum SignAs {
+ Binary,
+ Text,
+}
+
+impl std::str::FromStr for SignAs {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> openpgp::Result<Self> {
+ match s {
+ "binary" => Ok(SignAs::Binary),
+ "text" => Ok(SignAs::Text),
+ _ => Err(anyhow::anyhow!(
+ "{:?}, expected one of {{binary|text}}", s)),
+ }
+ }
+}
+
+impl fmt::Display for SignAs {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ SignAs::Binary => f.write_str("binary"),
+ SignAs::Text => f.write_str("text"),
+ }
+ }
+}
+
+impl From<SignAs> for SignatureType {
+ fn from(a: SignAs) -> Self {
+ match a {
+ SignAs::Binary => SignatureType::Binary,
+ SignAs::Text => SignatureType::Text,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum EncryptAs {
+ Binary,
+ Text,
+ MIME,
+}
+
+impl std::str::FromStr for EncryptAs {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> openpgp::Result<Self> {
+ match s {
+ "binary" => Ok(EncryptAs::Binary),
+ "text" => Ok(EncryptAs::Text),
+ "mime" => Ok(EncryptAs::MIME),
+ _ => Err(anyhow::anyhow!(
+ "{}, expected one of {{binary|text|mime}}", s)),
+ }
+ }
+}
+
+impl fmt::Display for EncryptAs {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ EncryptAs::Binary => f.write_str("binary"),
+ EncryptAs::Text => f.write_str("text"),
+ EncryptAs::MIME => f.write_str("mime"),
+ }
+ }
+}
+
+impl From<EncryptAs> for SignatureType {
+ fn from(a: EncryptAs) -> Self {
+ match a {
+ EncryptAs::Binary => SignatureType::Binary,
+ EncryptAs::Text => SignatureType::Text,
+ // XXX: We should inspect the serialized MIME structure
+ // and use Text if it is UTF-8, Binary otherwise. But, we
+ // cannot be bothered at this point.
+ EncryptAs::MIME => SignatureType::Binary,
+ }
+ }
+}
+
+impl From<EncryptAs> for DataFormat {
+ fn from(a: EncryptAs) -> Self {
+ match a {
+ EncryptAs::Binary => DataFormat::Binary,
+ EncryptAs::Text => DataFormat::Text,
+ EncryptAs::MIME => DataFormat::MIME,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum ArmorKind {
+ Auto,
+ Sig,
+ Key,
+ Cert,
+ Message,
+}
+
+impl std::str::FromStr for ArmorKind {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> openpgp::Result<Self> {
+ match s {
+ "auto" => Ok(ArmorKind::Auto),
+ "sig" => Ok(ArmorKind::Sig),
+ "key" => Ok(ArmorKind::Key),
+ "cert" => Ok(ArmorKind::Cert),
+ "message" => Ok(ArmorKind::Message),
+ _ => Err(anyhow::anyhow!(
+ "{:?}, expected one of \
+ {{auto|sig|key|cert|message}}", s)),
+ }
+ }
+}
+
+impl fmt::Display for ArmorKind {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ArmorKind::Auto => f.write_str("auto"),
+ ArmorKind::Sig => f.write_str("sig"),
+ ArmorKind::Key => f.write_str("key"),
+ ArmorKind::Cert => f.write_str("cert"),
+ ArmorKind::Message => f.write_str("message"),
+ }
+ }
+}
+
+
+fn is_special_designator<S: AsRef<str>>(file: S) -> bool {
+ file.as_ref().starts_with("@")
+}
+
+/// Loads the given (special) file.
+pub fn load_file<S: AsRef<str>>(file: S) -> Result<std::fs::File> {
+ let f = file.as_ref();
+
+ if is_special_designator(f) {
+ if Path::new(f).exists() {
+ return Err(anyhow::Error::from(Error::AmbiguousInput))
+ .context(format!("File {:?} exists", f));
+ }
+
+ return Err(anyhow::Error::from(Error::UnsupportedSpecialPrefix));
+ }
+
+ std::fs::File::open(f).map_err(|_| Error::MissingInput)
+ .context(format!("Failed to open file {:?}", f))
+}
+
+/// Creates the given (special) file.
+pub fn create_file<S: AsRef<str>>(file: S) -> Result<std::fs::File> {
+ let f = file.as_ref();
+
+ if is_special_designator(f) {
+ if Path::new(f).exists() {
+ return Err(anyhow::Error::from(Error::AmbiguousInput))
+ .context(format!("File {:?} exists", f));
+ }
+
+ return Err(anyhow::Error::from(Error::UnsupportedSpecialPrefix));
+ }
+
+ if Path::new(f).exists() {
+ return Err(anyhow::Error::from(Error::OutputExists))
+ .context(format!("File {:?} exists", f));
+ }
+
+ std::fs::File::create(f).map_err(|_| Error::MissingInput) // XXX
+ .context(format!("Failed to create file {:?}", f))
+}
+
+/// Loads the certs given by the (special) files.
+pub fn load_certs(files: Vec<String>) -> Result<Vec<Cert>> {
+ let mut certs = vec![];
+ for f in files {
+ let r = load_file(&f)?;
+ certs.push(Cert::from_reader(r).map_err(|_| Error::BadData)
+ .context(format!("Failed to load key from file {:?}", f))?);
+ }
+ Ok(certs)
+}
+
+/// Frobnicates the strings and converts them to passwords.
+pub fn frob_passwords(p: Vec<String>) -> Result<Vec<Password>> {
+ // XXX: Maybe do additional checks.
+ Ok(p.iter().map(|p| p.trim_end().into()).collect())
+}
diff --git a/sop/src/dates.rs b/sop/src/dates.rs
new file mode 100644
index 00000000..b76fe1fc
--- /dev/null
+++ b/sop/src/dates.rs
@@ -0,0 +1,95 @@
+use chrono::{DateTime, offset::Utc};
+
+use crate::Result;
+
+/// Parses the given string depicting a ISO 8601 timestamp, rounding down.
+pub fn parse_bound_round_down(s: &str) -> Result<DateTime<Utc>> {
+ match s {
+ // XXX: parse "-" to None once we figure out how to do that
+ // with structopt.
+ "now" => Ok(Utc::now()),
+ _ => parse_iso8601(s, chrono::NaiveTime::from_hms(0, 0, 0)),
+ }
+}
+
+/// Parses the given string depicting a ISO 8601 timestamp, rounding up.
+pub fn parse_bound_round_up(s: &str) -> Result<DateTime<Utc>> {
+ match s {
+ // XXX: parse "-" to None once we figure out how to do that
+ // with structopt.
+ "now" => Ok(Utc::now()),
+ _ => parse_iso8601(s, chrono::NaiveTime::from_hms(23, 59, 59)),
+ }
+}
+
+/// Parses the given string depicting a ISO 8601 timestamp.
+fn parse_iso8601(s: &str, pad_date_with: chrono::NaiveTime)
+ -> Result<DateTime<Utc>>
+{
+ // If you modify this function this function, synchronize the
+ // changes with the copy in sqv.rs!
+ for f in &[
+ "%Y-%m-%dT%H:%M:%S%#z",
+ "%Y-%m-%dT%H:%M:%S",
+ "%Y-%m-%dT%H:%M%#z",
+ "%Y-%m-%dT%H:%M",
+ "%Y-%m-%dT%H%#z",
+ "%Y-%m-%dT%H",
+ "%Y%m%dT%H%M%S%#z",
+ "%Y%m%dT%H%M%S",
+ "%Y%m%dT%H%M%#z",
+ "%Y%m%dT%H%M",
+ "%Y%m%dT%H%#z",
+ "%Y%m%dT%H",
+ ] {
+ if f.ends_with("%#z") {
+ if let Ok(d) = DateTime::parse_from_str(s, *f) {
+ return Ok(d.into());
+ }
+ } else {
+ if let Ok(d) = chrono::NaiveDateTime::parse_from_str(s, *f) {
+ return Ok(DateTime::from_utc(d, Utc));
+ }
+ }
+ }
+ for f in &[
+ "%Y-%m-%d",
+ "%Y-%m",
+ "%Y-%j",