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>2018-02-21 17:29:38 +0100
commit7b9eb7dd5a09db08cb7788456fd07835c232d72a (patch)
tree3d7f56b8b99c4abea7e1c960c44bf16146f6a056
parentd70e9361619cdeccb728eec8f9470498f8ebb84a (diff)
-rw-r--r--Cargo.toml1
-rw-r--r--secret/Cargo.toml36
-rw-r--r--secret/build.rs10
-rw-r--r--secret/src/backend.rs455
-rw-r--r--secret/src/lib.rs326
-rw-r--r--secret/src/macros.rs93
-rw-r--r--secret/src/secret_protocol.capnp42
-rw-r--r--secret/src/secret_protocol_capnp.rs2
-rw-r--r--secret/src/server.rs14
9 files changed, 979 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 1aacaccc..e5c64ac1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ openpgp = { path = "openpgp" }
sequoia-core = { path = "core" }
sequoia-ffi = { path = "ffi" }
sequoia-net = { path = "net" }
+sequoia-secret = { path = "secret" }
sequoia-store = { path = "store" }
sequoia-tool = { path = "tool" }
diff --git a/secret/Cargo.toml b/secret/Cargo.toml
new file mode 100644
index 00000000..da0f39e1
--- /dev/null
+++ b/secret/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "sequoia-secret"
+version = "0.1.0"
+authors = ["Justus Winter <justus@sequoia-pgp.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"
+failure = "0.1.1"
+futures = "0.1.17"
+rusqlite = "0.12.0"
+time = "0.1.38"
+tokio-core = "0.1.10"
+tokio-io = "0.1.4"
+
+[build-dependencies]
+capnpc = "0.8"
+
+[lib]
+name = "sequoia_secret"
+path = "src/lib.rs"
+
+[[bin]]
+name = "secret-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.rs b/secret/src/backend.rs
new file mode 100644
index 00000000..8fe5ca5c
--- /dev/null
+++ b/secret/src/backend.rs
@@ -0,0 +1,455 @@
+//! 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::types::{ToSql, ToSqlOutput, FromSql, FromSqlResult, ValueRef};
+use rusqlite;
+use std::fmt;
+use std::ops::{Add, Sub};
+use std::rc::Rc;
+use tokio_core::reactor::Handle;
+use tokio_core;
+use tokio_io::io::ReadHalf;
+use time::{Timespec, Duration, now_utc};
+
+use openpgp::Fingerprint;
+use openpgp::tpk;
+use sequoia_core as core;
+use sequoia_net::ipc;
+
+use secret_protocol_capnp::node;
+
+use super::{TSK, Error, Result};
+
+/* Entry point. */
+
+/// Makes backends.
+#[doc(hidden)]
+pub fn factory(descriptor: ipc::Descriptor, handle: Handle) -> Option<Box<ipc::Handler>> {
+ match Backend::new(descriptor, handle) {
+ Ok(backend) => Some(Box::new(backend)),
+ Err(_) => None,
+ }
+}
+
+struct Backend {
+ secret: node::Client,
+}
+
+impl Backend {
+ fn new(descriptor: ipc::Descriptor, handle: Handle) -> Result<Self> {
+ Ok(Backend {
+ secret: node::ToClient::new(NodeServer::new(descriptor, handle)?)
+ .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.secret.clone().client))
+ }
+}
+
+/* Server implementation. */
+
+struct NodeServer {
+ _descriptor: ipc::Descriptor,
+ c: Rc<Connection>,
+}
+
+impl NodeServer {
+ fn new(descriptor: ipc::Descriptor, handle: Handle) -> Result<Self> {
+ let mut db_path = descriptor.context().home().to_path_buf();
+ db_path.push("secrets.sqlite");
+
+ let c = Connection::open(db_path)?;
+ c.execute_batch("PRAGMA secure_delete = true;")?;
+ c.execute_batch("PRAGMA foreign_keys = true;")?;
+ let server = NodeServer {
+ _descriptor: descriptor,
+ c: Rc::new(c),
+ };
+ server.init()?;
+
+ Ok(server)
+ }
+
+ /// Initializes or migrates the database.
+ fn init(&self) -> Result<()> {
+ let v = self.c.query_row(
+ "SELECT version FROM version WHERE id=1",
+ &[], |row| row.get(0));
+
+ if let Ok(v) = v {
+ match v {
+ 1 => return Ok(()),
+ _ => unimplemented!(),
+ }
+ }
+
+ self.c.execute_batch(DB_SCHEMA_1)?;
+ Ok(())
+ }
+}
+
+impl node::Server for NodeServer {
+ fn open(&mut self,
+ params: node::OpenParams,
+ mut results: node::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)
+ .ok_or(node::Error::MalformedFingerprint));
+
+ let key = sry!(KeyServer::open(self.c.clone(), &fp));
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::key::ToClient::new(key).from_server::<capnp_rpc::Server>()));
+ Promise::ok(())
+ }
+
+ fn import(&mut self,
+ params: node::ImportParams,
+ mut results: node::ImportResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+
+ // This is the key to import.
+ let new = sry!(TSK::from_bytes(&pry!(pry!(params.get()).get_key())));
+ let fp = new.fingerprint();
+
+ let id: Option<ID>
+ = sry!(match self.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!(node::Error::KeyExists);
+ }
+
+ // Write key back to the database.
+ let mut blob = vec![];
+ sry!(new.serialize(&mut blob));
+
+ sry!(self.c.execute("INSERT INTO keys (fingerprint, key, created)
+ VALUES (?, ?, ?)",
+ &[&fp.to_hex(), &blob, &Timestamp::now()]));
+
+ let key = KeyServer::new(self.c.clone(),
+ self.c.last_insert_rowid().into());
+ pry!(pry!(results.get().get_result()).set_ok(
+ node::key::ToClient::new(key).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 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 KeyServer {
+ c: Rc<Connection>,
+ id: ID,
+ locked: bool,
+}
+
+impl KeyServer {
+ fn new(c: Rc<Connection>, id: ID) -> Self {
+ KeyServer {
+ c: c,
+ id: id,
+ locked: true,
+ }
+ }
+
+ fn open(c: Rc<Connection>, fp: &Fingerprint) -> Result<Self> {
+ let fp = fp.to_hex();
+ let id = c.query_row(
+ "SELECT id FROM keys WHERE fingerprint = ?1",
+ &[&fp], |row| row.get(0))
+ .map_err(|_| Error::NotFound)?;
+
+ Ok(Self::new(c, id))
+ }
+
+}
+
+impl node::key::Server for KeyServer {
+ 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.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: node::key::UnlockParams,
+ mut results: node::key::UnlockResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ let passphrase = pry!(pry!(params.get()).get_passphrase());
+ if passphrase != "streng geheim" {
+ fail!(node::Error::BadPassphrase);
+ }
+ self.locked = false;
+ Promise::ok(())
+ }
+
+ fn lock(&mut self,
+ _: node::key::LockParams,
+ mut results: node::key::LockResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ self.locked = true;
+ Promise::ok(())
+ }
+
+ fn decrypt(&mut self,
+ params: node::key::DecryptParams,
+ mut results: node::key::DecryptResults)
+ -> Promise<(), capnp::Error> {
+ bind_results!(results);
+ if self.locked {
+ fail!(node::Error::KeyLocked);
+ }
+
+ let sk = pry!(pry!(params.get()).get_sk());
+ pry!(pry!(results.get().get_result()).set_ok(sk));
+ Promise::ok(())
+ }
+}
+
+struct KeyIterServer {
+ c: Rc<Connection>,
+ n: ID,
+}
+
+impl KeyIterServer {
+ fn new(c: Rc<Connection>) -> Self {
+ KeyIterServer{c: c, n: ID::null()}
+ }
+}
+
+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): (ID, 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 mut entry = pry!(results.get().get_result()).init_ok();
+ entry.set_fingerprint(&fingerprint);
+ entry.set_key(node::key::ToClient::new(
+ KeyServer::new(self.c.clone(), id)).from_server::<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 keys (
+ id INTEGER PRIMARY KEY,
+ fingerprint TEXT NOT NULL,
+ key BLOB,
+
+ created INTEGER NOT NULL,
+ updated INTEGER NULL,
+
+ UNIQUE (fingerprint));
+";
+
+/// Represents a row id.
+///
+/// This is used to represent handles to stored objects.
+#[derive(Copy, Clone, PartialEq)]
+pub struct ID(i64);
+
+impl ID {
+ /// Returns ID(0).
+ ///
+ /// This is smaller than all valid ids.
+ fn null() -> Self {
+ ID(0)
+ }
+
+ /// Returns the largest id.
+ fn max() -> Self {
+ ID(::std::i64::MAX)
+ }
+}
+
+impl From<i64> for ID {
+ fn from(id: i64) -> Self {
+ ID(id)
+ }
+}
+
+impl ToSql for ID {
+ fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
+ Ok(ToSqlOutput::from(self.0))
+ }
+}
+
+impl FromSql for ID {
+ fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+ value.as_i64().map(|id| id.into())
+ }
+}
+
+impl fmt::Debug for node::Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "node::Error::{}",
+ match self {
+ &node::Error::NotFound => "NotFound",
+ &node::Error::KeyExists => "KeyExists",
+ &node::Error::MalformedKey => "MalformedKey",
+ &node::Error::MalformedFingerprint => "MalformedFingerprint",
+ &node::Error::KeyLocked => "KeyLocked",
+ &node::Error::BadPassphrase => "BadPassphrase",
+ })
+ }
+}
+
+impl From<failure::Error> for node::Error {
+ fn from(e: failure::Error) -> Self {
+ if e.downcast_ref::<tpk::Error>().is_some() {
+ return node::Error::MalformedKey;
+ }
+
+ if let Some(e) = e.downcast_ref::<Error>() {
+ return match e {
+ &Error::NotFound => node::Error::NotFound,
+ _ => unreachable!(),
+ }
+ }
+
+ if let Some(e) = e.downcast_ref::<core::Error>() {
+ return match e {
+ _ => unreachable!(),
+ }
+ }
+
+ if let Some(e) = e.downcast_ref::<rusqlite::Error>() {
+ return match e {
+ &rusqlite::Error::SqliteFailure(f, _) => match f.code {
+ rusqlite::ErrorCode::ConstraintViolation =>
+ node::Error::NotFound,
+ _ => unimplemented!(),
+ },
+ &rusqlite::Error::QueryReturnedNoRows =>
+ node::Error::NotFound,
+ _ => unimplemented!(),
+ }
+ }
+
+ eprintln!("Error not converted: {:?}", e);
+ unimplemented!()
+ }
+}
+
+impl From<rusqlite::Error> for node::Error {
+ fn from(error: rusqlite::Error) -> Self {
+ match error {
+ rusqlite::Error::SqliteFailure(f, _) => match f.code {
+ rusqlite::ErrorCode::ConstraintViolation =>
+ node::Error::NotFound,
+ _ => unimplemented!(),
+ },
+ rusqlite::Error::QueryReturnedNoRows =>
+ node::Error::NotFound,
+ _ => unimplemented!(),
+ }
+ }
+}
+
+/* Timestamps. */
+
+/// A serializable system time.
+#[derive(Clone, Copy, PartialEq, PartialOrd)]
+struct Timestamp(Timespec);
+
+impl Timestamp {
+ fn now() -> Self {
+ Timestamp(now_utc().to_timespec())
+ }
+
+ /// Converts to unix time.
+ fn unix(&self) -> i64 {
+ self.0.sec
+ }
+}
+
+impl ToSql for Timestamp {
+ fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
+ Ok(ToSqlOutput::from(self.0.sec))
+ }
+}
+
+impl FromSql for Timestamp {
+ fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+ value.as_i64().map(|t| Timestamp(Timespec::new(t, 0)))
+ }
+}
+
+impl Add<Duration> for Timestamp {
+ type Output = Timestamp;
+
+ fn add(self, other: Duration) -> Timestamp {
+ Timestamp(self.0 + other)
+ }
+}
+
+impl Sub<Timestamp> for Timestamp {
+ type Output = Duration;
+
+ fn sub(self, other: Self) -> Self::Output {
+ self.0 - other.0
+ }
+}
diff --git a/secret/src/lib.rs b/secret/src/lib.rs
new file mode 100644
index 00000000..02fec9a0
--- /dev/null
+++ b/secret/src/lib.rs
@@ -0,0 +1,326 @@
+//! 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::io;
+use std::rc::Rc;
+use tokio_core::reactor::Core;
+
+extern crate openpgp;
+#[allow(unused_imports)]
+#[macro_use]
+extern crate sequoia_core;
+use sequoia_core as core;
+extern crate sequoia_net;
+use sequoia_net as net;
+
+/// Macros managing requests and responses.
+#[macro_use] mod macros;
+
+use openpgp::Fingerprint;
+use openpgp::tpk::TPK;
+use core::Context;
+use net::ipc;
+
+#[allow(dead_code)] pub /*XXX*/ mod secret_protocol_capnp;
+use secret_protocol_capnp::node;
+
+/// Dummy TSK implementation.
+pub struct TSK(TPK);
+
+impl TSK {
+ pub fn new(tpk: TPK) -> TSK {
+ TSK(tpk)
+ }
+
+ pub fn from_bytes(b: &[u8]) -> Result<TSK> {
+ TPK::from_bytes(b).map(|x| TSK(x))
+ }
+
+ pub fn serialize<W: io::Write>(&self, o: &mut W) -> Result<()> {
+ self.0.serialize(o)
+ }
+
+ pub fn tpk(&self) -> TPK {
+ self.0.clone()
+ }
+
+ pub fn fingerprint(&self) -> Fingerprint {
+ self.0.fingerprint()
+ }
+}
+
+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("S.secret"),
+ c.lib().join("secret-store"),
+ backend::factory,
+ )
+}
+
+/// A secret key object.
+pub struct Key {
+ core: Rc<RefCell<Core>>,
+ fp: Fingerprint,
+ key: node::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) -> Result<(Core, node::Client)> {
+ let descriptor = descriptor(c);
+ let core = Core::new()?;
+ let handle = core.handle();
+
+ let mut rpc_system
+ = match descriptor.connect_with_policy(&handle,
+ core::IPCPolicy::External) {
+ Ok(r) => r,
+ Err(e) => return Err(e.into()),
+ };
+
+ let client: node::Client = rpc_system.bootstrap(Side::Server);
+ handle.spawn(rpc_system.map_err(|_e| ()));
+
+ Ok((core, client))
+ }
+
+ pub fn open(c: &Context, fp: &Fingerprint) -> Result<Self> {
+ let (mut core, client) = Self::connect(c)?;
+
+ 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: node::key::Client) -> Self {
+ Key{core: core, fp: fp.clone(), key: key}
+ }
+
+ pub fn import(c: &Context, tsk: &TSK) -> Result<Key> {
+ let mut blob = vec![];
+ tsk.serialize(&mut blob)?;
+ let (mut core, client) = Self::connect(c)?;
+ let mut request = client.import_request();
+ request.get().set_key(&blob);
+ let key = make_request!(core, request)?;
+ Ok(Self::new(Rc::new(RefCell::new(core)), &tsk.fingerprint(), key))
+ }
+
+ /// Lists all keys.
+ pub fn list(c: &Context) -> Result<KeyIter> {
+ let (mut core, client) = Self::connect(c)?;
+ let request = client.iter_request();
+ let iter = make_request!(&mut core, request)?;
+ Ok(KeyIter{core: Rc::new(RefCell::new(core)), iter: iter})
+ }
+}
+
+impl SecretKeyObject for Key {
+ /// Returns the fingerprint.
+ fn fingerprint(&self) -> &Fingerprint {
+ &self.fp
+ }
+
+ /// Returns the TPK.
+ fn tpk(&self) -> Result<TPK> {
+ make_request_map!(self.core.borrow_mut(),
+ self.key.tpk_request(),
+ |tpk| TPK::from_bytes(tpk).map_err(|e| e.into()))
+ }
+
+ /// Unlocks this secret key.
+ fn unlock(&self, passphrase: &str) -> Result<()> {
+ let mut request = self.key.unlock_request();
+ request.get().set_passphrase(passphrase.into());
+ make_request_map!(self.core.borrow_mut(), request,
+ |_| Ok(()))
+ }
+
+ /// Locks this secret key.
+ fn lock(&self) -> Result<()> {
+ make_request_map!(self.core.borrow_mut(),
+ self.key.lock_request(),
+ |_| Ok(()))
+ }
+
+ /// Decrypts the given session key
+ fn decrypt(&self, sk: &[u8]) -> Result<Vec<u8>> {
+ let mut request = self.key.decrypt_request();
+ request.get().set_sk(sk);
+ make_request_map!(self.core.borrow_mut(), request,
+ |x| Ok(Vec::from(x)))
+ }
+}
+
+/// Iterates over keys in the common key pool.
+pub struct KeyIter {
+ core: Rc<RefCell<Core>>,
+ iter: node::key_iter::Client,
+}
+
+impl Iterator for KeyIter {
+ type Item = Key;
+
+ 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| {
+ let fp =
+ Fingerprint::from_hex(r.get_fingerprint()?)
+ .ok_or(Error::NotFound)?;
+ Ok(Key::new(self.core.clone(), &fp, r.get_key()?))
+ })
+ };
+ doit().ok()
+ }
+}
+
+/* Error handling. */
+
+/// Results for sequoia-secret.
+pub type Result<T> = ::std::result::Result<T, failure::Error>;
+
+#[derive(Fail, Debug)]
+/// Errors returned from the store.
+pub enum Error {
+ /// A requested key was not found.
+ #[fail(display = "Key not found")]
+ NotFound,
+ /// The key already exists.
+ #[fail(display = "Key already exists")]
+ KeyExists,
+ /// The key is locked.
+ #[fail(display = "Key is locked")]
+ KeyLocked,
+ /// Bad unlock passphrase.
+ #[fail(display = "Bad passphrase")]
+ BadPassphrase,
+}
+
+/// Converts from backend errors.
+impl From<node::Error> for failure::Error {
+ fn from(error: node::Error) -> Self {
+ match error {
+ node::Error::NotFound => Error::NotFound,
+ node::Error::KeyExists => Error::KeyExists,
+ node::Error::KeyLocked => Error::KeyLocked,
+ node::Error::BadPassphrase => Error::BadPassphrase,
+ _ => unimplemented!(),
+ }.into()
+ }
+}
+
+#[cfg(test)]
+mod store_test {
+ use super::*;
+
+ macro_rules! bytes {
+ ( $x:expr ) => { include_bytes!(concat!("../../openpgp/tests/data/keys/", $x)) };
+ }
+
+ use std::path::PathBuf;
+ /// Returns the path to the binaries.
+ fn libpath() -> PathBuf {
+ use std::env::current_exe;
+ let mut path = current_exe().unwrap();
+ path.pop();
+ path.pop();
+ path
+ }
+
+ #[test]
+ fn import_key() {
+ let ctx = core::Context::configure("org.sequoia-pgp.tests")
+ .ephemeral()
+ .network_policy(core::NetworkPolicy::Offline)
+ .ipc_policy(core::IPCPolicy::Internal)
+ .lib(libpath())
+ .build().unwrap();
+ let tsk = TSK::from_bytes(bytes!("testy.pgp")).unwrap();
+ let key = Key::import(&ctx, &tsk).unwrap();
+ let tpk = key.tpk().unwrap();
+ assert_eq!(tsk.fingerprint(), tpk.fingerprint());
+ }
+
+ #[test]
+ fn key_not_found() {
+ let ctx = core::Context::configure("org.sequoia-pgp.tests")
+ .ephemeral()
+ .network_policy(core::NetworkPolicy::Offline)
+ .ipc_policy(core::IPCPolicy::Internal)
+ .lib(libpath())
+ .build().unwrap();
+ let fp = Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb");
+ let r = Key::open(&ctx, &fp);
+ assert_match!(Error::NotFound
+ = r.err().unwrap().downcast::<Error>().unwrap());
+ }
+
+ #[test]
+ fn decryption() {
+ let ctx = core::Context::configure("org.sequoia-pgp.tests")
+ .ephemeral()
+ .network_policy(core::NetworkPolicy::Offline)
+ .ipc_policy(core::IPCPolicy::Internal)
+ .lib(libpath())
+ .build().unwrap();
+ let tsk = TSK::from_bytes(bytes!("testy.pgp")).unwrap();
+ let key = Key::import(&ctx, &tsk).unwrap();
+ let sk = Vec::from(&b"abc"[..]);
+
+ let r = key.decrypt(&sk);
+ assert_match!(Error::KeyLocked
+ = r.err().unwrap().downcast::<Error>().unwrap());
+
+ let r = key.unlock("nicht geheim");
+ assert_match!(Error::BadPassphrase
+ = r.err().unwrap().downcast::<Error>().unwrap());
+
+ key.unlock("streng geheim").unwrap();
+ assert_eq!(key.decrypt(&sk).unwrap(), sk);
+
+ key.lock().unwrap();
+ let r = key.decrypt(&sk);
+ assert_match!(Error::KeyLocked
+ = r.err().unwrap().downcast::<Error>().unwrap());
+ }
+}
diff --git a/secret/src/macros.rs b/secret/src/macros.rs
new file mode 100644
index 00000000..7e08d381
--- /dev/null
+++ b/secret/src/macros.rs
@@ -0,0 +1,93 @@
+//! Macros managing requests and responses.
+//!
+//! Our protocol uses the Result(T) type to communicate rich errors to
+//! the client. These macros deal with sending a request using the