summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--store/src/backend.rs171
-rw-r--r--store/src/lib.rs238
-rw-r--r--store/src/store_protocol.capnp37
-rw-r--r--tool/Cargo.toml1
-rw-r--r--tool/src/main.rs82
-rw-r--r--tool/src/usage.rs97
6 files changed, 624 insertions, 2 deletions
diff --git a/store/src/backend.rs b/store/src/backend.rs
index 6bff34e0..fcd3cc67 100644
--- a/store/src/backend.rs
+++ b/store/src/backend.rs
@@ -107,6 +107,29 @@ impl node::Server for NodeServer {
node::store::ToClient::new(store).from_server::<capnp_rpc::Server>()));
Promise::ok(())
}
+
+ fn iter(&mut self,
+ params: node::IterParams,
+ mut results: node::IterResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let prefix = pry!(pry!(params.get()).get_domain_prefix());
+ let iter = StoreIterServer::new(self.c.clone(), prefix);
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::store_iter::ToClient::new(iter).from_server::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+
+ fn iter_keys(&mut self,
+ _: node::IterKeysParams,
+ mut results: node::IterKeysResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let iter = KeyIterServer::new(self.c.clone());
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::key_iter::ToClient::new(iter).from_server::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
}
struct StoreServer {
@@ -214,6 +237,17 @@ impl node::store::Server for StoreServer {
&[&self.id]));
Promise::ok(())
}
+
+ fn iter(&mut self,
+ _: node::store::IterParams,
+ mut results: node::store::IterResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let iter = BindingIterServer::new(self.c.clone(), self.id);
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::binding_iter::ToClient::new(iter).from_server::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
}
struct BindingServer {
@@ -458,6 +492,137 @@ impl node::key::Server for KeyServer {
}
}
+/* Iterators. */
+
+struct StoreIterServer {
+ c: Rc<Connection>,
+ prefix: String,
+ n: i64,
+}
+
+impl StoreIterServer {
+ fn new(c: Rc<Connection>, prefix: &str) -> Self {
+ StoreIterServer{c: c, prefix: String::from(prefix) + "%", n: 0}
+ }
+}
+
+impl node::store_iter::Server for StoreIterServer {
+ fn next(&mut self,
+ _: node::store_iter::NextParams,
+ mut results: node::store_iter::NextResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let (id, domain, name, network_policy): (i64, String, String, i64) =
+ sry!(self.c.query_row(
+ "SELECT id, domain, name, network_policy FROM stores
+ WHERE id > ?1 AND domain like ?2
+ ORDER BY id LIMIT 1",
+ &[&self.n, &self.prefix],
+ |row| (row.get(0), row.get(1), row.get(2), row.get(3))));
+
+ let count: i64 =
+ sry!(self.c.query_row(
+ "SELECT count(*) FROM bindings WHERE store = ?1",
+ &[&id], |row| row.get(0)));
+ assert!(count >= 0);
+
+ // We cannot implement FromSql and friends for
+ // core::NetworkPolicy, hence we need to do it by foot.
+ if network_policy < 0 || network_policy > 3 {
+ fail!(node::Error::SystemError);
+ }
+ let network_policy = core::NetworkPolicy::from(network_policy as u8);
+
+ let mut entry = pry!(results.get().get_result()).init_ok();
+ entry.set_domain(&domain);
+ entry.set_name(&name);
+ entry.set_network_policy(network_policy.into());
+ entry.set_entries(count as u64);
+ entry.set_store(node::store::ToClient::new(
+ StoreServer::new(self.c.clone(), id)).from_server::<capnp_rpc::Server>());
+ self.n = id;
+ Promise::ok(())
+ }
+}
+
+struct BindingIterServer {
+ c: Rc<Connection>,
+ store_id: i64,
+ n: i64,
+}
+
+impl BindingIterServer {
+ fn new(c: Rc<Connection>, store_id: i64) -> Self {
+ BindingIterServer{c: c, store_id: store_id, n: 0}
+ }
+}
+
+impl node::binding_iter::Server for BindingIterServer {
+ fn next(&mut self,
+ _: node::binding_iter::NextParams,
+ mut results: node::binding_iter::NextResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let (id, label, fingerprint): (i64, String, String) =
+ sry!(self.c.query_row(
+ "SELECT bindings.id, bindings.label, keys.fingerprint FROM bindings
+ JOIN keys ON bindings.key = keys.id
+ WHERE bindings.id > ?1 AND bindings.store = ?2
+ ORDER BY bindings.id LIMIT 1",
+ &[&self.n, &self.store_id],
+ |row| (row.get(0), row.get(1), row.get(2))));
+
+ let mut entry = pry!(results.get().get_result()).init_ok();
+ entry.set_label(&label);
+ entry.set_fingerprint(&fingerprint);
+ entry.set_binding(node::binding::ToClient::new(
+ BindingServer::new(self.c.clone(), id)).from_server::<capnp_rpc::Server>());
+ self.n = id;
+ Promise::ok(())
+ }
+}
+
+struct KeyIterServer {
+ c: Rc<Connection>,
+ n: i64,
+}
+
+impl KeyIterServer {
+ fn new(c: Rc<Connection>) -> Self {
+ KeyIterServer{c: c, n: 0}
+ }
+}
+
+impl node::key_iter::Server for KeyIterServer {
+ fn next(&mut self,
+ _: node::key_iter::NextParams,
+ mut results: node::key_iter::NextResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let (id, fingerprint): (i64, String) =
+ sry!(self.c.query_row(
+ "SELECT id, fingerprint FROM keys
+ WHERE keys.id > ?1
+ ORDER BY id LIMIT 1",
+ &[&self.n],
+ |row| (row.get(0), row.get(1))));
+
+ let count: i64 =
+ sry!(self.c.query_row(
+ "SELECT count(*) FROM bindings WHERE key = ?1",
+ &[&id], |row| row.get(0)));
+ assert!(count >= 0);
+
+ let mut entry = pry!(results.get().get_result()).init_ok();
+ entry.set_fingerprint(&fingerprint);
+ entry.set_bindings(count as u64);
+ entry.set_key(node::key::ToClient::new(
+ KeyServer::new(self.c.clone(), id)).from_server::<capnp_rpc::Server>());
+ self.n = id;
+ Promise::ok(())
+ }
+}
+
/* Error handling. */
impl fmt::Debug for node::Error {
@@ -617,6 +782,12 @@ impl<'a> From<&'a core::NetworkPolicy> for node::NetworkPolicy {
}
}
+impl From<core::NetworkPolicy> for node::NetworkPolicy {
+ fn from(policy: core::NetworkPolicy) -> Self {
+ (&policy).into()
+ }
+}
+
impl From<node::NetworkPolicy> for core::NetworkPolicy {
fn from(policy: node::NetworkPolicy) -> Self {
match policy {
diff --git a/store/src/lib.rs b/store/src/lib.rs
index b81d3606..028945e5 100644
--- a/store/src/lib.rs
+++ b/store/src/lib.rs
@@ -143,7 +143,52 @@ impl Store {
request.get().set_name(name);
let store = make_request!(&mut core, request)?;
- Ok(Store{name: name.into(), core: Rc::new(RefCell::new(core)), store: store})
+ Ok(Self::new(Rc::new(RefCell::new(core)), name, store))
+ }
+
+ fn new(core: Rc<RefCell<Core>>, name: &str, store: node::store::Client) -> Self {
+ Store{core: core, name: name.into(), store: store}
+ }
+
+ /// Lists all stores with the given prefix.
+ pub fn list(c: &Context, domain_prefix: &str) -> Result<StoreIter> {
+ let descriptor = descriptor(c);
+ let mut core = Core::new()?;
+ let handle = core.handle();
+
+ let mut rpc_system
+ = match descriptor.connect(&handle) {
+ Ok(r) => r,
+ Err(e) => return Err(e.into()),
+ };
+
+ let node: node::Client = rpc_system.bootstrap(Side::Server);
+ handle.spawn(rpc_system.map_err(|_e| ()));
+
+ let mut request = node.iter_request();
+ request.get().set_domain_prefix(domain_prefix);
+ let iter = make_request!(&mut core, request)?;
+ Ok(StoreIter{core: Rc::new(RefCell::new(core)), iter: iter})
+ }
+
+ /// Lists all keys in the common key pool.
+ pub fn list_keys(c: &Context) -> Result<KeyIter> {
+ let descriptor = descriptor(c);
+ let mut core = Core::new()?;
+ let handle = core.handle();
+
+ let mut rpc_system
+ = match descriptor.connect(&handle) {
+ Ok(r) => r,
+ Err(e) => return Err(e.into()),
+ };
+
+ let node: node::Client = rpc_system.bootstrap(Side::Server);
+ handle.spawn(rpc_system.map_err(|_e| ()));
+
+ let request = node.iter_keys_request();
+ let iter = make_request!(&mut core, request)?;
+ Ok(KeyIter{core: Rc::new(RefCell::new(core)), iter: iter})
}
/// Adds a key identified by fingerprint to the store.
@@ -269,6 +314,13 @@ impl Store {
let request = self.store.delete_request();
make_request_map!(self.core.borrow_mut(), request, |_| Ok(()))
}
+
+ /// Lists all bindings.
+ pub fn iter(&self) -> Result<BindingIter> {
+ let request = self.store.iter_request();
+ let iter = make_request!(self.core.borrow_mut(), request)?;
+ Ok(BindingIter{core: self.core.clone(), iter: iter})
+ }
}
/// Represents an entry in a Store.
@@ -637,6 +689,115 @@ impl Stamps {
}
}
+/* Iterators. */
+
+/// Iterates over stores.
+pub struct StoreIter {
+ core: Rc<RefCell<Core>>,
+ iter: node::store_iter::Client,
+}
+
+/// Items returned by `StoreIter`.
+#[derive(Debug)]
+pub struct StoreIterItem {
+ pub domain: String,
+ pub name: String,
+ pub network_policy: core::NetworkPolicy,
+ pub entries: usize,
+ pub store: Store,
+}
+
+impl Iterator for StoreIter {
+ type Item = StoreIterItem;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let request = self.iter.next_request();
+ let doit = || {
+ make_request_map!(
+ self.core.borrow_mut(), request,
+ |r: node::store_iter::item::Reader|
+ Ok(StoreIterItem{
+ domain: r.get_domain()?.into(),
+ name: r.get_name()?.into(),
+ network_policy: r.get_network_policy()?.into(),
+ entries: r.get_entries() as usize,
+ store: Store::new(self.core.clone(), r.get_name()?, r.get_store()?),
+ }))
+ };
+ doit().ok()
+ }
+}
+
+/// Iterates over bindings in a store.
+pub struct BindingIter {
+ core: Rc<RefCell<Core>>,
+ iter: node::binding_iter::Client,
+}
+
+/// Items returned by `BindingIter`.
+#[derive(Debug)]
+pub struct BindingIterItem {
+ pub label: String,
+ pub fingerprint: openpgp::Fingerprint,
+ pub binding: Binding,
+}
+
+impl Iterator for BindingIter {
+ type Item = BindingIterItem;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let request = self.iter.next_request();
+ let doit = || {
+ make_request_map!(
+ self.core.borrow_mut(), request,
+ |r: node::binding_iter::item::Reader| {
+ let label = String::from(r.get_label()?);
+ let binding = Binding::new(self.core.clone(), &label, r.get_binding()?);
+ Ok(BindingIterItem{
+ label: label,
+ fingerprint: openpgp::Fingerprint::from_hex(r.get_fingerprint()?).unwrap(),
+ binding: binding,
+ })
+ })
+ };
+ doit().ok()
+ }
+}
+
+/// Iterates over keys in the common key pool.
+pub struct KeyIter {
+ core: Rc<RefCell<Core>>,
+ iter: node::key_iter::Client,
+}
+
+/// Items returned by `KeyIter`.
+#[derive(Debug)]
+pub struct KeyIterItem {
+ pub fingerprint: openpgp::Fingerprint,
+ pub bindings: usize,
+ pub key: Key,
+}
+
+impl Iterator for KeyIter {
+ type Item = KeyIterItem;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let request = self.iter.next_request();
+ let doit = || {
+ make_request_map!(
+ self.core.borrow_mut(), request,
+ |r: node::key_iter::item::Reader| {
+ Ok(KeyIterItem{
+ fingerprint: openpgp::Fingerprint::from_hex(r.get_fingerprint()?).unwrap(),
+ bindings: r.get_bindings() as usize,
+ key: Key::new(self.core.clone(), r.get_key()?),
+ })
+ })
+ };
+ doit().ok()
+ }
+}
+
/* Error handling. */
/// Results for sequoia-store.
@@ -825,5 +986,80 @@ mod store_test {
assert_match!(Err(Error::NotFound) = b1.stats());
assert_match!(Err(Error::NotFound) = b1.key());
}
+
+ fn make_some_stores() -> core::Context {
+ let ctx0 = core::Context::configure("org.sequoia-pgp.tests.foo")
+ .ephemeral()
+ .network_policy(core::NetworkPolicy::Offline)
+ .build().unwrap();
+ let store = Store::open(&ctx0, "default").unwrap();
+ let fp = Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb");
+ store.add("Mister B.", &fp).unwrap();
+ store.add("B4", &fp).unwrap();
+
+ Store::open(&ctx0, "another store").unwrap();
+
+ let ctx1 = core::Context::configure("org.sequoia-pgp.tests.bar")
+ .home(ctx0.home())
+ .network_policy(core::NetworkPolicy::Offline)
+ .build().unwrap();
+ let store = Store::open(&ctx1, "default").unwrap();
+ let fp = Fingerprint::from_bytes(b"cccccccccccccccccccc");
+ store.add("Mister C.", &fp).unwrap();
+
+ ctx0
+ }
+
+ #[test]
+ fn store_iterator() {
+ let ctx = make_some_stores();
+ let mut iter = Store::list(&ctx, "org.sequoia-pgp.tests.f").unwrap();
+ let item = iter.next().unwrap();
+ assert_eq!(item.domain, "org.sequoia-pgp.tests.foo");
+ assert_eq!(item.name, "default");
+ assert_eq!(item.network_policy, core::NetworkPolicy::Offline);
+ assert_eq!(item.entries, 2);
+ let fp = Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb");
+ item.store.add("Mister B.", &fp).unwrap();
+ let item = iter.next().unwrap();
+ assert_eq!(item.domain, "org.sequoia-pgp.tests.foo");
+ assert_eq!(item.name, "another store");
+ assert_eq!(item.network_policy, core::NetworkPolicy::Offline);
+ assert_eq!(item.entries, 0);
+ item.store.add("Mister B.", &fp).unwrap();
+ assert!(iter.next().is_none());
+ }
+
+ #[test]
+ fn binding_iterator() {
+ let ctx = make_some_stores();
+ let store = Store::open(&ctx, "default").unwrap();
+ let mut iter = store.iter().unwrap();
+ let item = iter.next().unwrap();
+ let fp = Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb");
+ assert_eq!(item.label, "Mister B.");
+ assert_eq!(item.fingerprint, fp);
+ item.binding.stats().unwrap();
+ let item = iter.next().unwrap();
+ assert_eq!(item.label, "B4");
+ assert_eq!(item.fingerprint, fp);
+ item.binding.stats().unwrap();
+ assert!(iter.next().is_none());
+ }
+
+ #[test]
+ fn key_iterator() {
+ let ctx = make_some_stores();
+ let mut iter = Store::list_keys(&ctx).unwrap();
+ let item = iter.next().unwrap();
+ assert_eq!(item.fingerprint, Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb"));
+ assert_eq!(item.bindings, 2);
+ item.key.stats().unwrap();
+ let item = iter.next().unwrap();
+ assert_eq!(item.fingerprint, Fingerprint::from_bytes(b"cccccccccccccccccccc"));
+ assert_eq!(item.bindings, 1);
+ item.key.stats().unwrap();
+ assert!(iter.next().is_none());
+ }
}
diff --git a/store/src/store_protocol.capnp b/store/src/store_protocol.capnp
index f29565da..6f11da0f 100644
--- a/store/src/store_protocol.capnp
+++ b/store/src/store_protocol.capnp
@@ -3,12 +3,14 @@
interface Node {
open @0 (domain: Text, networkPolicy: NetworkPolicy, ephemeral: Bool, name: Text)
-> (result: Result(Store));
+ iter @1 (domainPrefix: Text) -> (result: Result(StoreIter));
+ iterKeys @2 () -> (result: Result(KeyIter));
interface Store {
add @0 (label: Text, fingerprint: Text) -> (result: Result(Binding));
lookup @1 (label: Text) -> (result: Result(Binding));
delete @2 () -> (result: Result(Unit));
- #iterate @3 (id: UInt32) -> (result: Result(Binding));
+ iter @3 () -> (result: Result(BindingIter));
}
interface Binding {
@@ -26,6 +28,39 @@ interface Node {
import @2 (key: Data) -> (result: Result(Data));
}
+ # Iterators.
+ interface StoreIter {
+ next @0 () -> (result: Result(Item));
+
+ struct Item {
+ domain @0 :Text;
+ name @1 :Text;
+ networkPolicy @2 :NetworkPolicy;
+ entries @3 :UInt64;
+ store @4 :Store;
+ }
+ }
+
+ interface BindingIter {
+ next @0 () -> (result: Result(Item));
+
+ struct Item {
+ label @0 :Text;
+ fingerprint @1 :Text;
+ binding @2 :Binding;
+ }
+ }
+
+ interface KeyIter {
+ next @0 () -> (result: Result(Item));
+
+ struct Item {
+ fingerprint @0 :Text;
+ bindings @1 :UInt64;
+ key @2 :Key;
+ }
+ }
+
# Unit struct. Useful with Result.
struct Unit {}
diff --git a/tool/Cargo.toml b/tool/Cargo.toml
index 3becea63..f0118c1a 100644
--- a/tool/Cargo.toml
+++ b/tool/Cargo.toml
@@ -9,6 +9,7 @@ sequoia-core = { path = "../core" }
sequoia-net = { path = "../net" }
sequoia-store = { path = "../store" }
clap = "2.27.1"
+prettytable-rs = "0.6.7"
[[bin]]
name = "sq"
diff --git a/tool/src/main.rs b/tool/src/main.rs
index dd791252..0df05a36 100644
--- a/tool/src/main.rs
+++ b/tool/src/main.rs
@@ -1,8 +1,13 @@
/// A command-line frontend for Sequoia.
extern crate clap;
+#[macro_use]
+extern crate prettytable;
use clap::{Arg, App, SubCommand, AppSettings};
+use prettytable::Table;
+use prettytable::cell::Cell;
+use prettytable::row::Row;
use std::fs::File;
use std::io;
use std::process::exit;
@@ -113,6 +118,8 @@ fn real_main() -> Result<()> {
.arg(Arg::with_name("name").value_name("NAME")
.required(true)
.help("Name of the store"))
+ .subcommand(SubCommand::with_name("list")
+ .about("Lists keys in the store"))
.subcommand(SubCommand::with_name("add")
.about("Add a key identified by fingerprint")
.arg(Arg::with_name("label").value_name("LABEL")
@@ -160,6 +167,18 @@ fn real_main() -> Result<()> {
.arg(Arg::with_name("label").value_name("LABEL")
.required(true)
.help("Label to use"))))
+ .subcommand(SubCommand::with_name("list")
+ .about("Lists key stores and known keys")
+ .subcommand(SubCommand::with_name("stores")
+ .about("Lists key stores")
+ .arg(Arg::with_name("prefix").value_name("PREFIX")
+ .help("List only stores with the given domain prefix")))
+ .subcommand(SubCommand::with_name("bindings")
+ .about("Lists all bindings in all key stores")
+ .arg(Arg::with_name("prefix").value_name("PREFIX")
+ .help("List only bindings from stores with the given domain prefix")))
+ .subcommand(SubCommand::with_name("keys")
+ .about("Lists all keys in the common key pool")))
.get_matches();
let policy = match matches.value_of("policy") {
@@ -271,6 +290,9 @@ fn real_main() -> Result<()> {
.expect("Failed to open store");
match m.subcommand() {
+ ("list", Some(_)) => {
+ list_bindings(&store);
+ },
("add", Some(m)) => {
let fp = Fingerprint::from_hex(m.value_of("fingerprint").unwrap())
.expect("Malformed fingerprint");
@@ -333,6 +355,53 @@ fn real_main() -> Result<()> {
},
}
},
+ ("list", Some(m)) => {
+ match m.subcommand() {
+ ("stores", Some(m)) => {
+ let mut table = Table::new();
+ table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
+ table.set_titles(row!["domain", "name", "network policy", "# of entries"]);
+
+ for item in Store::list(&ctx, m.value_of("prefix").unwrap_or(""))
+ .expect("Failed to iterate over stores") {
+ table.add_row(Row::new(vec![
+ Cell::new(&item.domain),
+ Cell::new(&item.name),
+ Cell::new(&format!("{:?}", item.network_policy)),
+ Cell::new(&format!("{}", item.entries))])
+ );
+ }
+
+ table.printstd();
+ },
+ ("bindings", Some(m)) => {
+ for item in Store::list(&ctx, m.value_of("prefix").unwrap_or(""))
+ .expect("Failed to iterate over stores") {
+ println!("Domain {:?} Name {:?}:", item.domain, item.name);
+ list_bindings(&item.store);
+ }
+ },
+ ("keys", Some(_)) => {
+ let mut table = Table::new();
+ table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
+ table.set_titles(row!["fingerprint", "# of bindings"]);
+
+ for item in Store::list_keys(&ctx)
+ .expect("Failed to iterate over keys") {
+ table.add_row(Row::new(vec![
+ Cell::new(&item.fingerprint.to_string()),
+ Cell::new(&format!("{}", item.bindings))])
+ );
+ }
+
+ table.printstd();
+ },
+ _ => {
+ eprintln!("No list subcommand given.");
+ exit(1);
+ },
+ }
+ },
_ => {
eprintln!("No subcommand given.");
exit(1);
@@ -342,4 +411,17 @@ fn real_main() -> Result<()> {
return Ok(())
}
+fn list_bindings(store: &Store) {
+ let mut table = Table::new();
+ table.set_format(*prettytable::format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
+ table.set_titles(row!["label", "fingerprint"]);
+ for item in store.iter().expect("Failed to iterate over bindings") {
+ table.add_row(Row::new(vec![
+ Cell::new(&item.label),
+ Cell::new(&item.fingerprint.to_string())]));
+ }
+ table.printstd();
+}
+
+
fn main() { real_main().expect("An error occured"); }
diff --git a/tool/src/usage.rs b/tool/src/usage.rs
index 07cdc099..213ac00b 100644
--- a/tool/src/usage.rs
+++ b/tool/src/usage.rs
@@ -22,6 +22,7 @@
//! enarmor Applies ASCII Armor to a file
//! help Prints this message or the help of the given subcommand(s)
//! keyserver Interacts with keyservers
+//! list Lists key stores and known keys
//! store Interacts with key stores
//! ```
//!
@@ -135,6 +136,70 @@
//! -i, --input <FILE> Sets the input file to use
//! ```
//!
+//! ## Subcommand list
+//!
+//! ```text
+//! Lists key stores and known keys
+//!
+//! USAGE:
+//! sq list [SUBCOMMAND]
+//!
+//! FLAGS:
+//! -h, --help Prints help information
+//! -V, --version Prints version information
+//!
+//! SUBCOMMANDS:
+//! bindings Lists all bindings in all key stores
+//! help Prints this message or the help of the given subcommand(s)
+//! keys Lists all keys in the common key pool
+//! stores Lists key stores
+//! ```
+//!
+//! ### Subcommand list bindings
+//!
+//! ```text
+//! Lists all bindings in all key stores
+//!
+//! USAGE:
+//! sq list bindings [PREFIX]
+//!
+//! FLAGS:
+//! -h, --help Prints help information
+//! -V, --version Prints version information
+//!
+//! ARGS:
+//! <PREFIX> List only bindings from stores with the given domain prefix
+//! ```
+//!
+//! ### Subcommand list keys
+//!
+//! ```text
+//! Lists all keys in the common key pool
+//!
+//! USAGE:
+//! sq list keys
+//!
+//! FLAGS:
+//! -h, --help Prints help information
+//! -V, --version Prints version information
+//! ```
+//!
+//! ### Subcommand list stores
+//!
+//! ```text
+//! Lists key stores
+//!
+//! USAGE:
+//! sq list stores [PREFIX]
+//!
+//! FLAGS:
+//! -h, --help Prints help information
+//! -V, --version Prints version information
+//!
+//! ARGS:
+//! <PREFIX> List only stores with the given domain prefix
+//! ```
+//!
//! ## Subcommand store
//!
//! ```text
@@ -152,9 +217,11 @@
//!
//! SUBCOMMANDS:
//! add Add a key identified by fingerprint
+//! delete Deletes bindings or stores
//! export Exports a key
//! help Prints this message or the help of the given subcommand(s)
//! import Imports a key
+//! list Lists keys in the store
//! stats Get stats for the given label
//! ```
//!
@@ -175,6 +242,23 @@
//! <FINGERPRINT> Key to add
//! ```
//!
+//! ### Subcommand store delete
+//!
+//! ```text
+//! Deletes bindings or stores
+//!
+//! USAGE:
+//! sq store <NAME> delete [FLAGS] [LABEL]
+//!
+//! FLAGS:
+//! -h, --help Prints help information
+//! --the-store Delete the whole store
+//! -V, --version Prints version information
+//!
+//! ARGS:
+//! <LABEL> Delete binding with this label
+//! ```
+//!
//! ### Subcommand store export
//!
//! ```text
@@ -215,6 +299,19 @@
//! <LABEL> Label to use
//! ```
//!
+//! ### Subcommand store list
+//!
+//! ```text
+//! Lists keys in the store
+//!
+//! USAGE:
+//! sq store <NAME> list
+//!
+//! FLAGS:
+//! -h, --help Prints help information
+//! -V, --version Prints version information
+//! ```
+//!
//! ### Subcommand store stats
//!
//! ```text