summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2018-02-06 13:10:10 +0100
committerJustus Winter <justus@sequoia-pgp.org>2019-05-24 16:20:49 +0200
commitd52d2f4eaaf6e9806d18f65fb7ff4f8298abc1de (patch)
treeb023aa5384a8de1a7f0b5b0afbdfb2d488db3698
parente97befc021e3e66b554cdd698a37ca80d9d88e44 (diff)
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--Cargo.lock20
-rw-r--r--Cargo.toml1
-rw-r--r--secret/Cargo.toml52
-rw-r--r--secret/build.rs10
-rw-r--r--secret/src/backend/mod.rs535
-rw-r--r--secret/src/lib.rs317
-rw-r--r--secret/src/macros.rs98
-rw-r--r--secret/src/secret_protocol.capnp44
-rw-r--r--secret/src/secret_protocol_capnp.rs2
-rw-r--r--secret/src/server.rs14
11 files changed, 1094 insertions, 1 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5875acff..3c69138d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,7 +9,7 @@ test:
- cargo --version
- clang --version
- SEQUOIA_CTEST_VALGRIND=/usr/bin/valgrind make test
- - cargo clean -p buffered-reader -p sequoia-rfc2822 -p sequoia-openpgp -p sequoia-openpgp-ffi -p sequoia-core -p sequoia-ffi -p sequoia-ffi-macros -p sequoia-ipc -p sequoia-net -p sequoia-store -p sequoia-tool -p sequoia-sqv -p sequoia-guide
+ - cargo clean -p buffered-reader -p sequoia-rfc2822 -p sequoia-openpgp -p sequoia-openpgp-ffi -p sequoia-core -p sequoia-ffi -p sequoia-ffi-macros -p sequoia-ipc -p sequoia-net -p sequoia-store -p sequoia-tool -p sequoia-secret -p sequoia-sqv -p sequoia-guide
- du -sh target
- du -sh cargo
- tar cf cache.tar target cargo
diff --git a/Cargo.lock b/Cargo.lock
index 37a135fe..79fc4864 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1508,6 +1508,7 @@ dependencies = [
"sequoia-openpgp 0.7.0",
"sequoia-openpgp-ffi 0.7.0",
"sequoia-rfc2822 0.7.0",
+ "sequoia-secret 0.7.0",
"sequoia-sqv 0.7.0",
"sequoia-store 0.7.0",
"sequoia-store-rusqlite 0.7.0",
@@ -1648,6 +1649,25 @@ dependencies = [
]
[[package]]
+name = "sequoia-secret"
+version = "0.7.0"
+dependencies = [
+ "capnp 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "capnp-rpc 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "capnpc 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rusqlite 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sequoia-core 0.7.0",
+ "sequoia-ipc 0.7.0",
+ "sequoia-openpgp 0.7.0",
+ "sequoia-store-rusqlite 0.7.0",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "sequoia-sqv"
version = "0.7.0"
dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index e36292a8..c96a9a77 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,6 +34,7 @@ sequoia-net = { path = "net", version = "0.7" }
sequoia-store = { path = "store", version = "0.7" }
sequoia-store-rusqlite = { path = "store-rusqlite", version = "0.7" }
sequoia-tool = { path = "tool", version = "0.7" }
+sequoia-secret = { path = "secret", version = "0.7" }
sequoia-sqv = { path = "sqv", version = "0.7" }
sequoia-guide = { path = "guide", version = "0.7" }
diff --git a/secret/Cargo.toml b/secret/Cargo.toml
new file mode 100644
index 00000000..7655ca3f
--- /dev/null
+++ b/secret/Cargo.toml
@@ -0,0 +1,52 @@
+[package]
+name = "sequoia-secret"
+version = "0.7.0"
+build = "build.rs"
+authors = [
+ "Justus Winter <justus@sequoia-pgp.org>",
+ "Kai Michaelis <kai@sequoia-pgp.org>",
+ "Neal H. Walfield <neal@sequoia-pgp.org>",
+]
+documentation = "https://docs.sequoia-pgp.org/"
+homepage = "https://sequoia-pgp.org/"
+repository = "https://gitlab.com/sequoia-pgp/sequoia"
+readme = "README.md"
+keywords = ["cryptography", "openpgp", "pgp", "secrets", "keystore", "signing"]
+categories = ["cryptography", "authentication", "email"]
+license = "GPL-3.0"
+
+[badges]
+gitlab = { repository = "sequoia-pgp/sequoia" }
+maintenance = { status = "actively-developed" }
+
+[features]
+default = ["background-services"]
+background-services = []
+
+[dependencies]
+sequoia-openpgp = { path = "../openpgp", version = "0.7" }
+sequoia-core = { path = "../core", version = "0.7" }
+sequoia-ipc = { path = "../ipc", version = "0.7" }
+sequoia-store-rusqlite = { path = "../store-rusqlite", version = "0.7" }
+
+capnp = "0.9"
+capnp-rpc = "0.9"
+failure = "0.1.1"
+futures = "0.1.17"
+rusqlite = "0.14"
+time = "0.1.38"
+tokio-core = "0.1.10"
+tokio-io = "0.1.4"
+
+[build-dependencies]
+capnpc = "0.9"
+
+[lib]
+name = "sequoia_secret"
+path = "src/lib.rs"
+
+[[bin]]
+name = "sequoia-secret-key-store"
+path = "src/server.rs"
+doc = false
+required-features = ["background-services"]
diff --git a/secret/build.rs b/secret/build.rs
new file mode 100644
index 00000000..3d0bc8ac
--- /dev/null
+++ b/secret/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/secret_protocol.capnp");
+}
diff --git a/secret/src/backend/mod.rs b/secret/src/backend/mod.rs
new file mode 100644
index 00000000..1d865051
--- /dev/null
+++ b/secret/src/backend/mod.rs
@@ -0,0 +1,535 @@
+//! Secret key backend.
+
+use capnp::capability::Promise;
+use capnp;
+use capnp_rpc::rpc_twoparty_capnp::Side;
+use capnp_rpc::{self, RpcSystem, twoparty};
+use failure;
+use rusqlite::Connection;
+use rusqlite;
+use std::cell::{Ref, RefCell};
+use std::fmt;
+use std::rc::Rc;
+use tokio_core::reactor::Handle;
+use tokio_core;
+use tokio_io::io::ReadHalf;
+
+use openpgp::{
+ self,
+ Fingerprint,
+ KeyID,
+ TPK,
+ constants::{DataFormat, PublicKeyAlgorithm, SymmetricAlgorithm},
+ crypto::{mpis, Password, SessionKey},
+ packet::{Key, key::SecretKey, PKESK, SKESK},
+ parse::Parse,
+ serialize::{Serialize, SerializeInto},
+};
+use ipc;
+
+use store_rusqlite::{ID, Timestamp};
+
+use secret_protocol_capnp::store;
+
+use super::{Error, Result};
+
+/* Entry point. */
+
+/// Makes backends.
+#[doc(hidden)]
+pub fn factory(descriptor: ipc::Descriptor, handle: Handle)
+ -> Result<Box<ipc::Handler>> {
+ Backend::new(descriptor, handle)
+ .map(|b| -> Box<ipc::Handler> { Box::new(b) })
+}
+
+struct Backend {
+ secret: store::Client,
+}
+
+impl Backend {
+ fn new(descriptor: ipc::Descriptor, handle: Handle) -> Result<Self> {
+ Ok(Backend {
+ secret: store::ToClient::new(StoreServer::new(descriptor, handle)?)
+ .into_client::<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.secret.clone().client))
+ }
+}
+
+/* Server implementation. */
+
+struct DBKey {
+ tpk: openpgp::TPK,
+ secret: RefCell<Option<mpis::SecretKey>>,
+}
+
+impl DBKey {
+ fn new(tpk: openpgp::TPK) -> Result<DBKey> {
+ // The key has one encryption subkey with a secret key.
+ let secret = {
+ let key =
+ tpk.subkeys().nth(0).ok_or(failure::Error::from(Error::InternalInconsistency))?
+ .subkey();
+ let secret =
+ key.secret().ok_or(failure::Error::from(Error::InternalInconsistency))?;
+ match secret {
+ SecretKey::Unencrypted { ref mpis } => Some(mpis.clone()),
+ SecretKey::Encrypted { .. } => None,
+ }
+ };
+ Ok(DBKey {
+ tpk: tpk,
+ secret: RefCell::new(secret),
+ })
+ }
+
+ fn public(&self) -> &openpgp::packet::Key {
+ self.tpk.subkeys().nth(0).unwrap().subkey()
+ }
+
+ fn secret(&self) -> Ref<Option<mpis::SecretKey>> {
+ self.secret.borrow()
+ }
+
+ fn is_locked(&self) -> bool {
+ self.secret.borrow().is_none()
+ }
+
+ fn lock(&self) {
+ *self.secret.borrow_mut() = None;
+ }
+
+ fn unlock(&self, password: &Password) -> Result<()> {
+ let secret =
+ self.public().secret().expect("established in new()").clone();
+ *self.secret.borrow_mut() = match secret {
+ SecretKey::Unencrypted { ref mpis } => Some(mpis.clone()),
+ SecretKey::Encrypted { .. } =>
+ Some(secret.decrypt(PublicKeyAlgorithm::ECDH, password)?),
+ };
+ Ok(())
+ }
+
+ fn encrypt(&self, tpk: TPK) -> Result<Vec<u8>> {
+ use openpgp::serialize::stream::{Message, Encryptor, EncryptionMode,
+ LiteralWriter};
+ let mut buf = Vec::new();
+ {
+ let msg = Message::new(&mut buf);
+ let msg = Encryptor::new(msg, &[], &[&self.tpk],
+ EncryptionMode::AtRest, None)?;
+ let mut msg = LiteralWriter::new(msg, DataFormat::Binary,
+ None, None)?;
+ tpk.as_tsk().serialize(&mut msg)?;
+ msg.finalize()?;
+ }
+ Ok(buf)
+ }
+
+ fn decrypt(&self, ciphertext: &[u8]) -> Result<TPK> {
+ use openpgp::parse::stream::*;
+
+ if self.is_locked() {
+ return Err(Error::StoreLocked.into());
+ }
+
+ struct Helper(Key, mpis::SecretKey);
+ impl VerificationHelper for Helper {
+ fn get_public_keys(&mut self, _ids: &[KeyID])
+ -> openpgp::Result<Vec<TPK>> {
+ Ok(Vec::new())
+ }
+ fn check(&mut self, _structure: &MessageStructure)
+ -> openpgp::Result<()> {
+ Ok(())
+ }
+ }
+ impl DecryptionHelper for Helper {
+ fn decrypt<D>(&mut self, pkesks: &[PKESK], _: &[SKESK],
+ mut decrypt: D)
+ -> openpgp::Result<Option<openpgp::Fingerprint>>
+ where D: FnMut(SymmetricAlgorithm, &SessionKey)
+ -> openpgp::Result<()>
+ {
+ pkesks.get(0).ok_or(Error::InternalInconsistency.into())
+ .and_then(|pkesk| pkesk.decrypt(&self.0, &self.1))
+ .and_then(|(algo, session_key)| decrypt(algo, &session_key))
+ .map(|_| None)
+ }
+ }
+
+ let h = Helper(self.public().clone(), self.secret().as_ref().unwrap().clone());
+ let decryptor = Decryptor::from_bytes(ciphertext, h, None)?;
+
+ let tpk = TPK::from_reader(decryptor)?;
+ Ok(tpk)
+ }
+}
+
+/// Shared server state.
+struct State {
+ key: DBKey,
+ c: Connection,
+}
+
+struct StoreServer {
+ _descriptor: ipc::Descriptor,
+ state: Rc<State>,
+}
+
+impl StoreServer {
+ fn new(descriptor: ipc::Descriptor, _handle: Handle) -> Result<Self> {
+ let mut db_path = descriptor.context().home().to_path_buf();
+ db_path.push("secret-key-store.sqlite");
+
+ let c = Connection::open(db_path)?;
+ c.execute_batch("PRAGMA secure_delete = true;")?;
+ c.execute_batch("PRAGMA foreign_keys = true;")?;
+ let key = Self::init(&c)?;
+ let server = StoreServer {
+ _descriptor: descriptor,
+ state: Rc::new(State {
+ key: DBKey::new(key)?,
+ c: c,
+ }),
+ };
+
+ Ok(server)
+ }
+
+ /// Initializes or migrates the database.
+ fn init(c: &Connection) -> Result<openpgp::TPK> {
+ let v = c.query_row(
+ "SELECT version FROM version WHERE id=1",
+ &[], |row| row.get(0));
+
+ if let Ok(v) = v {
+ match v {
+ 1 => return Self::read_local_key(c),
+ _ => unimplemented!(),
+ }
+ }
+
+ c.execute_batch(DB_SCHEMA_1)?;
+
+ Self::generate_local_key(c)
+ }
+
+ /// Generates a local key.
+ fn generate_local_key(c: &Connection) -> Result<openpgp::TPK> {
+ let (tpk, _) = openpgp::tpk::TPKBuilder::new()
+ .set_cipher_suite(openpgp::tpk::CipherSuite::Cv25519)
+ .add_encryption_subkey()
+ .generate()?;
+
+ c.execute("INSERT INTO local_key (id, key) VALUES (1, ?)",
+ &[&tpk.to_vec()?])?;
+ Ok(tpk)
+ }
+
+ /// Reads the local key.
+ fn read_local_key(c: &Connection) -> Result<openpgp::TPK> {
+ let key: Vec<u8> =
+ c.query_row(
+ "SELECT key FROM keys WHERE id = 1",
+ &[],
+ |row| row.get_checked(0).unwrap_or(vec![]))?;
+
+ Ok(openpgp::TPK::from_bytes(&key)?)
+ }
+}
+
+impl store::Server for StoreServer {
+ fn open(&mut self,
+ params: store::OpenParams,
+ mut results: store::OpenResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let params = pry!(params.get());
+ let fp = pry!(params.get_fingerprint());
+ let fp = sry!(Fingerprint::from_hex(fp)
+ .map_err(|_| store::Error::MalformedFingerprint));
+
+ let key = sry!(KeyServer::open(self.state.clone(), &fp));
+ pry!(pry!(results.get().get_result()).set_ok(
+ store::key::ToClient::new(key).into_client::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+
+ fn import(&mut self,
+ params: store::ImportParams,
+ mut results: store::ImportResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+
+ // This is the key to import.
+ let new = sry!(TPK::from_bytes(&pry!(pry!(params.get()).get_key())));
+ let fp = new.fingerprint();
+
+ let id: Option<ID>
+ = sry!(match self.state.c.query_row(
+ "SELECT id FROM keys WHERE fingerprint = ?1",
+ &[&fp.to_hex()],
+ |row| row.get(0)) {
+ Ok(x) => Ok(Some(x)),
+ Err(e) => match e {
+ rusqlite::Error::QueryReturnedNoRows =>
+ Ok(None),
+ _ => Err(e),
+ },
+ });
+
+ if id.is_some() {
+ fail!(store::Error::KeyExists);
+ }
+
+ // Write key back to the database.
+ let mut blob = vec![];
+ sry!(new.serialize(&mut blob));
+
+ sry!(self.state.c.execute("INSERT INTO keys (fingerprint, key, created)
+ VALUES (?, ?, ?)",
+ &[&fp.to_hex(), &blob, &Timestamp::now()]));
+
+ let key = KeyServer::new(self.state.clone(),
+ self.state.c.last_insert_rowid().into());
+ pry!(pry!(results.get().get_result()).set_ok(
+ store::key::ToClient::new(key).into_client::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+
+ fn iter(&mut self,
+ _: store::IterParams,
+ mut results: store::IterResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let iter = KeyIterServer::new(self.state.clone());
+ pry!(pry!(results.get().get_result()).set_ok(
+ store::key_iter::ToClient::new(iter).into_client::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+}
+
+struct KeyServer {
+ state: Rc<State>,
+ id: ID,
+}
+
+impl KeyServer {
+ fn new(state: Rc<State>, id: ID) -> Self {
+ KeyServer {
+ state: state,
+ id: id,
+ }
+ }
+
+ fn open(state: Rc<State>, fp: &Fingerprint) -> Result<Self> {
+ let fp = fp.to_hex();
+ let id = state.c.query_row(
+ "SELECT id FROM keys WHERE fingerprint = ?1",
+ &[&fp], |row| row.get(0))
+ .map_err(|_| Error::NotFound)?;
+
+ Ok(Self::new(state, id))
+ }
+
+}
+
+impl store::key::Server for KeyServer {
+ fn tpk(&mut self,
+ _: store::key::TpkParams,
+ mut results: store::key::TpkResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let key: Vec<u8> = sry!(
+ self.state.c.query_row(
+ "SELECT key FROM keys WHERE id = ?1",
+ &[&self.id],
+ |row| row.get_checked(0).unwrap_or(vec![])));
+ // XXX tpkify key
+ pry!(pry!(results.get().get_result()).set_ok(key.as_slice()));
+ Promise::ok(())
+ }
+
+ fn unlock(&mut self,
+ params: store::key::UnlockParams,
+ mut results: store::key::UnlockResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let passphrase = pry!(pry!(params.get()).get_passphrase());
+ if passphrase != "streng geheim" {
+ fail!(store::Error::BadPassphrase);
+ }
+ Promise::ok(())
+ }
+
+ fn lock(&mut self,
+ _: store::key::LockParams,
+ _: store::key::LockResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ Promise::ok(())
+ }
+
+ fn decrypt(&mut self,
+ params: store::key::DecryptParams,
+ mut results: store::key::DecryptResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ if false {
+ fail!(store::Error::KeyLocked);
+ }
+
+ let sk = pry!(pry!(params.get()).get_sk());
+ pry!(pry!(results.get().get_result()).set_ok(sk));
+ Promise::ok(())
+ }
+
+ fn sign(&mut self,
+ params: store::key::SignParams,
+ mut results: store::key::SignResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+
+ let subkey: openpgp::KeyID =
+ pry!(params.get()).get_subkey_id().into();
+ //pry!(pry!(results.get().get_result()).set_ok(sk));
+ Promise::ok(())
+ }
+}
+
+struct KeyIterServer {
+ state: Rc<State>,
+ n: ID,
+}
+
+impl KeyIterServer {
+ fn new(state: Rc<State>) -> Self {
+ KeyIterServer{state: state, n: ID::null()}
+ }
+}
+
+impl store::key_iter::Server for KeyIterServer {
+ fn next(&mut self,
+ _: store::key_iter::NextParams,
+ mut results: store::key_iter::NextResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let (id, fingerprint): (ID, String) =
+ sry!(self.state.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 mut entry = pry!(results.get().get_result()).init_ok();
+ entry.set_fingerprint(&fingerprint);
+ entry.set_key(store::key::ToClient::new(
+ KeyServer::new(self.state.clone(), id)).into_client::<capnp_rpc::Server>());
+ self.n = id;
+ 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 local_key (
+ id INTEGER PRIMARY KEY,
+ key BLOB);
+
+CREATE TABLE keys (
+ id INTEGER PRIMARY KEY,
+ fingerprint TEXT NOT NULL,
+ key BLOB,
+
+ created INTEGER NOT NULL,
+ updated INTEGER NULL,
+
+ UNIQUE (fingerprint));
+";
+
+impl fmt::Debug for store::Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "store::Error::{}",
+ match self {
+ &store::Error::NotFound => "NotFound",
+ &store::Error::KeyExists => "KeyExists",
+ &store::Error::MalformedKey => "MalformedKey",
+ &store::Error::MalformedFingerprint => "MalformedFingerprint",
+ &store::Error::StoreLocked => "StoreLocked",
+ &store::Error::KeyLocked => "KeyLocked",
+ &store::Error::BadPassphrase => "BadPassphrase",
+ })
+ }
+}
+
+impl From<failure::Error> for store::Error {
+ fn from(e: failure::Error) -> Self {
+ if e.downcast_ref::<openpgp::Error>().is_some() {
+ // XXX differentiate
+ return store::Error::MalformedKey;
+ }
+
+ if let Some(e) = e.downcast_ref::<Error>() {
+ return match e {
+ &Error::NotFound => store::Error::NotFound,
+ _ => unimplemented!(),
+ }
+ }
+
+ // XXX: Currently, this does not happen, hence rustc warns
+ // if let Some(e) = e.downcast_ref::<core::Error>() {
+ // return match e {
+ // _ => unimplemented!(),
+ // }
+ // }
+
+ if let Some(e) = e.downcast_ref::<rusqlite::Error>() {
+ return match e {
+ &rusqlite::Error::SqliteFailure(f, _) => match f.code {
+ rusqlite::ErrorCode::ConstraintViolation =>
+ store::Error::NotFound,
+ _ => unimplemented!(),
+ },
+ &rusqlite::Error::QueryReturnedNoRows =>
+ store::Error::NotFound,
+ _ => unimplemented!(),
+ }
+ }
+
+ eprintln!("Error not converted: {:?}", e);
+ unimplemented!()
+ }
+}
+
+impl From<rusqlite::Error> for store::Error {
+ fn from(error: rusqlite::Error) -> Self {
+ match error {
+ rusqlite::Error::SqliteFailure(f, _) => match f.code {
+ rusqlite::ErrorCode::ConstraintViolation =>
+ store::Error::NotFound,
+ _ => unimplemented!(),
+ },
+ rusqlite::Error::QueryReturnedNoRows =>
+ store::Error::NotFound,
+ _ => unimplemented!(),
+ }
+ }
+}
diff --git a/secret/src/lib.rs b/secret/src/lib.rs
new file mode 100644
index 00000000..1debd51f
--- /dev/null
+++ b/secret/src/lib.rs
@@ -0,0 +1,317 @@
+//! Handling secret keys.
+
+extern crate capnp;
+#[macro_use]
+extern crate capnp_rpc;
+#[macro_use]
+extern crate failure;
+extern crate futures;
+extern crate rusqlite;
+extern crate time;
+extern crate tokio_core;
+extern crate tokio_io;
+
+use capnp::capability::Promise;
+use capnp_rpc::rpc_twoparty_capnp::Side;
+use futures::Future;
+use std::cell::RefCell;
+use std::fmt;
+use std::rc::Rc;
+use tokio_core::reactor::Core;
+
+extern crate sequoia_openpgp as openpgp;
+#[allow(unused_imports)]
+#[macro_use]
+extern crate sequoia_core;
+use sequoia_core as core;
+extern crate sequoia_ipc as ipc;
+extern crate sequoia_store_rusqlite as store_rusqlite;
+
+/// Macros managing requests and responses.
+#[macro_use] mod macros;
+
+use openpgp::Fingerprint;
+use openpgp::TPK;
+use openpgp::parse::Parse;
+use openpgp::serialize::Serialize;
+use core::Context;
+
+#[allow(dead_code)] pub /*XXX*/ mod secret_protocol_capnp;
+use secret_protocol_capnp::store;
+
+trait SecretKeyObject {
+ fn fingerprint(&self) -> &Fingerprint;
+ fn tpk(&self) -> Result<TPK>;
+ fn unlock(&self, passphrase: &str) -> Result<()>;
+ fn lock(&self) -> Result<()>;
+ fn decrypt(&self, sk: &[u8]) -> Result<Vec<u8>>;
+}
+
+/// Storage backend.
+mod backend;
+
+/// Returns the service descriptor.
+#[doc(hidden)]
+pub fn descriptor(c: &Context) -> ipc::Descriptor {
+ ipc::Descriptor::new(
+ c,
+ c.home().join("secret-key-store.cookie"),
+ c.lib().join("sequoia-secret-key-store"),
+ backend::factory,
+ )
+}
+
+/// A secret key object.
+pub struct Key {
+ core: Rc<RefCell<Core>>,
+ fp: Fingerprint,
+ key: store::key::Client,
+}
+
+impl fmt::Debug for Key {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Key {{ {} }}", self.fp)
+ }
+}
+
+impl Key {
+ fn connect(c: &Context, ipc_policy: Option<core::IPCPolicy>)
+ -> Result<(Core, store::Client)> {
+ let descriptor = descriptor(c);
+ let core = Core::new()?;
+ let handle = core.handle();
+
+ let mut rpc_system
+ = match descriptor.connect_with_policy(
+ &handle, ipc_policy.unwrap_or(core::IPCPolicy::External)) {
+ Ok(r) => r,
+ Err(e) => return Err(e.into()),
+ };
+
+ let client: store::Client = rpc_system.bootstrap(Side::Server);
+ handle.spawn(rpc_system.map_err(|_e| ()));
+
+ Ok((core, client))
+ }
+
+ pub fn open(c: &Context, fp: &Fingerprint,
+ ipc_policy: Option<core::IPCPolicy>) -> Result<Self> {
+ let (mut core, client) = Self::connect(c, ipc_policy)?;
+
+ let mut request = client.open_request();
+ request.get().set_fingerprint(&fp.to_hex());
+
+ let key = make_request!(&mut core, request)?;
+ Ok(Self::new(Rc::new(RefCell::new(core)), fp, key))
+ }
+
+ fn new(core: Rc<RefCell<Core>>, fp: &Fingerprint,
+ key: store::key::Client