diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2018-02-06 13:10:10 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-05-24 16:20:49 +0200 |
commit | d52d2f4eaaf6e9806d18f65fb7ff4f8298abc1de (patch) | |
tree | b023aa5384a8de1a7f0b5b0afbdfb2d488db3698 | |
parent | e97befc021e3e66b554cdd698a37ca80d9d88e44 (diff) |
-rw-r--r-- | .gitlab-ci.yml | 2 | ||||
-rw-r--r-- | Cargo.lock | 20 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | secret/Cargo.toml | 52 | ||||
-rw-r--r-- | secret/build.rs | 10 | ||||
-rw-r--r-- | secret/src/backend/mod.rs | 535 | ||||
-rw-r--r-- | secret/src/lib.rs | 317 | ||||
-rw-r--r-- | secret/src/macros.rs | 98 | ||||
-rw-r--r-- | secret/src/secret_protocol.capnp | 44 | ||||
-rw-r--r-- | secret/src/secret_protocol_capnp.rs | 2 | ||||
-rw-r--r-- | secret/src/server.rs | 14 |
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 @@ -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 = [ @@ -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 |