diff --git a/Cargo.lock b/Cargo.lock index 409dbc418..f36aa132e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,6 +635,8 @@ name = "ethcore-secretstore" version = "1.0.0" dependencies = [ "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ethabi 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ethcore 1.7.0", "ethcore-devtools 1.7.0", "ethcore-ipc 1.7.0", "ethcore-ipc-codegen 1.7.0", @@ -646,6 +648,7 @@ dependencies = [ "futures-cpupool 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "native-contracts 0.1.0", "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/native_contracts/build.rs b/ethcore/native_contracts/build.rs index a8488617a..91eaa86cd 100644 --- a/ethcore/native_contracts/build.rs +++ b/ethcore/native_contracts/build.rs @@ -23,6 +23,7 @@ use std::io::Write; // TODO: `include!` these from files where they're pretty-printed? const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#; const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#; +const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#; fn build_file(name: &str, abi: &str, filename: &str) { let code = ::native_contract_generator::generate_module(name, abi).unwrap(); @@ -37,4 +38,5 @@ fn build_file(name: &str, abi: &str, filename: &str) { fn main() { build_file("Registry", REGISTRY_ABI, "registry.rs"); build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs"); + build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs"); } diff --git a/ethcore/native_contracts/src/lib.rs b/ethcore/native_contracts/src/lib.rs index 55c6446b7..e894a636f 100644 --- a/ethcore/native_contracts/src/lib.rs +++ b/ethcore/native_contracts/src/lib.rs @@ -25,6 +25,8 @@ extern crate ethcore_util as util; mod registry; mod service_transaction; +mod secretstore_acl_storage; pub use self::registry::Registry; pub use self::service_transaction::ServiceTransactionChecker; +pub use self::secretstore_acl_storage::SecretStoreAclStorage; diff --git a/ethcore/native_contracts/src/secretstore_acl_storage.rs b/ethcore/native_contracts/src/secretstore_acl_storage.rs new file mode 100644 index 000000000..3ebfcfb75 --- /dev/null +++ b/ethcore/native_contracts/src/secretstore_acl_storage.rs @@ -0,0 +1,22 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +#![allow(unused_mut, unused_variables, unused_imports)] + +//! Secret store ACL storage contract. +// TODO: testing. + +include!(concat!(env!("OUT_DIR"), "/secretstore_acl_storage.rs")); diff --git a/parity/run.rs b/parity/run.rs index a85bcc39b..8da5130d4 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -463,7 +463,9 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?; // secret store key server - let secretstore_deps = secretstore::Dependencies { }; + let secretstore_deps = secretstore::Dependencies { + client: client.clone(), + }; let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps); // the ipfs server diff --git a/parity/secretstore.rs b/parity/secretstore.rs index 13d6d28d2..d31614193 100644 --- a/parity/secretstore.rs +++ b/parity/secretstore.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::sync::Arc; use dir::default_data_path; +use ethcore::client::Client; use helpers::replace_home; #[derive(Debug, PartialEq, Clone)] @@ -30,10 +32,10 @@ pub struct Configuration { pub data_path: String, } -#[derive(Debug, PartialEq, Clone)] /// Secret store dependencies pub struct Dependencies { - // the only dependency will be BlockChainClient + /// Blockchain client. + pub client: Arc, } #[cfg(not(feature = "secretstore"))] @@ -64,7 +66,7 @@ mod server { impl KeyServer { /// Create new key server - pub fn new(conf: Configuration, _deps: Dependencies) -> Result { + pub fn new(conf: Configuration, deps: Dependencies) -> Result { let key_pairs = vec![ ethkey::KeyPair::from_secret("6c26a76e9b31048d170873a791401c7e799a11f0cefc0171cc31a49800967509".parse().unwrap()).unwrap(), ethkey::KeyPair::from_secret("7e94018b3731afdb3b4e6f4c3e179475640166da12e1d1b0c7d80729b1a5b452".parse().unwrap()).unwrap(), @@ -96,7 +98,7 @@ mod server { } }; - let key_server = ethcore_secretstore::start(conf) + let key_server = ethcore_secretstore::start(deps.client, conf) .map_err(Into::::into)?; Ok(KeyServer { diff --git a/secret_store/Cargo.toml b/secret_store/Cargo.toml index fba76804b..539f15f1f 100644 --- a/secret_store/Cargo.toml +++ b/secret_store/Cargo.toml @@ -24,12 +24,15 @@ tokio-core = "0.1" tokio-service = "0.1" tokio-proto = "0.1" url = "1.0" +ethabi = "1.0.0" +ethcore = { path = "../ethcore" } ethcore-devtools = { path = "../devtools" } ethcore-util = { path = "../util" } ethcore-ipc = { path = "../ipc/rpc" } ethcore-ipc-nano = { path = "../ipc/nano" } ethcrypto = { path = "../ethcrypto" } ethkey = { path = "../ethkey" } +native-contracts = { path = "../ethcore/native_contracts" } [profile.release] debug = true diff --git a/secret_store/src/acl_storage.rs b/secret_store/src/acl_storage.rs index 47ec3d44a..fea45c920 100644 --- a/secret_store/src/acl_storage.rs +++ b/secret_store/src/acl_storage.rs @@ -14,38 +14,92 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::collections::{HashMap, HashSet}; -use parking_lot::RwLock; +use std::sync::Arc; +use futures::{future, Future}; +use parking_lot::Mutex; +use ethkey::public_to_address; +use ethcore::client::{Client, BlockChainClient, BlockId}; +use native_contracts::SecretStoreAclStorage; use types::all::{Error, DocumentAddress, Public}; +const ACL_CHECKER_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_acl_checker"; + /// ACL storage of Secret Store pub trait AclStorage: Send + Sync { /// Check if requestor with `public` key can access document with hash `document` fn check(&self, public: &Public, document: &DocumentAddress) -> Result; } -/// Dummy ACL storage implementation -#[derive(Default, Debug)] -pub struct DummyAclStorage { - prohibited: RwLock>>, +/// On-chain ACL storage implementation. +pub struct OnChainAclStorage { + /// Blockchain client. + client: Arc, + /// On-chain contract. + contract: Mutex>, } -impl DummyAclStorage { - #[cfg(test)] - /// Prohibit given requestor access to given document - pub fn prohibit(&self, public: Public, document: DocumentAddress) { - self.prohibited.write() - .entry(public) - .or_insert_with(Default::default) - .insert(document); +impl OnChainAclStorage { + pub fn new(client: Arc) -> Self { + OnChainAclStorage { + client: client, + contract: Mutex::new(None), + } } } -impl AclStorage for DummyAclStorage { +impl AclStorage for OnChainAclStorage { fn check(&self, public: &Public, document: &DocumentAddress) -> Result { - Ok(self.prohibited.read() - .get(public) - .map(|docs| !docs.contains(document)) - .unwrap_or(true)) + let mut contract = self.contract.lock(); + if !contract.is_some() { + *contract = self.client.registry_address(ACL_CHECKER_CONTRACT_REGISTRY_NAME.to_owned()) + .and_then(|contract_addr| { + trace!(target: "secretstore", "Configuring for ACL checker contract from {}", contract_addr); + + Some(SecretStoreAclStorage::new(contract_addr)) + }) + } + if let Some(ref contract) = *contract { + let address = public_to_address(&public); + let do_call = |a, d| future::done(self.client.call_contract(BlockId::Latest, a, d)); + contract.check_permissions(do_call, address, document.clone()) + .map_err(|err| Error::Internal(err)) + .wait() + } else { + Err(Error::Internal("ACL checker contract is not configured".to_owned())) + } + } +} + +#[cfg(test)] +pub mod tests { + use std::collections::{HashMap, HashSet}; + use parking_lot::RwLock; + use types::all::{Error, DocumentAddress, Public}; + use super::AclStorage; + + #[derive(Default, Debug)] + /// Dummy ACL storage implementation + pub struct DummyAclStorage { + prohibited: RwLock>>, + } + + impl DummyAclStorage { + #[cfg(test)] + /// Prohibit given requestor access to given document + pub fn prohibit(&self, public: Public, document: DocumentAddress) { + self.prohibited.write() + .entry(public) + .or_insert_with(Default::default) + .insert(document); + } + } + + impl AclStorage for DummyAclStorage { + fn check(&self, public: &Public, document: &DocumentAddress) -> Result { + Ok(self.prohibited.read() + .get(public) + .map(|docs| !docs.contains(document)) + .unwrap_or(true)) + } } } diff --git a/secret_store/src/key_server.rs b/secret_store/src/key_server.rs index 553b49bfe..598f06338 100644 --- a/secret_store/src/key_server.rs +++ b/secret_store/src/key_server.rs @@ -147,7 +147,7 @@ mod tests { use std::sync::Arc; use ethcrypto; use ethkey::{self, Random, Generator}; - use acl_storage::DummyAclStorage; + use acl_storage::tests::DummyAclStorage; use key_storage::tests::DummyKeyStorage; use types::all::{ClusterConfiguration, NodeAddress, EncryptionConfiguration, DocumentEncryptedKey, DocumentKey}; use super::super::{RequestSignature, DocumentAddress}; diff --git a/secret_store/src/key_server_cluster/decryption_session.rs b/secret_store/src/key_server_cluster/decryption_session.rs index 71d8ad26f..652ed5c5a 100644 --- a/secret_store/src/key_server_cluster/decryption_session.rs +++ b/secret_store/src/key_server_cluster/decryption_session.rs @@ -220,7 +220,7 @@ impl SessionImpl { self.completed.notify_all(); }, // we can not decrypt data - SessionState::Failed => (), + SessionState::Failed => self.completed.notify_all(), // cannot reach other states _ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"), } @@ -285,7 +285,10 @@ impl SessionImpl { SessionState::WaitingForPartialDecryption => SessionImpl::start_waiting_for_partial_decryption(self.node().clone(), self.id.clone(), self.access_key.clone(), &self.cluster, &self.encrypted_data, &mut *data), // we can not have enough nodes for decryption - SessionState::Failed => Ok(()), + SessionState::Failed => { + self.completed.notify_all(); + Ok(()) + }, // cannot reach other states _ => unreachable!("process_initialization_response can change state to WaitingForPartialDecryption or Failed; checked that we are in WaitingForInitializationConfirm state above; qed"), } @@ -480,6 +483,7 @@ fn process_initialization_response(encrypted_data: &DocumentKeyShare, data: &mut // check if we still can receive enough confirmations to do a decryption? if encrypted_data.id_numbers.len() - data.rejected_nodes.len() < encrypted_data.threshold + 1 { + data.decrypted_secret = Some(Err(Error::AccessDenied)); data.state = SessionState::Failed; } }, @@ -503,7 +507,7 @@ fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants mod tests { use std::sync::Arc; use std::collections::BTreeMap; - use super::super::super::acl_storage::DummyAclStorage; + use super::super::super::acl_storage::tests::DummyAclStorage; use ethkey::{self, Random, Generator, Public, Secret}; use key_server_cluster::{NodeId, DocumentKeyShare, SessionId, Error}; use key_server_cluster::cluster::tests::DummyCluster; diff --git a/secret_store/src/key_server_cluster/mod.rs b/secret_store/src/key_server_cluster/mod.rs index 8b33e06f7..e889ef322 100644 --- a/secret_store/src/key_server_cluster/mod.rs +++ b/secret_store/src/key_server_cluster/mod.rs @@ -21,7 +21,7 @@ use ethcrypto; use super::types::all::DocumentAddress; pub use super::types::all::{NodeId, EncryptionConfiguration}; -pub use super::acl_storage::{AclStorage, DummyAclStorage}; +pub use super::acl_storage::AclStorage; pub use super::key_storage::{KeyStorage, DocumentKeyShare}; pub use super::serialization::{SerializableSignature, SerializableH256, SerializableSecret, SerializablePublic}; pub use self::cluster::{ClusterCore, ClusterConfiguration, ClusterClient}; @@ -30,6 +30,8 @@ pub use self::decryption_session::Session as DecryptionSession; #[cfg(test)] pub use super::key_storage::tests::DummyKeyStorage; +#[cfg(test)] +pub use super::acl_storage::tests::DummyAclStorage; pub type SessionId = DocumentAddress; @@ -72,6 +74,8 @@ pub enum Error { Serde(String), /// Key storage error. KeyStorage(String), + /// Acl storage error. + AccessDenied, } impl From for Error { @@ -110,6 +114,7 @@ impl fmt::Display for Error { Error::Io(ref e) => write!(f, "i/o error {}", e), Error::Serde(ref e) => write!(f, "serde error {}", e), Error::KeyStorage(ref e) => write!(f, "key storage error {}", e), + Error::AccessDenied => write!(f, "Access denied"), } } } diff --git a/secret_store/src/lib.rs b/secret_store/src/lib.rs index bbb8474d4..7de957991 100644 --- a/secret_store/src/lib.rs +++ b/secret_store/src/lib.rs @@ -32,11 +32,14 @@ extern crate tokio_service; extern crate tokio_proto; extern crate url; +extern crate ethabi; +extern crate ethcore; extern crate ethcore_devtools as devtools; extern crate ethcore_util as util; extern crate ethcore_ipc as ipc; extern crate ethcrypto; extern crate ethkey; +extern crate native_contracts; mod key_server_cluster; mod types; @@ -52,15 +55,18 @@ mod key_server; mod key_storage; mod serialization; +use std::sync::Arc; +use ethcore::client::Client; + pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public, Error, NodeAddress, ServiceConfiguration, ClusterConfiguration, EncryptionConfiguration}; pub use traits::{KeyServer}; /// Start new key server instance -pub fn start(config: ServiceConfiguration) -> Result, Error> { +pub fn start(client: Arc, config: ServiceConfiguration) -> Result, Error> { use std::sync::Arc; - let acl_storage = Arc::new(acl_storage::DummyAclStorage::default()); + let acl_storage = Arc::new(acl_storage::OnChainAclStorage::new(client)); let key_storage = Arc::new(key_storage::PersistentKeyStorage::new(&config)?); let key_server = key_server::KeyServerImpl::new(&config.cluster_config, acl_storage, key_storage)?; let listener = http_listener::KeyServerHttpListener::start(config, key_server)?; diff --git a/secret_store/src/types/all.rs b/secret_store/src/types/all.rs index 514b4eb6b..23e07e994 100644 --- a/secret_store/src/types/all.rs +++ b/secret_store/src/types/all.rs @@ -119,7 +119,10 @@ impl From for Error { impl From for Error { fn from(err: key_server_cluster::Error) -> Self { - Error::Internal(err.into()) + match err { + key_server_cluster::Error::AccessDenied => Error::AccessDenied, + _ => Error::Internal(err.into()), + } } }