diff options
-rw-r--r-- | openpgp/tests/data/keys/testy-new-private.pgp | bin | 0 -> 504 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/testy-new-with-sig.pgp | bin | 0 -> 740 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/testy-new.pgp | bin | 0 -> 430 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/testy-private.pgp | bin | 0 -> 2540 bytes | |||
-rw-r--r-- | store/Cargo.toml | 26 | ||||
-rw-r--r-- | store/build.rs | 10 | ||||
-rw-r--r-- | store/src/backend.rs | 534 | ||||
-rw-r--r-- | store/src/lib.rs | 648 | ||||
-rw-r--r-- | store/src/macros.rs | 116 | ||||
-rw-r--r-- | store/src/server.rs | 28 | ||||
-rw-r--r-- | store/src/store_protocol.capnp | 53 | ||||
-rw-r--r-- | store/src/store_protocol_capnp.rs | 2 |
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 Binary files differnew file mode 100644 index 00000000..1faf30c6 --- /dev/null +++ b/openpgp/tests/data/keys/testy-new-private.pgp diff --git a/openpgp/tests/data/keys/testy-new-with-sig.pgp b/openpgp/tests/data/keys/testy-new-with-sig.pgp Binary files differnew file mode 100644 index 00000000..0f6f054e --- /dev/null +++ b/openpgp/tests/data/keys/testy-new-with-sig.pgp diff --git a/openpgp/tests/data/keys/testy-new.pgp b/openpgp/tests/data/keys/testy-new.pgp Binary files differnew file mode 100644 index 00000000..2bfdcfd2 --- /dev/null +++ b/openpgp/tests/data/keys/testy-new.pgp diff --git a/openpgp/tests/data/keys/testy-private.pgp b/openpgp/tests/data/keys/testy-private.pgp Binary files differnew file mode 100644 index 00000000..ceadd8e7 --- /dev/null +++ b/openpgp/tests/data/keys/testy-private.pgp 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(¤t)); + + if current.fingerprint().to_hex() != fingerprint { + // Inconsistent database. + fail!(node::Error::SystemError); + } + + if current.fingerprint() != new.fingerprint() { + if force || new.is_signed_by(¤t) { + // 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(¤t)); + + 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; |