diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2018-02-06 13:10:10 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2018-02-21 17:29:38 +0100 |
commit | 7b9eb7dd5a09db08cb7788456fd07835c232d72a (patch) | |
tree | 3d7f56b8b99c4abea7e1c960c44bf16146f6a056 | |
parent | d70e9361619cdeccb728eec8f9470498f8ebb84a (diff) |
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | secret/Cargo.toml | 36 | ||||
-rw-r--r-- | secret/build.rs | 10 | ||||
-rw-r--r-- | secret/src/backend.rs | 455 | ||||
-rw-r--r-- | secret/src/lib.rs | 326 | ||||
-rw-r--r-- | secret/src/macros.rs | 93 | ||||
-rw-r--r-- | secret/src/secret_protocol.capnp | 42 | ||||
-rw-r--r-- | secret/src/secret_protocol_capnp.rs | 2 | ||||
-rw-r--r-- | secret/src/server.rs | 14 |
9 files changed, 979 insertions, 0 deletions
@@ -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 |