summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--openpgp/tests/data/keys/testy-new-private.pgpbin0 -> 504 bytes
-rw-r--r--openpgp/tests/data/keys/testy-new-with-sig.pgpbin0 -> 740 bytes
-rw-r--r--openpgp/tests/data/keys/testy-new.pgpbin0 -> 430 bytes
-rw-r--r--openpgp/tests/data/keys/testy-private.pgpbin0 -> 2540 bytes
-rw-r--r--store/Cargo.toml26
-rw-r--r--store/build.rs10
-rw-r--r--store/src/backend.rs534
-rw-r--r--store/src/lib.rs648
-rw-r--r--store/src/macros.rs116
-rw-r--r--store/src/server.rs28
-rw-r--r--store/src/store_protocol.capnp53
-rw-r--r--store/src/store_protocol_capnp.rs2
12 files changed, 1416 insertions, 1 deletions
diff --git a/openpgp/tests/data/keys/testy-new-private.pgp b/openpgp/tests/data/keys/testy-new-private.pgp
new file mode 100644
index 00000000..1faf30c6
--- /dev/null
+++ b/openpgp/tests/data/keys/testy-new-private.pgp
Binary files differ
diff --git a/openpgp/tests/data/keys/testy-new-with-sig.pgp b/openpgp/tests/data/keys/testy-new-with-sig.pgp
new file mode 100644
index 00000000..0f6f054e
--- /dev/null
+++ b/openpgp/tests/data/keys/testy-new-with-sig.pgp
Binary files differ
diff --git a/openpgp/tests/data/keys/testy-new.pgp b/openpgp/tests/data/keys/testy-new.pgp
new file mode 100644
index 00000000..2bfdcfd2
--- /dev/null
+++ b/openpgp/tests/data/keys/testy-new.pgp
Binary files differ
diff --git a/openpgp/tests/data/keys/testy-private.pgp b/openpgp/tests/data/keys/testy-private.pgp
new file mode 100644
index 00000000..ceadd8e7
--- /dev/null
+++ b/openpgp/tests/data/keys/testy-private.pgp
Binary files differ
diff --git a/store/Cargo.toml b/store/Cargo.toml
index 3dccafc3..130e2b00 100644
--- a/store/Cargo.toml
+++ b/store/Cargo.toml
@@ -2,5 +2,31 @@
name = "sequoia-store"
version = "0.1.0"
authors = ["Justus Winter <justus@pep-project.org>"]
+build = "build.rs"
+
+[features]
+default = ["background-services"]
+background-services = []
[dependencies]
+openpgp = { path = "../openpgp" }
+sequoia-core = { path = "../core" }
+sequoia-net = { path = "../net" }
+capnp = "0.8"
+capnp-rpc = "0.8"
+futures = "0.1.17"
+rusqlite = "0.12.0"
+tokio-core = "0.1.10"
+tokio-io = "0.1.4"
+
+[build-dependencies]
+capnpc = "0.8"
+
+[lib]
+name = "sequoia_store"
+path = "src/lib.rs"
+
+[[bin]]
+name = "keystore"
+path = "src/server.rs"
+required-features = ["background-services"]
diff --git a/store/build.rs b/store/build.rs
new file mode 100644
index 00000000..d7bd89d3
--- /dev/null
+++ b/store/build.rs
@@ -0,0 +1,10 @@
+extern crate capnpc;
+
+fn capnp(src: &str) {
+ println!("rerun-if-changed={}", src);
+ ::capnpc::CompilerCommand::new().file(src).run().unwrap();
+}
+
+fn main() {
+ capnp("src/store_protocol.capnp");
+}
diff --git a/store/src/backend.rs b/store/src/backend.rs
new file mode 100644
index 00000000..7b2614f8
--- /dev/null
+++ b/store/src/backend.rs
@@ -0,0 +1,534 @@
+//! Storage backend.
+
+use std::cell::RefCell;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use capnp::capability::Promise;
+use capnp;
+use capnp_rpc::rpc_twoparty_capnp::Side;
+use capnp_rpc::{self, RpcSystem, twoparty};
+use rusqlite::Connection;
+use rusqlite;
+use tokio_core;
+use tokio_io::io::ReadHalf;
+
+use openpgp::tpk::{self, TPK};
+use sequoia_net::ipc;
+
+use store_protocol_capnp::node;
+use super::Result;
+
+/// Makes backends.
+#[doc(hidden)]
+pub fn factory(descriptor: ipc::Descriptor) -> Option<Box<ipc::Handler>> {
+ match Backend::new(descriptor) {
+ Ok(backend) => Some(Box::new(backend)),
+ Err(_) => None,
+ }
+}
+
+struct Backend {
+ store: node::Client,
+}
+
+impl Backend {
+ fn new(descriptor: ipc::Descriptor) -> Result<Self> {
+ Ok(Backend {
+ store: node::ToClient::new(NodeServer::new(descriptor)?)
+ .from_server::<capnp_rpc::Server>(),
+ })
+ }
+}
+
+impl ipc::Handler for Backend {
+ fn handle(&self,
+ network: twoparty::VatNetwork<ReadHalf<tokio_core::net::TcpStream>>)
+ -> RpcSystem<Side> {
+ RpcSystem::new(Box::new(network), Some(self.store.clone().client))
+ }
+}
+
+
+struct NodeServer {
+ descriptor: ipc::Descriptor,
+ c: Rc<RefCell<Connection>>,
+}
+
+impl NodeServer {
+ fn new(descriptor: ipc::Descriptor) -> Result<Self> {
+ let mut db_path = descriptor.home.clone();
+ db_path.push("keystore.sqlite");
+
+ let c = Connection::open(db_path)?;
+ c.execute_batch("PRAGMA secure_delete = true;")?;
+ c.execute_batch("PRAGMA foreign_keys = true;")?;
+
+ Ok(NodeServer {
+ descriptor: descriptor,
+ c: Rc::new(RefCell::new(c)),
+ })
+ }
+}
+
+impl node::Server for NodeServer {
+ fn new(&mut self,
+ params: node::NewParams,
+ mut results: node::NewResults)
+ -> Promise<(), capnp::Error> {
+ let params = pry!(params.get());
+ let home = pry!(params.get_home());
+ if PathBuf::from(home) != self.descriptor.home {
+ pry!(results.get().get_result()).set_err(node::Error::Unspecified);
+ return Promise::ok(());
+ }
+
+ // XXX maybe check ephemeral and use in-core sqlite db
+
+ let store = StoreServer::new(self.c.clone(),
+ pry!(params.get_domain()),
+ pry!(params.get_name()));
+ match store {
+ Ok(store) => {
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::store::ToClient::new(store).from_server::<capnp_rpc::Server>()));
+ },
+ Err(_e) => {
+ pry!(results.get().get_result()).set_err(node::Error::Unspecified);
+ }
+ };
+ Promise::ok(())
+ }
+}
+
+struct StoreServer {
+ c: Rc<RefCell<Connection>>,
+ store_id: i64,
+}
+
+impl StoreServer {
+ fn new(c: Rc<RefCell<Connection>>, domain: &str, name: &str) -> Result<Self> {
+ let mut server = StoreServer {
+ c: c,
+ store_id: 0,
+ }.init()?;
+
+ {
+ let c = server.c.borrow();
+ c.execute(
+ "INSERT OR IGNORE INTO stores (domain, name) VALUES (?1, ?2)",
+ &[&domain, &name])?;
+ server.store_id = c.query_row(
+ "SELECT id FROM stores WHERE domain = ?1 AND name = ?2",
+ &[&domain, &name], |row| row.get(0))?;
+ }
+
+ Ok(server)
+ }
+
+ fn init(self) -> Result<Self> {
+ let v = self.c.borrow().query_row(
+ "SELECT version FROM version WHERE id=1",
+ &[], |row| row.get(0));
+
+ if let Ok(v) = v {
+ match v {
+ 1 => return Ok(self),
+ _ => unimplemented!(),
+ }
+ }
+
+ self.c.borrow()
+ .execute_batch(DB_SCHEMA_1)?;
+ Ok(self)
+ }
+}
+
+impl From<rusqlite::Error> for node::Error {
+ fn from(_error: rusqlite::Error) -> Self {
+ node::Error::Unspecified
+ }
+}
+
+impl From<tpk::Error> for node::Error {
+ fn from(_: tpk::Error) -> Self {
+ node::Error::MalformedKey
+ }
+}
+
+impl node::store::Server for StoreServer {
+ fn add(&mut self,
+ params: node::store::AddParams,
+ mut results: node::store::AddResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let params = pry!(params.get());
+ let fp = pry!(params.get_fingerprint());
+ let label = pry!(params.get_label());
+ let time = sry!(now());
+ let c = self.c.borrow();
+
+ let key_id = sry!(get_key_id(&c, fp));
+
+ sry!(c.execute("INSERT OR IGNORE INTO bindings (store, label, key, created)
+ VALUES (?, ?, ?, ?)",
+ &[&self.store_id,
+ &label,
+ &key_id,
+ &time]));
+ let binding_id: i64 = sry!(c.query_row(
+ "SELECT id FROM bindings WHERE store = ?1 AND label = ?2",
+ &[&self.store_id, &label], |row| row.get(0)));
+
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::binding::ToClient::new(
+ BindingServer::new(self.c.clone(), binding_id))
+ .from_server::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+
+ fn lookup(&mut self,
+ params: node::store::LookupParams,
+ mut results: node::store::LookupResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let label = pry!(pry!(params.get()).get_label());
+ let c = self.c.borrow();
+
+ let binding_id: i64 = sry!(c.query_row(
+ "SELECT id FROM bindings WHERE store = ?1 AND label = ?2",
+ &[&self.store_id, &label], |row| row.get(0)));
+
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::binding::ToClient::new(
+ BindingServer::new(self.c.clone(), binding_id))
+ .from_server::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+}
+
+struct BindingServer {
+ c: Rc<RefCell<Connection>>,
+ id: i64,
+}
+
+impl BindingServer {
+ fn new(c: Rc<RefCell<Connection>>, id: i64) -> Self {
+ BindingServer {
+ c: c,
+ id: id,
+ }
+ }
+
+ fn key_id(&mut self) -> Result<i64> {
+ self.query("key")
+ }
+}
+
+trait Query {
+ fn query(&mut self, column: &str) -> Result<i64>;
+}
+
+impl Query for BindingServer {
+ fn query(&mut self, column: &str) -> Result<i64> {
+ self.c.borrow().query_row(
+ format!("SELECT {} FROM bindings WHERE id = ?1", column).as_ref(),
+ &[&self.id], |row| row.get(0)).map_err(|e| e.into())
+ }
+}
+
+impl node::binding::Server for BindingServer {
+ fn stats(&mut self,
+ _: node::binding::StatsParams,
+ mut results: node::binding::StatsResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ sry!(compute_stats(self, pry!(results.get().get_result()).init_ok()));
+ Promise::ok(())
+ }
+
+ fn key(&mut self,
+ _: node::binding::KeyParams,
+ mut results: node::binding::KeyResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let key = sry!(self.key_id());
+
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::key::ToClient::new(
+ KeyServer::new(self.c.clone(), key)).from_server::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+
+ fn import(&mut self,
+ params: node::binding::ImportParams,
+ mut results: node::binding::ImportResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let force = pry!(params.get()).get_force();
+
+ let mut new = sry!(TPK::from_bytes(&pry!(pry!(params.get()).get_key())));
+
+ let key_id = sry!(self.key_id());
+ let (fingerprint, key): (String, Option<Vec<u8>>)
+ = sry!(self.c.borrow().query_row(
+ "SELECT fingerprint, key FROM keys WHERE id = ?1",
+ &[&key_id],
+ |row| (row.get(0), row.get_checked(1).ok())));
+ if let Some(current) = key {
+ let current = sry!(TPK::from_bytes(&current));
+
+ if current.fingerprint().to_hex() != fingerprint {
+ // Inconsistent database.
+ fail!(node::Error::SystemError);
+ }
+
+ if current.fingerprint() != new.fingerprint() {
+ if force || new.is_signed_by(&current) {
+ // Update binding, and retry.
+ let key_id =
+ sry!(get_key_id(&self.c.borrow(), new.fingerprint().to_hex().as_ref()));
+ sry!(self.c.borrow()
+ .execute("UPDATE bindings SET key = ?1 WHERE id = ?2",
+ &[&key_id, &self.id]));
+ return self.import(params, results);
+ } else {
+ fail!(node::Error::Conflict);
+ }
+ } else {
+ new = sry!(current.merge(new));
+ }
+ }
+
+ // Write key back to the database.
+ let mut blob = vec![];
+ sry!(new.serialize(&mut blob));
+
+ sry!(self.c.borrow()
+ .execute("UPDATE keys SET key = ?1 WHERE id = ?2",
+ &[&blob, &key_id]));
+
+ pry!(pry!(results.get().get_result()).set_ok(&blob[..]));
+ Promise::ok(())
+ }
+
+ fn register_encryption(&mut self,
+ _: node::binding::RegisterEncryptionParams,
+ mut results: node::binding::RegisterEncryptionResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let key = sry!(self.key_id());
+ sry!(self.c.borrow()
+ .execute("UPDATE bindings SET encryption_count = encryption_count + 1 WHERE id = ?1",
+ &[&self.id]));
+ sry!(self.c.borrow()
+ .execute("UPDATE keys SET encryption_count = encryption_count + 1 WHERE id = ?1",
+ &[&key]));
+
+ sry!(compute_stats(self, pry!(results.get().get_result()).init_ok()));
+ Promise::ok(())
+ }
+
+ fn register_verification(&mut self,
+ _: node::binding::RegisterVerificationParams,
+ mut results: node::binding::RegisterVerificationResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let key = sry!(self.key_id());
+ sry!(self.c.borrow()
+ .execute("UPDATE bindings SET verification_count = verification_count + 1 WHERE id = ?1",
+ &[&self.id]));
+ sry!(self.c.borrow()
+ .execute("UPDATE keys SET verification_count = verification_count + 1 WHERE id = ?1",
+ &[&key]));
+
+ sry!(compute_stats(self, pry!(results.get().get_result()).init_ok()));
+ Promise::ok(())
+ }
+}
+
+struct KeyServer {
+ c: Rc<RefCell<Connection>>,
+ id: i64,
+}
+
+impl KeyServer {
+ fn new(c: Rc<RefCell<Connection>>, id: i64) -> Self {
+ KeyServer {
+ c: c,
+ id: id,
+ }
+ }
+}
+
+impl Query for KeyServer {
+ fn query(&mut self, column: &str) -> Result<i64> {
+ self.c.borrow().query_row(
+ format!("SELECT {} FROM keys WHERE id = ?1", column).as_ref(),
+ &[&self.id], |row| row.get(0)).map_err(|e| e.into())
+ }
+}
+
+impl node::key::Server for KeyServer {
+ fn stats(&mut self,
+ _: node::key::StatsParams,
+ mut results: node::key::StatsResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ sry!(compute_stats(self, pry!(results.get().get_result()).init_ok()));
+ Promise::ok(())
+ }
+
+ fn tpk(&mut self,
+ _: node::key::TpkParams,
+ mut results: node::key::TpkResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let key: Vec<u8> = sry!(
+ self.c.borrow().query_row(
+ "SELECT key FROM keys WHERE id = ?1",
+ &[&self.id],
+ |row| row.get_checked(0).unwrap_or(vec![])));
+ pry!(pry!(results.get().get_result()).set_ok(key.as_slice()));
+ Promise::ok(())
+ }
+
+ fn import(&mut self,
+ params: node::key::ImportParams,
+ mut results: node::key::ImportResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let mut new = sry!(TPK::from_bytes(&pry!(pry!(params.get()).get_key())));
+
+ let (fingerprint, key): (String, Option<Vec<u8>>)
+ = sry!(self.c.borrow().query_row(
+ "SELECT fingerprint, key FROM keys WHERE id = ?1",
+ &[&self.id],
+ |row| (row.get(0), row.get_checked(1).ok())));
+ if let Some(current) = key {
+ let current = sry!(TPK::from_bytes(&current));
+
+ if current.fingerprint().to_hex() != fingerprint {
+ // Inconsistent database.
+ fail!(node::Error::SystemError);
+ }
+
+ if current.fingerprint() != new.fingerprint() {
+ fail!(node::Error::Conflict);
+ }
+
+ new = sry!(current.merge(new));
+ }
+
+ // Write key back to the database.
+ let mut blob = vec![];
+ sry!(new.serialize(&mut blob));
+
+ sry!(self.c.borrow()
+ .execute("UPDATE keys SET key = ?1 WHERE id = ?2",
+ &[&blob, &self.id]));
+
+ pry!(pry!(results.get().get_result()).set_ok(&blob[..]));
+ Promise::ok(())
+ }
+}
+
+/* Database schemata and migrations. */
+
+/* Version 1. */
+const DB_SCHEMA_1: &'static str = "
+CREATE TABLE version (
+ id INTEGER PRIMARY KEY,
+ version INTEGER);
+
+INSERT INTO version (id, version) VALUES (1, 1);
+
+CREATE TABLE stores (
+ id INTEGER PRIMARY KEY,
+ domain TEXT,
+ name TEXT,
+ UNIQUE (domain, name));
+
+CREATE TABLE bindings (
+ id INTEGER PRIMARY KEY,
+ store INTEGER NOT NULL,
+ label TEXT NOT NULL,
+ key INTEGER NOT NULL,
+
+ created INTEGER NOT NULL,
+ updated DEFAULT 0,
+
+ encryption_count DEFAULT 0,
+ encryption_first DEFAULT 0,
+ encryption_last DEFAULT 0,
+ verification_count DEFAULT 0,
+ verification_first DEFAULT 0,
+ verification_last DEFAULT 0,
+
+ UNIQUE(store, label),
+ FOREIGN KEY (store) REFERENCES stores(id),
+ FOREIGN KEY (key) REFERENCES keys(id));
+
+CREATE TABLE keys (
+ id INTEGER PRIMARY KEY,
+ fingerprint TEXT NOT NULL,
+ key BLOB,
+
+ created INTEGER NOT NULL,
+ updated DEFAULT 0,
+
+ encryption_count DEFAULT 0,
+ encryption_first DEFAULT 0,
+ encryption_last DEFAULT 0,
+ verification_count DEFAULT 0,
+ verification_first DEFAULT 0,
+ verification_last DEFAULT 0,
+
+ UNIQUE (fingerprint));
+";
+
+/* Miscellaneous. */
+
+/// Given a fingerprint, return the key id.
+fn get_key_id(c: &Connection, fp: &str) -> Result<i64> {
+ if let Ok(x) = c.query_row(
+ "SELECT id FROM keys WHERE fingerprint = ?1",
+ &[&fp], |row| row.get(0)) {
+ Ok(x)
+ } else {
+ c.execute(
+ "INSERT INTO keys (fingerprint, created) VALUES (?1, ?2)",
+ &[&fp, &now()?])?;
+ c.query_row(
+ "SELECT id FROM keys WHERE fingerprint = ?1",
+ &[&fp], |row| row.get(0)).map_err(|e| e.into())
+ }
+}
+
+fn now() -> Result<i64> {
+ match SystemTime::now().duration_since(UNIX_EPOCH) {
+ Ok(n) => Ok(n.as_secs() as i64),
+ Err(_) => Err(node::Error::SystemError.into()),
+ }
+}
+
+fn compute_stats(q: &mut Query, mut stats: node::stats::Builder) -> Result<()> {
+ let created = q.query("created")?;
+ let updated = q.query("updated")?;
+ let encryption_count = q.query("encryption_count")?;
+ let encryption_first = q.query("encryption_first")?;
+ let encryption_last = q.query("encryption_last")?;
+ let verification_count = q.query("verification_count")?;
+ let verification_first = q.query("verification_first")?;
+ let verification_last = q.query("verification_last")?;
+ stats.set_created(created);
+ stats.set_updated(updated);
+ stats.set_encryption_count(encryption_count);
+ stats.set_encryption_first(encryption_first);
+ stats.set_encryption_last(encryption_last);
+ stats.set_verification_count(verification_count);
+ stats.set_verification_first(verification_first);
+ stats.set_verification_last(verification_last);
+ Ok(())
+}
diff --git a/store/src/lib.rs b/store/src/lib.rs
index 45935c17..8cf02b68 100644
--- a/store/src/lib.rs
+++ b/store/src/lib.rs
@@ -1 +1,647 @@
-//! For storing keys.
+//! For storing transferable public keys.
+//!
+//! The key store stores transferable public keys (TPKs) using an
+//! arbitrary label. Stored keys are automatically updated from
+//! remote sources. This ensures that updates like new subkeys and
+//! revocations are discovered in a timely manner.
+//!
+//! # Security considerations
+//!
+//! XXX
+//!
+//! # Example
+//!
+//! ```
+//! # extern crate openpgp;
+//! # extern crate sequoia_core;
+//! # extern crate sequoia_store;
+//! # use openpgp::Fingerprint;
+//! # use sequoia_core::Context;
+//! # use sequoia_store::{Store, Result};
+//! # fn main() { f().unwrap(); }
+//! # fn f() -> Result<()> {
+//! # let ctx = Context::configure("org.sequoia-pgp.demo.store")
+//! # .ephemeral().build()?;
+//! let mut store = Store::open(&ctx, "default")?;
+//!
+//! let fp = Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb");
+//! let binding = store.add("Mister B.", &fp)?;
+//!
+//! println!("Binding {:?}", binding.stats()?);
+//! // prints:
+//! // Binding Stats {
+//! // created: Some(SystemTime { tv_sec: 1513704042, tv_nsec: 0 }),
+//! // updated: None,
+//! // encryption: Stamps { count: 0, first: None, latest: None },
+//! // verification: Stamps { count: 0, first: None, latest: None }
+//! // }
+//! # Ok(())
+//! # }
+//! ```
+
+extern crate capnp;
+#[macro_use]
+extern crate capnp_rpc;
+extern crate futures;
+extern crate rusqlite;
+extern crate tokio_core;
+extern crate tokio_io;
+
+use std::cell::RefCell;
+use std::io;
+use std::time::{SystemTime, Duration, UNIX_EPOCH};
+
+use capnp::capability::Promise;
+use capnp_rpc::rpc_twoparty_capnp::Side;
+use futures::{Future};
+use tokio_core::reactor::Core;
+
+extern crate openpgp;
+extern crate sequoia_core;
+extern crate sequoia_net;
+
+use openpgp::Fingerprint;
+use openpgp::tpk::{self, TPK};
+use sequoia_core::Context;
+use sequoia_net::ipc;
+
+#[allow(dead_code)] mod store_protocol_capnp;
+use store_protocol_capnp::node;
+
+/// Macros managing requests and responses.
+#[macro_use] mod macros;
+
+/// Storage backend.
+mod backend;
+
+/// Returns the service descriptor.
+#[doc(hidden)]
+pub fn descriptor(c: &Context) -> ipc::Descriptor {
+ ipc::Descriptor::new(
+ c.home().to_path_buf(),
+ c.home().join("S.keystore"),
+ c.lib().join("keystore"),
+ backend::factory,
+ )
+}
+
+/// A public key store.
+pub struct Store {
+ core: RefCell<Core>,
+ store: node::store::Client,
+}
+
+impl<'a> Store {
+ /// Opens a store.
+ ///
+ /// Opens a store with the given name. If the store does not
+ /// exist, it is created. Stores are handles for objects
+ /// maintained by a background service. The background service
+ /// associates state with this name.
+ pub fn open(c: &Context, name: &str) -> Result<Self> {
+ let descriptor = descriptor(c);
+ let mut core = tokio_core::reactor::Core::new()?;
+ let handle = core.handle();
+
+ let mut rpc_system
+ = match descriptor.connect(&handle) {
+ Ok(r) => r,
+ Err(e) => return Err(e.into()),
+ };
+
+ let store: node::Client = rpc_system.bootstrap(Side::Server);
+ handle.spawn(rpc_system.map_err(|_e| ()));
+
+ let mut request = store.new_request();
+ request.get().set_home(&c.home().to_string_lossy());
+ request.get().set_domain(c.domain());
+ request.get().set_ephemeral(c.ephemeral());
+ request.get().set_name(name);
+
+ let store = make_request!(&mut core, request)?;
+ Ok(Store{core: RefCell::new(core), store: store})
+ }
+
+ /// Adds a key identified by fingerprint to the store.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate openpgp;
+ /// # extern crate sequoia_core;
+ /// # extern crate sequoia_store;
+ /// # use openpgp::Fingerprint;
+ /// # use sequoia_core::Context;
+ /// # use sequoia_store::{Store, Result};
+ /// # fn main() { f().unwrap(); }
+ /// # fn f() -> Result<()> {
+ /// # let ctx = Context::configure("org.sequoia-pgp.demo.store")
+ /// # .ephemeral().build()?;
+ /// let mut store = Store::open(&ctx, "default")?;
+ /// let fp = Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb");
+ /// store.add("Mister B.", &fp)?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn add(&'a mut self, label: &str, fingerprint: &Fingerprint) -> Result<Binding> {
+ let mut request = self.store.add_request();
+ request.get().set_label(label);
+ request.get().set_fingerprint(fingerprint.to_hex().as_ref());
+ let binding = make_request!(self.core.borrow_mut(), request)?;
+ Ok(Binding::new(self, binding))
+ }
+
+ /// Imports a key into the store.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate openpgp;
+ /// # extern crate sequoia_core;
+ /// # extern crate sequoia_store;