On-chain ACL checker for secretstore (#5015)

* ECDKG protocol prototype

* added test for enc/dec math

* get rid of decryption_session

* added licenses

* fix after merge

* get rid of unused serde dependency

* doc

* decryption session [without commutative enc]

* failed_dec_session

* fixed tests

* added commen

* added more decryption session tests

* helper to localize an issue

* more computations to localize error

* decryption_session::SessionParams

* added tests for EC math to localize problem

* secretstore network transport

* encryption_session_works_over_network

* network errors processing

* connecting to KeyServer

* licenses

* get rid of debug println-s

* fixed secretstore args

* encryption results are stored in KS database

* decryption protocol works over network

* enc/dec Session traits

* fixing warnings

* fix after merge

* on-chain ACL checker proto

* fixed compilation

* fixed compilation

* finally fixed <odd>-of-N-scheme

* temporary commented test

* 1-of-N works in math

* scheme 1-of-N works

* updated AclStorage with real contract ABI

* remove unnecessary unsafety

* fixed grumbles

* wakeup on access denied

* fix after merge

* fix after merge

* moved contract to native-contracts lib
This commit is contained in:
Svyatoslav Nikolsky 2017-04-03 18:46:51 +03:00 committed by Gav Wood
parent ee4f9da385
commit abec06f50c
13 changed files with 140 additions and 32 deletions

3
Cargo.lock generated
View File

@ -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)",

View File

@ -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");
}

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
#![allow(unused_mut, unused_variables, unused_imports)]
//! Secret store ACL storage contract.
// TODO: testing.
include!(concat!(env!("OUT_DIR"), "/secretstore_acl_storage.rs"));

View File

@ -463,7 +463,9 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> 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

View File

@ -14,7 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
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<Client>,
}
#[cfg(not(feature = "secretstore"))]
@ -64,7 +66,7 @@ mod server {
impl KeyServer {
/// Create new key server
pub fn new(conf: Configuration, _deps: Dependencies) -> Result<Self, String> {
pub fn new(conf: Configuration, deps: Dependencies) -> Result<Self, String> {
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::<String>::into)?;
Ok(KeyServer {

View File

@ -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

View File

@ -14,38 +14,92 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
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<bool, Error>;
}
/// Dummy ACL storage implementation
#[derive(Default, Debug)]
pub struct DummyAclStorage {
prohibited: RwLock<HashMap<Public, HashSet<DocumentAddress>>>,
/// On-chain ACL storage implementation.
pub struct OnChainAclStorage {
/// Blockchain client.
client: Arc<Client>,
/// On-chain contract.
contract: Mutex<Option<SecretStoreAclStorage>>,
}
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<Client>) -> Self {
OnChainAclStorage {
client: client,
contract: Mutex::new(None),
}
}
}
impl AclStorage for DummyAclStorage {
impl AclStorage for OnChainAclStorage {
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> {
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<HashMap<Public, HashSet<DocumentAddress>>>,
}
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<bool, Error> {
Ok(self.prohibited.read()
.get(public)
.map(|docs| !docs.contains(document))
.unwrap_or(true))
}
}
}

View File

@ -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};

View File

@ -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;

View File

@ -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<ethkey::Error> 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"),
}
}
}

View File

@ -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<Box<KeyServer>, Error> {
pub fn start(client: Arc<Client>, config: ServiceConfiguration) -> Result<Box<KeyServer>, 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)?;

View File

@ -119,7 +119,10 @@ impl From<ethkey::Error> for Error {
impl From<key_server_cluster::Error> 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()),
}
}
}