diff --git a/Cargo.lock b/Cargo.lock index 83e643900..aecf8960f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,7 @@ dependencies = [ "ethcore-signer 1.7.0", "ethcore-stratum 1.7.0", "ethcore-util 1.7.0", + "ethkey 0.2.0", "ethsync 1.7.0", "evmbin 0.1.0", "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -633,6 +634,7 @@ dependencies = [ name = "ethcore-secretstore" version = "1.0.0" dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.7.0", "ethcore-ipc 1.7.0", "ethcore-ipc-codegen 1.7.0", @@ -640,9 +642,18 @@ dependencies = [ "ethcore-util 1.7.0", "ethcrypto 0.1.0", "ethkey 0.2.0", + "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "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)", "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)", + "serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-proto 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index b82490e88..1d0e27a71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-light = { path = "ethcore/light" } ethcore-logger = { path = "logger" } ethcore-stratum = { path = "stratum" } +ethkey = { path = "ethkey" } evmbin = { path = "evmbin" } rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } diff --git a/ethcrypto/src/lib.rs b/ethcrypto/src/lib.rs index a4d426b54..9c1352087 100644 --- a/ethcrypto/src/lib.rs +++ b/ethcrypto/src/lib.rs @@ -78,6 +78,12 @@ impl fmt::Display for Error { } } +impl Into for Error { + fn into(self) -> String { + format!("{}", self) + } +} + impl From for Error { fn from(e: SecpError) -> Self { Error::Secp(e) diff --git a/ethkey/src/keypair.rs b/ethkey/src/keypair.rs index b25664cd7..f883c4738 100644 --- a/ethkey/src/keypair.rs +++ b/ethkey/src/keypair.rs @@ -27,6 +27,7 @@ pub fn public_to_address(public: &Public) -> Address { result } +#[derive(Clone)] /// secp256k1 key pair pub struct KeyPair { secret: Secret, diff --git a/parity/main.rs b/parity/main.rs index 4b6dc6dab..1063571a9 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -53,6 +53,7 @@ extern crate ethcore_logger; extern crate ethcore_rpc; extern crate ethcore_signer; extern crate ethcore_util as util; +extern crate ethkey; extern crate ethsync; extern crate parity_hash_fetch as hash_fetch; extern crate parity_ipfs_api; diff --git a/parity/secretstore.rs b/parity/secretstore.rs index 79a209504..13d6d28d2 100644 --- a/parity/secretstore.rs +++ b/parity/secretstore.rs @@ -53,6 +53,7 @@ mod server { #[cfg(feature="secretstore")] mod server { + use ethkey; use ethcore_secretstore; use super::{Configuration, Dependencies}; @@ -64,10 +65,35 @@ mod server { impl KeyServer { /// Create new key server 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(), + ethkey::KeyPair::from_secret("5ab6ed2a52c33142380032c39a03a86b12eacb3fa4b53bc16d84f51318156f8c".parse().unwrap()).unwrap(), + ]; let conf = ethcore_secretstore::ServiceConfiguration { - listener_addr: conf.interface, - listener_port: conf.port, - data_path: conf.data_path, + listener_address: ethcore_secretstore::NodeAddress { + address: conf.interface.clone(), + port: conf.port, + }, + data_path: conf.data_path.clone(), + // TODO: this is test configuration. how it will be configured in production? + cluster_config: ethcore_secretstore::ClusterConfiguration { + threads: 4, + self_private: (***key_pairs[(conf.port - 8082) as usize].secret()).into(), + listener_address: ethcore_secretstore::NodeAddress { + address: conf.interface.clone(), + port: conf.port + 10, + }, + nodes: key_pairs.iter().enumerate().map(|(i, kp)| (kp.public().clone(), + ethcore_secretstore::NodeAddress { + address: conf.interface.clone(), + port: 8082 + 10 + (i as u16), + })).collect(), + allow_connecting_to_higher_nodes: true, + encryption_config: ethcore_secretstore::EncryptionConfiguration { + key_check_timeout_ms: 1000, + }, + } }; let key_server = ethcore_secretstore::start(conf) diff --git a/secret_store/Cargo.toml b/secret_store/Cargo.toml index eff7c1ef0..fba76804b 100644 --- a/secret_store/Cargo.toml +++ b/secret_store/Cargo.toml @@ -10,9 +10,19 @@ build = "build.rs" ethcore-ipc-codegen = { path = "../ipc/codegen" } [dependencies] +byteorder = "1.0" log = "0.3" parking_lot = "0.4" hyper = { version = "0.10", default-features = false } +serde = "0.9" +serde_json = "0.9" +serde_derive = "0.9" +futures = "0.1" +futures-cpupool = "0.1" +rustc-serialize = "0.3" +tokio-core = "0.1" +tokio-service = "0.1" +tokio-proto = "0.1" url = "1.0" ethcore-devtools = { path = "../devtools" } ethcore-util = { path = "../util" } diff --git a/secret_store/src/http_listener.rs b/secret_store/src/http_listener.rs index 92799d221..79fe71330 100644 --- a/secret_store/src/http_listener.rs +++ b/secret_store/src/http_listener.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::str::FromStr; use std::sync::Arc; use hyper::header; use hyper::uri::RequestUri; @@ -39,7 +38,9 @@ pub struct KeyServerHttpListener { enum Request { /// Invalid request Invalid, - /// Request encryption key of given document for given requestor + /// Generate encryption key. + GenerateDocumentKey(DocumentAddress, RequestSignature, usize), + /// Request encryption key of given document for given requestor. GetDocumentKey(DocumentAddress, RequestSignature), } @@ -63,9 +64,9 @@ impl KeyServerHttpListener where T: KeyServer + 'static { handler: shared_handler.clone(), }; - let listener_addr: &str = &format!("{}:{}", config.listener_addr, config.listener_port); - let http_server = HttpServer::http(&listener_addr).unwrap(); - let http_server = http_server.handle(handler).unwrap(); + let listener_addr: &str = &format!("{}:{}", config.listener_address.address, config.listener_address.port); + let http_server = HttpServer::http(&listener_addr).expect("cannot start HttpServer"); + let http_server = http_server.handle(handler).expect("cannot start HttpServer"); let listener = KeyServerHttpListener { _http_server: http_server, handler: shared_handler, @@ -75,6 +76,10 @@ impl KeyServerHttpListener where T: KeyServer + 'static { } impl KeyServer for KeyServerHttpListener where T: KeyServer + 'static { + fn generate_document_key(&self, signature: &RequestSignature, document: &DocumentAddress, threshold: usize) -> Result { + self.handler.key_server.generate_document_key(signature, document, threshold) + } + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result { self.handler.key_server.document_key(signature, document) } @@ -82,95 +87,103 @@ impl KeyServer for KeyServerHttpListener where T: KeyServer + 'static { impl HttpHandler for KeyServerHttpHandler where T: KeyServer + 'static { fn handle(&self, req: HttpRequest, mut res: HttpResponse) { - if req.method != HttpMethod::Get { - warn!(target: "secretstore", "Ignoring {}-request {}", req.method, req.uri); - *res.status_mut() = HttpStatusCode::NotFound; - return; - } - if req.headers.has::() { warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method, req.uri); *res.status_mut() = HttpStatusCode::NotFound; return; } - match req.uri { - RequestUri::AbsolutePath(ref path) => match parse_request(&path) { - Request::GetDocumentKey(document, signature) => { - let document_key = self.handler.key_server.document_key(&signature, &document) + let req_method = req.method.clone(); + let req_uri = req.uri.clone(); + match &req_uri { + &RequestUri::AbsolutePath(ref path) => match parse_request(&req_method, &path) { + Request::GenerateDocumentKey(document, signature, threshold) => { + return_document_key(req, res, self.handler.key_server.generate_document_key(&signature, &document, threshold) .map_err(|err| { - warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req.uri, err); + warn!(target: "secretstore", "GenerateDocumentKey request {} has failed with: {}", req_uri, err); err - }); - match document_key { - Ok(document_key) => { - let document_key = document_key.to_hex().into_bytes(); - res.headers_mut().set(header::ContentType::plaintext()); - if let Err(err) = res.send(&document_key) { - // nothing to do, but log error - warn!(target: "secretstore", "GetDocumentKey request {} response has failed with: {}", req.uri, err); - } - }, - Err(Error::BadSignature) => *res.status_mut() = HttpStatusCode::BadRequest, - Err(Error::AccessDenied) => *res.status_mut() = HttpStatusCode::Forbidden, - Err(Error::DocumentNotFound) => *res.status_mut() = HttpStatusCode::NotFound, - Err(Error::Database(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, - Err(Error::Internal(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, - } + })); + }, + Request::GetDocumentKey(document, signature) => { + return_document_key(req, res, self.handler.key_server.document_key(&signature, &document) + .map_err(|err| { + warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req_uri, err); + err + })); }, Request::Invalid => { - warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); + warn!(target: "secretstore", "Ignoring invalid {}-request {}", req_method, req_uri); *res.status_mut() = HttpStatusCode::BadRequest; }, }, _ => { - warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri); + warn!(target: "secretstore", "Ignoring invalid {}-request {}", req_method, req_uri); *res.status_mut() = HttpStatusCode::NotFound; }, }; } } -fn parse_request(uri_path: &str) -> Request { +fn return_document_key(req: HttpRequest, mut res: HttpResponse, document_key: Result) { + match document_key { + Ok(document_key) => { + let document_key = document_key.to_hex().into_bytes(); + res.headers_mut().set(header::ContentType::plaintext()); + if let Err(err) = res.send(&document_key) { + // nothing to do, but to log an error + warn!(target: "secretstore", "response to request {} has failed with: {}", req.uri, err); + } + }, + Err(Error::BadSignature) => *res.status_mut() = HttpStatusCode::BadRequest, + Err(Error::AccessDenied) => *res.status_mut() = HttpStatusCode::Forbidden, + Err(Error::DocumentNotFound) => *res.status_mut() = HttpStatusCode::NotFound, + Err(Error::Database(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, + Err(Error::Internal(_)) => *res.status_mut() = HttpStatusCode::InternalServerError, + } +} + +fn parse_request(method: &HttpMethod, uri_path: &str) -> Request { let uri_path = match percent_decode(uri_path.as_bytes()).decode_utf8() { Ok(path) => path, Err(_) => return Request::Invalid, }; let path: Vec = uri_path.trim_left_matches('/').split('/').map(Into::into).collect(); - if path.len() != 2 || path[0].is_empty() || path[1].is_empty() { + if path.len() < 2 || path[0].is_empty() || path[1].is_empty() { return Request::Invalid; } - let document = DocumentAddress::from_str(&path[0]); - let signature = RequestSignature::from_str(&path[1]); - match (document, signature) { - (Ok(document), Ok(signature)) => Request::GetDocumentKey(document, signature), + let args_len = path.len(); + let document = path[0].parse(); + let signature = path[1].parse(); + let threshold = (if args_len > 2 { &path[2] } else { "" }).parse(); + match (args_len, method, document, signature, threshold) { + (3, &HttpMethod::Post, Ok(document), Ok(signature), Ok(threshold)) => Request::GenerateDocumentKey(document, signature, threshold), + (2, &HttpMethod::Get, Ok(document), Ok(signature), _) => Request::GetDocumentKey(document, signature), _ => Request::Invalid, } } #[cfg(test)] mod tests { - use std::str::FromStr; - use super::super::RequestSignature; + use hyper::method::Method as HttpMethod; use super::{parse_request, Request}; #[test] fn parse_request_successful() { - assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), + assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), - RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); - assert_eq!(parse_request("/%30000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), + "a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01".parse().unwrap())); + assert_eq!(parse_request(&HttpMethod::Get, "/%30000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"), Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(), - RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap())); + "a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01".parse().unwrap())); } #[test] fn parse_request_failed() { - assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001"), Request::Invalid); - assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/"), Request::Invalid); - assert_eq!(parse_request("/a/b"), Request::Invalid); - assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01/0000000000000000000000000000000000000000000000000000000000000002"), Request::Invalid); + assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001"), Request::Invalid); + assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001/"), Request::Invalid); + assert_eq!(parse_request(&HttpMethod::Get, "/a/b"), Request::Invalid); + assert_eq!(parse_request(&HttpMethod::Get, "/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01/0000000000000000000000000000000000000000000000000000000000000002"), Request::Invalid); } } diff --git a/secret_store/src/key_server.rs b/secret_store/src/key_server.rs index 32ac48031..553b49bfe 100644 --- a/secret_store/src/key_server.rs +++ b/secret_store/src/key_server.rs @@ -14,42 +14,78 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::thread; +use std::sync::Arc; +use std::sync::mpsc; +use futures::{self, Future}; +use parking_lot::Mutex; +use tokio_core::reactor::Core; use ethcrypto; use ethkey; use super::acl_storage::AclStorage; use super::key_storage::KeyStorage; +use key_server_cluster::ClusterCore; use traits::KeyServer; -use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey}; +use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey, ClusterConfiguration}; +use key_server_cluster::{ClusterClient, ClusterConfiguration as NetClusterConfiguration}; /// Secret store key server implementation -pub struct KeyServerImpl { - acl_storage: T, - key_storage: U, +pub struct KeyServerImpl { + data: Arc>, } -impl KeyServerImpl where T: AclStorage, U: KeyStorage { +/// Secret store key server data. +pub struct KeyServerCore { + close: Option>, + handle: Option>, + cluster: Option>, +} + +impl KeyServerImpl { /// Create new key server instance - pub fn new(acl_storage: T, key_storage: U) -> Self { - KeyServerImpl { - acl_storage: acl_storage, - key_storage: key_storage, - } + pub fn new(config: &ClusterConfiguration, acl_storage: Arc, key_storage: Arc) -> Result { + Ok(KeyServerImpl { + data: Arc::new(Mutex::new(KeyServerCore::new(config, acl_storage, key_storage)?)), + }) + } + + #[cfg(test)] + /// Get cluster client reference. + pub fn cluster(&self) -> Arc { + self.data.lock().cluster.clone() + .expect("cluster can be None in test cfg only; test cfg is for correct tests; qed") } } -impl KeyServer for KeyServerImpl where T: AclStorage, U: KeyStorage { +impl KeyServer for KeyServerImpl { + fn generate_document_key(&self, signature: &RequestSignature, document: &DocumentAddress, threshold: usize) -> Result { + // recover requestor' public key from signature + let public = ethkey::recover(signature, document) + .map_err(|_| Error::BadSignature)?; + + // generate document key + let data = self.data.lock(); + let encryption_session = data.cluster.as_ref().expect("cluster can be None in test cfg only; test cfg is for correct tests; qed") + .new_encryption_session(document.clone(), threshold)?; + let document_key = encryption_session.wait()?; + + // encrypt document key with requestor public key + let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key) + .map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?; + Ok(document_key) + } + fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result { // recover requestor' public key from signature let public = ethkey::recover(signature, document) .map_err(|_| Error::BadSignature)?; - // check that requestor has access to the document - if !self.acl_storage.check(&public, document)? { - return Err(Error::AccessDenied); - } + // decrypt document key + let data = self.data.lock(); + let decryption_session = data.cluster.as_ref().expect("cluster can be None in test cfg only; test cfg is for correct tests; qed") + .new_decryption_session(document.clone(), signature.clone())?; + let document_key = decryption_session.wait()?; - // read unencrypted document key - let document_key = self.key_storage.get(document)?; // encrypt document key with requestor public key let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key) .map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?; @@ -57,68 +93,132 @@ impl KeyServer for KeyServerImpl where T: AclStorage, U: KeyStorage } } +impl KeyServerCore { + pub fn new(config: &ClusterConfiguration, acl_storage: Arc, key_storage: Arc) -> Result { + let config = NetClusterConfiguration { + threads: config.threads, + self_key_pair: ethkey::KeyPair::from_secret_slice(&config.self_private)?, + listen_address: (config.listener_address.address.clone(), config.listener_address.port), + nodes: config.nodes.iter() + .map(|(node_id, node_address)| (node_id.clone(), (node_address.address.clone(), node_address.port))) + .collect(), + allow_connecting_to_higher_nodes: config.allow_connecting_to_higher_nodes, + encryption_config: config.encryption_config.clone(), + acl_storage: acl_storage, + key_storage: key_storage, + }; + + let (stop, stopped) = futures::oneshot(); + let (tx, rx) = mpsc::channel(); + let handle = thread::spawn(move || { + let mut el = match Core::new() { + Ok(el) => el, + Err(e) => { + tx.send(Err(Error::Internal(format!("error initializing event loop: {}", e)))).expect("Rx is blocking upper thread."); + return; + }, + }; + + let cluster = ClusterCore::new(el.handle(), config); + let cluster_client = cluster.and_then(|c| c.run().map(|_| c.client())); + tx.send(cluster_client.map_err(Into::into)).expect("Rx is blocking upper thread."); + let _ = el.run(futures::empty().select(stopped)); + }); + let cluster = rx.recv().map_err(|e| Error::Internal(format!("error initializing event loop: {}", e)))??; + + Ok(KeyServerCore { + close: Some(stop), + handle: Some(handle), + cluster: Some(cluster), + }) + } +} + +impl Drop for KeyServerCore { + fn drop(&mut self) { + self.close.take().map(|v| v.send(())); + self.handle.take().map(|h| h.join()); + } +} + #[cfg(test)] mod tests { - use std::str::FromStr; + use std::time; + use std::sync::Arc; use ethcrypto; - use ethkey::{self, Secret}; + use ethkey::{self, Random, Generator}; use acl_storage::DummyAclStorage; - use key_storage::KeyStorage; use key_storage::tests::DummyKeyStorage; - use super::super::{Error, RequestSignature, DocumentAddress}; + use types::all::{ClusterConfiguration, NodeAddress, EncryptionConfiguration, DocumentEncryptedKey, DocumentKey}; + use super::super::{RequestSignature, DocumentAddress}; use super::{KeyServer, KeyServerImpl}; const DOCUMENT1: &'static str = "0000000000000000000000000000000000000000000000000000000000000001"; - const DOCUMENT2: &'static str = "0000000000000000000000000000000000000000000000000000000000000002"; - const KEY1: &'static str = "key1"; const PRIVATE1: &'static str = "03055e18a8434dcc9061cc1b81c4ef84dc7cf4574d755e52cdcf0c8898b25b11"; - const PUBLIC2: &'static str = "dfe62f56bb05fbd85b485bac749f3410309e24b352bac082468ce151e9ddb94fa7b5b730027fe1c7c5f3d5927621d269f91aceb5caa3c7fe944677a22f88a318"; - const PRIVATE2: &'static str = "0eb3816f4f705fa0fd952fb27b71b8c0606f09f4743b5b65cbc375bd569632f2"; - - fn create_key_server() -> KeyServerImpl { - let acl_storage = DummyAclStorage::default(); - let key_storage = DummyKeyStorage::default(); - key_storage.insert(DOCUMENT1.into(), KEY1.into()).unwrap(); - acl_storage.prohibit(PUBLIC2.into(), DOCUMENT1.into()); - KeyServerImpl::new(acl_storage, key_storage) - } fn make_signature(secret: &str, document: &'static str) -> RequestSignature { - let secret = Secret::from_str(secret).unwrap(); + let secret = secret.parse().unwrap(); let document: DocumentAddress = document.into(); ethkey::sign(&secret, &document).unwrap() } - #[test] - fn document_key_succeeds() { - let key_server = create_key_server(); - let signature = make_signature(PRIVATE1, DOCUMENT1); - let document_key = key_server.document_key(&signature, &DOCUMENT1.into()).unwrap(); - let document_key = ethcrypto::ecies::decrypt_single_message(&Secret::from_str(PRIVATE1).unwrap(), &document_key); - assert_eq!(document_key, Ok(KEY1.into())); + fn decrypt_document_key(secret: &str, document_key: DocumentEncryptedKey) -> DocumentKey { + let secret = secret.parse().unwrap(); + ethcrypto::ecies::decrypt_single_message(&secret, &document_key).unwrap() } #[test] - fn document_key_fails_when_bad_signature() { - let key_server = create_key_server(); - let signature = RequestSignature::default(); - let document_key = key_server.document_key(&signature, &DOCUMENT1.into()); - assert_eq!(document_key, Err(Error::BadSignature)); - } + fn document_key_generation_and_retrievement_works_over_network() { + //::util::log::init_log(); - #[test] - fn document_key_fails_when_acl_check_fails() { - let key_server = create_key_server(); - let signature = make_signature(PRIVATE2, DOCUMENT1); - let document_key = key_server.document_key(&signature, &DOCUMENT1.into()); - assert_eq!(document_key, Err(Error::AccessDenied)); - } + let num_nodes = 3; + let key_pairs: Vec<_> = (0..num_nodes).map(|_| Random.generate().unwrap()).collect(); + let configs: Vec<_> = (0..num_nodes).map(|i| ClusterConfiguration { + threads: 1, + self_private: (***key_pairs[i].secret()).into(), + listener_address: NodeAddress { + address: "127.0.0.1".into(), + port: 6060 + (i as u16), + }, + nodes: key_pairs.iter().enumerate().map(|(j, kp)| (kp.public().clone(), + NodeAddress { + address: "127.0.0.1".into(), + port: 6060 + (j as u16), + })).collect(), + allow_connecting_to_higher_nodes: false, + encryption_config: EncryptionConfiguration { + key_check_timeout_ms: 10, + }, + }).collect(); + let key_servers: Vec<_> = configs.into_iter().map(|cfg| + KeyServerImpl::new(&cfg, Arc::new(DummyAclStorage::default()), Arc::new(DummyKeyStorage::default())).unwrap() + ).collect(); - #[test] - fn document_key_fails_when_document_not_found() { - let key_server = create_key_server(); - let signature = make_signature(PRIVATE1, DOCUMENT2); - let document_key = key_server.document_key(&signature, &DOCUMENT2.into()); - assert_eq!(document_key, Err(Error::DocumentNotFound)); + // wait until connections are established + let start = time::Instant::now(); + loop { + if key_servers.iter().all(|ks| ks.cluster().cluster_state().connected.len() == num_nodes - 1) { + break; + } + if time::Instant::now() - start > time::Duration::from_millis(30000) { + panic!("connections are not established in 30000ms"); + } + } + + let test_cases = [0, 1, 2]; + for threshold in &test_cases { + // generate document key + // TODO: it is an error that we can regenerate key for the same DOCUMENT + let signature = make_signature(PRIVATE1, DOCUMENT1); + let generated_key = key_servers[0].generate_document_key(&signature, &DOCUMENT1.into(), *threshold).unwrap(); + let generated_key = decrypt_document_key(PRIVATE1, generated_key); + + // now let's try to retrieve key back + for key_server in key_servers.iter() { + let retrieved_key = key_server.document_key(&signature, &DOCUMENT1.into()).unwrap(); + let retrieved_key = decrypt_document_key(PRIVATE1, retrieved_key); + assert_eq!(retrieved_key, generated_key); + } + } } } diff --git a/secret_store/src/key_server_cluster/cluster.rs b/secret_store/src/key_server_cluster/cluster.rs index 5f6c99808..388a79aef 100644 --- a/secret_store/src/key_server_cluster/cluster.rs +++ b/secret_store/src/key_server_cluster/cluster.rs @@ -14,11 +14,42 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use key_server_cluster::{Error, NodeId}; -use key_server_cluster::message::Message; +use std::io; +use std::time; +use std::sync::Arc; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::collections::btree_map::Entry; +use std::net::{SocketAddr, IpAddr}; +use futures::{finished, failed, Future, Stream, BoxFuture}; +use futures_cpupool::CpuPool; +use parking_lot::{RwLock, Mutex}; +use tokio_core::io::IoFuture; +use tokio_core::reactor::{Handle, Remote, Timeout, Interval}; +use tokio_core::net::{TcpListener, TcpStream}; +use ethkey::{Secret, KeyPair, Signature, Random, Generator}; +use key_server_cluster::{Error, NodeId, SessionId, EncryptionConfiguration, AclStorage, KeyStorage}; +use key_server_cluster::message::{self, Message, ClusterMessage, EncryptionMessage, DecryptionMessage}; +use key_server_cluster::decryption_session::{SessionImpl as DecryptionSessionImpl, DecryptionSessionId, + SessionParams as DecryptionSessionParams, Session as DecryptionSession}; +use key_server_cluster::encryption_session::{SessionImpl as EncryptionSessionImpl, SessionState as EncryptionSessionState, + SessionParams as EncryptionSessionParams, Session as EncryptionSession}; +use key_server_cluster::io::{DeadlineStatus, ReadMessage, SharedTcpStream, read_encrypted_message, WriteMessage, write_encrypted_message}; +use key_server_cluster::net::{accept_connection as net_accept_connection, connect as net_connect, Connection as NetConnection}; + +pub type BoxedEmptyFuture = BoxFuture<(), ()>; + +/// Cluster interface for external clients. +pub trait ClusterClient: Send + Sync { + /// Get cluster state. + fn cluster_state(&self) -> ClusterState; + /// Start new encryption session. + fn new_encryption_session(&self, session_id: SessionId, threshold: usize) -> Result, Error>; + /// Start new decryption session. + fn new_decryption_session(&self, session_id: SessionId, requestor_signature: Signature) -> Result, Error>; +} /// Cluster access for single encryption/decryption participant. -pub trait Cluster { +pub trait Cluster: Send + Sync { /// Broadcast message to all other nodes. fn broadcast(&self, message: Message) -> Result<(), Error>; /// Send message to given node. @@ -27,13 +58,841 @@ pub trait Cluster { fn blacklist(&self, node: &NodeId); } +#[derive(Clone)] +/// Cluster initialization parameters. +pub struct ClusterConfiguration { + /// Number of threads reserved by cluster. + pub threads: usize, + /// Allow connecting to 'higher' nodes. + pub allow_connecting_to_higher_nodes: bool, + /// KeyPair this node holds. + pub self_key_pair: KeyPair, + /// Interface to listen to. + pub listen_address: (String, u16), + /// Cluster nodes. + pub nodes: BTreeMap, + /// Encryption session configuration. + pub encryption_config: EncryptionConfiguration, + /// Reference to key storage + pub key_storage: Arc, + /// Reference to ACL storage + pub acl_storage: Arc, +} + +/// Cluster state. +pub struct ClusterState { + /// Nodes, to which connections are established. + pub connected: BTreeSet, +} + +/// Network cluster implementation. +pub struct ClusterCore { + /// Handle to the event loop. + handle: Handle, + /// Listen address. + listen_address: SocketAddr, + /// Cluster data. + data: Arc, +} + +/// Network cluster client interface implementation. +pub struct ClusterClientImpl { + /// Cluster data. + data: Arc, +} + +/// Network cluster view. It is a communication channel, required in single session. +pub struct ClusterView { + core: Arc>, +} + +/// Cross-thread shareable cluster data. +pub struct ClusterData { + /// Cluster configuration. + config: ClusterConfiguration, + /// Handle to the event loop. + handle: Remote, + /// Handle to the cpu thread pool. + pool: CpuPool, + /// KeyPair this node holds. + self_key_pair: KeyPair, + /// Connections data. + connections: ClusterConnections, + /// Active sessions data. + sessions: ClusterSessions, +} + +/// Connections that are forming the cluster. +pub struct ClusterConnections { + /// Self node id. + pub self_node_id: NodeId, + /// All known other key servers. + pub nodes: BTreeMap, + /// Active connections to key servers. + pub connections: RwLock>>, +} + +/// Active sessions on this cluster. +pub struct ClusterSessions { + /// Self node id. + pub self_node_id: NodeId, + /// Reference to key storage + pub key_storage: Arc, + /// Reference to ACL storage + pub acl_storage: Arc, + /// Active encryption sessions. + pub encryption_sessions: RwLock>, + /// Active decryption sessions. + pub decryption_sessions: RwLock>, +} + +/// Encryption session and its message queue. +pub struct QueuedEncryptionSession { + /// Encryption session. + pub session: Arc, + /// Messages queue. + pub queue: VecDeque<(NodeId, EncryptionMessage)>, +} + +/// Decryption session and its message queue. +pub struct QueuedDecryptionSession { + /// Decryption session. + pub session: Arc, + /// Messages queue. + pub queue: VecDeque<(NodeId, DecryptionMessage)>, +} + +/// Cluster view core. +struct ClusterViewCore { + /// Cluster reference. + cluster: Arc, + /// Subset of nodes, required for this session. + nodes: BTreeSet, +} + +/// Connection to single node. +pub struct Connection { + /// Node id. + node_id: NodeId, + /// Node address. + node_address: SocketAddr, + /// Is inbound connection? + is_inbound: bool, + /// Tcp stream. + stream: SharedTcpStream, + /// Connection key. + key: Secret, + /// Last message time. + last_message_time: Mutex, +} + +impl ClusterCore { + pub fn new(handle: Handle, config: ClusterConfiguration) -> Result, Error> { + let listen_address = make_socket_address(&config.listen_address.0, config.listen_address.1)?; + let connections = ClusterConnections::new(&config)?; + let sessions = ClusterSessions::new(&config); + let data = ClusterData::new(&handle, config, connections, sessions); + + Ok(Arc::new(ClusterCore { + handle: handle, + listen_address: listen_address, + data: data, + })) + } + + /// Create new client interface. + pub fn client(&self) -> Arc { + Arc::new(ClusterClientImpl::new(self.data.clone())) + } + + #[cfg(test)] + /// Get cluster configuration. + pub fn config(&self) -> &ClusterConfiguration { + &self.data.config + } + + #[cfg(test)] + /// Get connection to given node. + pub fn connection(&self, node: &NodeId) -> Option> { + self.data.connection(node) + } + + /// Run cluster + pub fn run(&self) -> Result<(), Error> { + // try to connect to every other peer + ClusterCore::connect_disconnected_nodes(self.data.clone()); + + // schedule maintain procedures + ClusterCore::schedule_maintain(&self.handle, self.data.clone()); + + // start listening for incoming connections + self.handle.spawn(ClusterCore::listen(&self.handle, self.data.clone(), self.listen_address.clone())?); + + Ok(()) + } + + /// Connect to peer. + fn connect(data: Arc, node_address: SocketAddr) { + data.handle.clone().spawn(move |handle| { + data.pool.clone().spawn(ClusterCore::connect_future(handle, data, node_address)) + }) + } + + /// Connect to socket using given context and handle. + fn connect_future(handle: &Handle, data: Arc, node_address: SocketAddr) -> BoxedEmptyFuture { + let disconnected_nodes = data.connections.disconnected_nodes().keys().cloned().collect(); + net_connect(&node_address, handle, data.self_key_pair.clone(), disconnected_nodes) + .then(move |result| ClusterCore::process_connection_result(data, false, result)) + .then(|_| finished(())) + .boxed() + } + + /// Start listening for incoming connections. + fn listen(handle: &Handle, data: Arc, listen_address: SocketAddr) -> Result { + Ok(TcpListener::bind(&listen_address, &handle)? + .incoming() + .and_then(move |(stream, node_address)| { + ClusterCore::accept_connection(data.clone(), stream, node_address); + Ok(()) + }) + .for_each(|_| Ok(())) + .then(|_| finished(())) + .boxed()) + } + + /// Accept connection. + fn accept_connection(data: Arc, stream: TcpStream, node_address: SocketAddr) { + data.handle.clone().spawn(move |handle| { + data.pool.clone().spawn(ClusterCore::accept_connection_future(handle, data, stream, node_address)) + }) + } + + /// Accept connection future. + fn accept_connection_future(handle: &Handle, data: Arc, stream: TcpStream, node_address: SocketAddr) -> BoxedEmptyFuture { + let disconnected_nodes = data.connections.disconnected_nodes().keys().cloned().collect(); + net_accept_connection(node_address, stream, handle, data.self_key_pair.clone(), disconnected_nodes) + .then(move |result| ClusterCore::process_connection_result(data, true, result)) + .then(|_| finished(())) + .boxed() + } + + /// Schedule mainatain procedures. + fn schedule_maintain(handle: &Handle, data: Arc) { + // TODO: per-session timeouts (node can respond to messages, but ignore sessions messages) + let (d1, d2, d3) = (data.clone(), data.clone(), data.clone()); + let interval: BoxedEmptyFuture = Interval::new(time::Duration::new(10, 0), handle) + .expect("failed to create interval") + .and_then(move |_| Ok(trace!(target: "secretstore_net", "{}: executing maintain procedures", d1.self_key_pair.public()))) + .and_then(move |_| Ok(ClusterCore::keep_alive(d2.clone()))) + .and_then(move |_| Ok(ClusterCore::connect_disconnected_nodes(d3.clone()))) + .for_each(|_| Ok(())) + .then(|_| finished(())) + .boxed(); + + data.spawn(interval); + } + + /// Called for every incomming mesage. + fn process_connection_messages(data: Arc, connection: Arc) -> IoFuture> { + connection + .read_message() + .then(move |result| + match result { + Ok((_, Ok(message))) => { + ClusterCore::process_connection_message(data.clone(), connection.clone(), message); + // continue serving connection + data.spawn(ClusterCore::process_connection_messages(data.clone(), connection)); + finished(Ok(())).boxed() + }, + Ok((_, Err(err))) => { + warn!(target: "secretstore_net", "{}: protocol error {} when reading message from node {}", data.self_key_pair.public(), err, connection.node_id()); + // continue serving connection + data.spawn(ClusterCore::process_connection_messages(data.clone(), connection)); + finished(Err(err)).boxed() + }, + Err(err) => { + warn!(target: "secretstore_net", "{}: network error {} when reading message from node {}", data.self_key_pair.public(), err, connection.node_id()); + // close connection + data.connections.remove(connection.node_id(), connection.is_inbound()); + failed(err).boxed() + }, + } + ).boxed() + } + + /// Send keepalive messages to every othe node. + fn keep_alive(data: Arc) { + for connection in data.connections.active_connections() { + let last_message_diff = time::Instant::now() - connection.last_message_time(); + if last_message_diff > time::Duration::from_secs(60) { + data.connections.remove(connection.node_id(), connection.is_inbound()); + data.sessions.on_connection_timeout(connection.node_id()); + } + else if last_message_diff > time::Duration::from_secs(30) { + data.spawn(connection.send_message(Message::Cluster(ClusterMessage::KeepAlive(message::KeepAlive {})))); + } + } + } + + /// Try to connect to every disconnected node. + fn connect_disconnected_nodes(data: Arc) { + for (node_id, node_address) in data.connections.disconnected_nodes() { + if data.config.allow_connecting_to_higher_nodes || data.self_key_pair.public() < &node_id { + ClusterCore::connect(data.clone(), node_address); + } + } + } + + /// Process connection future result. + fn process_connection_result(data: Arc, is_inbound: bool, result: Result>, io::Error>) -> IoFuture> { + match result { + Ok(DeadlineStatus::Meet(Ok(connection))) => { + let connection = Connection::new(is_inbound, connection); + if data.connections.insert(connection.clone()) { + ClusterCore::process_connection_messages(data.clone(), connection) + } else { + finished(Ok(())).boxed() + } + }, + Ok(DeadlineStatus::Meet(Err(_))) => { + finished(Ok(())).boxed() + }, + Ok(DeadlineStatus::Timeout) => { + finished(Ok(())).boxed() + }, + Err(_) => { + // network error + finished(Ok(())).boxed() + }, + } + } + + /// Process single message from the connection. + fn process_connection_message(data: Arc, connection: Arc, message: Message) { + connection.set_last_message_time(time::Instant::now()); + trace!(target: "secretstore_net", "{}: processing message {} from {}", data.self_key_pair.public(), message, connection.node_id()); + match message { + Message::Encryption(message) => ClusterCore::process_encryption_message(data, connection, message), + Message::Decryption(message) => ClusterCore::process_decryption_message(data, connection, message), + Message::Cluster(message) => ClusterCore::process_cluster_message(data, connection, message), + } + } + + /// Process single encryption message from the connection. + fn process_encryption_message(data: Arc, connection: Arc, mut message: EncryptionMessage) { + let mut sender = connection.node_id().clone(); + let mut is_queued_message = false; + let session_id = message.session_id().clone(); + let key_check_timeout_ms = data.config.encryption_config.key_check_timeout_ms; + loop { + let result = match message { + EncryptionMessage::InitializeSession(ref message) => { + let mut connected_nodes = data.connections.connected_nodes(); + connected_nodes.insert(data.self_key_pair.public().clone()); + + let cluster = Arc::new(ClusterView::new(data.clone(), connected_nodes)); + let session_id: SessionId = message.session.clone().into(); + data.sessions.new_encryption_session(sender.clone(), session_id.clone(), cluster) + .and_then(|s| s.on_initialize_session(sender.clone(), message)) + }, + EncryptionMessage::ConfirmInitialization(ref message) => data.sessions.encryption_session(&*message.session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_confirm_initialization(sender.clone(), message)), + EncryptionMessage::CompleteInitialization(ref message) => data.sessions.encryption_session(&*message.session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_complete_initialization(sender.clone(), message)), + EncryptionMessage::KeysDissemination(ref message) => data.sessions.encryption_session(&*message.session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| { + // TODO: move this logic to session (or session connector) + let is_in_key_check_state = s.state() == EncryptionSessionState::KeyCheck; + let result = s.on_keys_dissemination(sender.clone(), message); + if !is_in_key_check_state && s.state() == EncryptionSessionState::KeyCheck { + let session = s.clone(); + let d = data.clone(); + data.handle.spawn(move |handle| + Timeout::new(time::Duration::new(key_check_timeout_ms / 1000, 0), handle) + .expect("failed to create timeout") + .and_then(move |_| { + if let Err(error) = session.start_key_generation_phase() { + session.on_session_error(d.self_key_pair.public().clone(), &message::SessionError { + session: session.id().clone().into(), + error: error.into(), + }); + } + Ok(()) + }) + .then(|_| finished(())) + ); + } + + result + }), + EncryptionMessage::Complaint(ref message) => data.sessions.encryption_session(&*message.session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_complaint(sender.clone(), message)), + EncryptionMessage::ComplaintResponse(ref message) => data.sessions.encryption_session(&*message.session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_complaint_response(sender.clone(), message)), + EncryptionMessage::PublicKeyShare(ref message) => data.sessions.encryption_session(&*message.session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_public_key_share(sender.clone(), message)), + EncryptionMessage::SessionError(ref message) => { + if let Some(s) = data.sessions.encryption_session(&*message.session) { + data.sessions.remove_encryption_session(s.id()); + s.on_session_error(sender.clone(), message); + } + Ok(()) + }, + EncryptionMessage::SessionCompleted(ref message) => data.sessions.encryption_session(&*message.session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| { + let result = s.on_session_completed(sender.clone(), message); + if result.is_ok() && s.state() == EncryptionSessionState::Finished { + data.sessions.remove_encryption_session(s.id()); + } + + result + }), + }; + + match result { + Err(Error::TooEarlyForRequest) => { + data.sessions.enqueue_encryption_message(&session_id, sender, message, is_queued_message); + break; + }, + Err(err) => { + warn!(target: "secretstore_net", "{}: error {} when processing message {} from node {}", data.self_key_pair.public(), err, message, sender); + if let Some(connection) = data.connections.get(&sender) { + data.spawn(connection.send_message(Message::Encryption(EncryptionMessage::SessionError(message::SessionError { + session: session_id.clone().into(), + error: format!("{:?}", err), + })))); + } + + if err != Error::InvalidSessionId { + data.sessions.remove_encryption_session(&session_id); + } + break; + }, + _ => { + match data.sessions.dequeue_encryption_message(&session_id) { + Some((msg_sender, msg)) => { + is_queued_message = true; + sender = msg_sender; + message = msg; + }, + None => break, + } + }, + } + } + } + + /// Process single decryption message from the connection. + fn process_decryption_message(data: Arc, connection: Arc, mut message: DecryptionMessage) { + let mut sender = connection.node_id().clone(); + let mut is_queued_message = false; + let session_id = message.session_id().clone(); + let sub_session_id = message.sub_session_id().clone(); + loop { + let result = match message { + DecryptionMessage::InitializeDecryptionSession(ref message) => { + let mut connected_nodes = data.connections.connected_nodes(); + connected_nodes.insert(data.self_key_pair.public().clone()); + + let cluster = Arc::new(ClusterView::new(data.clone(), connected_nodes)); + data.sessions.new_decryption_session(sender.clone(), session_id.clone(), sub_session_id.clone(), cluster) + .and_then(|s| s.on_initialize_session(sender.clone(), message)) + }, + DecryptionMessage::ConfirmDecryptionInitialization(ref message) => data.sessions.decryption_session(&*message.session, &*message.sub_session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_confirm_initialization(sender.clone(), message)), + DecryptionMessage::RequestPartialDecryption(ref message) => data.sessions.decryption_session(&*message.session, &*message.sub_session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_partial_decryption_requested(sender.clone(), message)), + DecryptionMessage::PartialDecryption(ref message) => data.sessions.decryption_session(&*message.session, &*message.sub_session) + .ok_or(Error::InvalidSessionId) + .and_then(|s| s.on_partial_decryption(sender.clone(), message)), + DecryptionMessage::DecryptionSessionError(ref message) => { + if let Some(s) = data.sessions.decryption_session(&*message.session, &*message.sub_session) { + data.sessions.remove_decryption_session(&session_id, &sub_session_id); + s.on_session_error(sender.clone(), message); + } + Ok(()) + }, + }; + + match result { + Err(Error::TooEarlyForRequest) => { + data.sessions.enqueue_decryption_message(&session_id, &sub_session_id, sender, message, is_queued_message); + break; + }, + Err(err) => { + if let Some(connection) = data.connections.get(&sender) { + data.spawn(connection.send_message(Message::Decryption(DecryptionMessage::DecryptionSessionError(message::DecryptionSessionError { + session: session_id.clone().into(), + sub_session: sub_session_id.clone().into(), + error: format!("{:?}", err), + })))); + } + + if err != Error::InvalidSessionId { + data.sessions.remove_decryption_session(&session_id, &sub_session_id); + } + break; + }, + _ => { + match data.sessions.dequeue_decryption_message(&session_id, &sub_session_id) { + Some((msg_sender, msg)) => { + is_queued_message = true; + sender = msg_sender; + message = msg; + }, + None => break, + } + }, + } + } + } + + /// Process single cluster message from the connection. + fn process_cluster_message(data: Arc, connection: Arc, message: ClusterMessage) { + match message { + ClusterMessage::KeepAlive(_) => data.spawn(connection.send_message(Message::Cluster(ClusterMessage::KeepAliveResponse(message::KeepAliveResponse {})))), + ClusterMessage::KeepAliveResponse(_) => (), + _ => warn!(target: "secretstore_net", "{}: received unexpected message {} from node {} at {}", data.self_key_pair.public(), message, connection.node_id(), connection.node_address()), + } + } +} + +impl ClusterConnections { + pub fn new(config: &ClusterConfiguration) -> Result { + let mut connections = ClusterConnections { + self_node_id: config.self_key_pair.public().clone(), + nodes: BTreeMap::new(), + connections: RwLock::new(BTreeMap::new()), + }; + + for (node_id, &(ref node_addr, node_port)) in config.nodes.iter().filter(|&(node_id, _)| node_id != config.self_key_pair.public()) { + let socket_address = make_socket_address(&node_addr, node_port)?; + connections.nodes.insert(node_id.clone(), socket_address); + } + + Ok(connections) + } + + pub fn cluster_state(&self) -> ClusterState { + ClusterState { + connected: self.connections.read().keys().cloned().collect(), + } + } + + pub fn get(&self, node: &NodeId) -> Option> { + self.connections.read().get(node).cloned() + } + + pub fn insert(&self, connection: Arc) -> bool { + let mut connections = self.connections.write(); + if connections.contains_key(connection.node_id()) { + // we have already connected to the same node + // the agreement is that node with lower id must establish connection to node with higher id + if (&self.self_node_id < connection.node_id() && connection.is_inbound()) + || (&self.self_node_id > connection.node_id() && !connection.is_inbound()) { + return false; + } + } + trace!(target: "secretstore_net", "{}: inserting connection to {} at {}", self.self_node_id, connection.node_id(), connection.node_address()); + connections.insert(connection.node_id().clone(), connection); + true + } + + pub fn remove(&self, node: &NodeId, is_inbound: bool) { + let mut connections = self.connections.write(); + if let Entry::Occupied(entry) = connections.entry(node.clone()) { + if entry.get().is_inbound() != is_inbound { + return; + } + + trace!(target: "secretstore_net", "{}: removing connection to {} at {}", self.self_node_id, entry.get().node_id(), entry.get().node_address()); + entry.remove_entry(); + } + } + + pub fn connected_nodes(&self) -> BTreeSet { + self.connections.read().keys().cloned().collect() + } + + pub fn active_connections(&self)-> Vec> { + self.connections.read().values().cloned().collect() + } + + pub fn disconnected_nodes(&self) -> BTreeMap { + let connections = self.connections.read(); + self.nodes.iter() + .filter(|&(node_id, _)| !connections.contains_key(node_id)) + .map(|(node_id, node_address)| (node_id.clone(), node_address.clone())) + .collect() + } +} + +impl ClusterSessions { + pub fn new(config: &ClusterConfiguration) -> Self { + ClusterSessions { + self_node_id: config.self_key_pair.public().clone(), + acl_storage: config.acl_storage.clone(), + key_storage: config.key_storage.clone(), + encryption_sessions: RwLock::new(BTreeMap::new()), + decryption_sessions: RwLock::new(BTreeMap::new()), + } + } + + pub fn new_encryption_session(&self, _master: NodeId, session_id: SessionId, cluster: Arc) -> Result, Error> { + let mut encryption_sessions = self.encryption_sessions.write(); + if encryption_sessions.contains_key(&session_id) { + return Err(Error::DuplicateSessionId); + } + + let session = Arc::new(EncryptionSessionImpl::new(EncryptionSessionParams { + id: session_id.clone(), + self_node_id: self.self_node_id.clone(), + key_storage: self.key_storage.clone(), + cluster: cluster, + })); + let encryption_session = QueuedEncryptionSession { + session: session.clone(), + queue: VecDeque::new() + }; + encryption_sessions.insert(session_id, encryption_session); + Ok(session) + } + + pub fn remove_encryption_session(&self, session_id: &SessionId) { + self.encryption_sessions.write().remove(session_id); + } + + pub fn encryption_session(&self, session_id: &SessionId) -> Option> { + self.encryption_sessions.read().get(session_id).map(|s| s.session.clone()) + } + + pub fn enqueue_encryption_message(&self, session_id: &SessionId, sender: NodeId, message: EncryptionMessage, is_queued_message: bool) { + self.encryption_sessions.write().get_mut(session_id) + .map(|session| if is_queued_message { session.queue.push_front((sender, message)) } + else { session.queue.push_back((sender, message)) }); + } + + pub fn dequeue_encryption_message(&self, session_id: &SessionId) -> Option<(NodeId, EncryptionMessage)> { + self.encryption_sessions.write().get_mut(session_id) + .and_then(|session| session.queue.pop_front()) + } + + pub fn new_decryption_session(&self, _master: NodeId, session_id: SessionId, sub_session_id: Secret, cluster: Arc) -> Result, Error> { + let mut decryption_sessions = self.decryption_sessions.write(); + let session_id = DecryptionSessionId::new(session_id, sub_session_id); + if decryption_sessions.contains_key(&session_id) { + return Err(Error::DuplicateSessionId); + } + + let session = Arc::new(DecryptionSessionImpl::new(DecryptionSessionParams { + id: session_id.id.clone(), + access_key: session_id.access_key.clone(), + self_node_id: self.self_node_id.clone(), + encrypted_data: self.key_storage.get(&session_id.id).map_err(|e| Error::KeyStorage(e.into()))?, + acl_storage: self.acl_storage.clone(), + cluster: cluster, + })?); + let decryption_session = QueuedDecryptionSession { + session: session.clone(), + queue: VecDeque::new() + }; + decryption_sessions.insert(session_id, decryption_session); + Ok(session) + } + + pub fn remove_decryption_session(&self, session_id: &SessionId, sub_session_id: &Secret) { + let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone()); + self.decryption_sessions.write().remove(&session_id); + } + + pub fn decryption_session(&self, session_id: &SessionId, sub_session_id: &Secret) -> Option> { + let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone()); + self.decryption_sessions.read().get(&session_id).map(|s| s.session.clone()) + } + + pub fn enqueue_decryption_message(&self, session_id: &SessionId, sub_session_id: &Secret, sender: NodeId, message: DecryptionMessage, is_queued_message: bool) { + let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone()); + self.decryption_sessions.write().get_mut(&session_id) + .map(|session| if is_queued_message { session.queue.push_front((sender, message)) } + else { session.queue.push_back((sender, message)) }); + } + + pub fn dequeue_decryption_message(&self, session_id: &SessionId, sub_session_id: &Secret) -> Option<(NodeId, DecryptionMessage)> { + let session_id = DecryptionSessionId::new(session_id.clone(), sub_session_id.clone()); + self.decryption_sessions.write().get_mut(&session_id) + .and_then(|session| session.queue.pop_front()) + } + + pub fn on_connection_timeout(&self, node_id: &NodeId) { + for encryption_session in self.encryption_sessions.read().values() { + encryption_session.session.on_session_timeout(node_id); + } + for decryption_session in self.decryption_sessions.read().values() { + decryption_session.session.on_session_timeout(node_id); + } + } +} + +impl ClusterData { + pub fn new(handle: &Handle, config: ClusterConfiguration, connections: ClusterConnections, sessions: ClusterSessions) -> Arc { + Arc::new(ClusterData { + handle: handle.remote().clone(), + pool: CpuPool::new(config.threads), + self_key_pair: config.self_key_pair.clone(), + connections: connections, + sessions: sessions, + config: config, + }) + } + + /// Get connection to given node. + pub fn connection(&self, node: &NodeId) -> Option> { + self.connections.get(node) + } + + /// Spawns a future using thread pool and schedules execution of it with event loop handle. + pub fn spawn(&self, f: F) where F: Future + Send + 'static, F::Item: Send + 'static, F::Error: Send + 'static { + let pool_work = self.pool.spawn(f); + self.handle.spawn(move |_handle| { + pool_work.then(|_| finished(())) + }) + } +} + +impl Connection { + pub fn new(is_inbound: bool, connection: NetConnection) -> Arc { + Arc::new(Connection { + node_id: connection.node_id, + node_address: connection.address, + is_inbound: is_inbound, + stream: connection.stream, + key: connection.key, + last_message_time: Mutex::new(time::Instant::now()), + }) + } + + pub fn is_inbound(&self) -> bool { + self.is_inbound + } + + pub fn node_id(&self) -> &NodeId { + &self.node_id + } + + pub fn last_message_time(&self) -> time::Instant { + *self.last_message_time.lock() + } + + pub fn set_last_message_time(&self, last_message_time: time::Instant) { + *self.last_message_time.lock() = last_message_time; + } + + pub fn node_address(&self) -> &SocketAddr { + &self.node_address + } + + pub fn send_message(&self, message: Message) -> WriteMessage { + write_encrypted_message(self.stream.clone(), &self.key, message) + } + + pub fn read_message(&self) -> ReadMessage { + read_encrypted_message(self.stream.clone(), self.key.clone()) + } +} + +impl ClusterView { + pub fn new(cluster: Arc, nodes: BTreeSet) -> Self { + ClusterView { + core: Arc::new(Mutex::new(ClusterViewCore { + cluster: cluster, + nodes: nodes, + })), + } + } +} + +impl Cluster for ClusterView { + fn broadcast(&self, message: Message) -> Result<(), Error> { + let core = self.core.lock(); + for node in core.nodes.iter().filter(|n| *n != core.cluster.self_key_pair.public()) { + let connection = core.cluster.connection(node).ok_or(Error::NodeDisconnected)?; + core.cluster.spawn(connection.send_message(message.clone())) + } + Ok(()) + } + + fn send(&self, to: &NodeId, message: Message) -> Result<(), Error> { + let core = self.core.lock(); + let connection = core.cluster.connection(to).ok_or(Error::NodeDisconnected)?; + core.cluster.spawn(connection.send_message(message)); + Ok(()) + } + + fn blacklist(&self, _node: &NodeId) { + // TODO: unimplemented!() + } +} + +impl ClusterClientImpl { + pub fn new(data: Arc) -> Self { + ClusterClientImpl { + data: data, + } + } +} + +impl ClusterClient for ClusterClientImpl { + fn cluster_state(&self) -> ClusterState { + self.data.connections.cluster_state() + } + + fn new_encryption_session(&self, session_id: SessionId, threshold: usize) -> Result, Error> { + let mut connected_nodes = self.data.connections.connected_nodes(); + connected_nodes.insert(self.data.self_key_pair.public().clone()); + + let cluster = Arc::new(ClusterView::new(self.data.clone(), connected_nodes.clone())); + let session = self.data.sessions.new_encryption_session(self.data.self_key_pair.public().clone(), session_id, cluster)?; + session.initialize(threshold, connected_nodes)?; + Ok(session) + } + + fn new_decryption_session(&self, session_id: SessionId, requestor_signature: Signature) -> Result, Error> { + let mut connected_nodes = self.data.connections.connected_nodes(); + connected_nodes.insert(self.data.self_key_pair.public().clone()); + + let access_key = Random.generate()?.secret().clone(); + let cluster = Arc::new(ClusterView::new(self.data.clone(), connected_nodes.clone())); + let session = self.data.sessions.new_decryption_session(self.data.self_key_pair.public().clone(), session_id, access_key, cluster)?; + session.initialize(requestor_signature)?; + Ok(session) + } +} + +fn make_socket_address(address: &str, port: u16) -> Result { + let ip_address: IpAddr = address.parse().map_err(|_| Error::InvalidNodeAddress)?; + Ok(SocketAddr::new(ip_address, port)) +} + #[cfg(test)] pub mod tests { + use std::sync::Arc; + use std::time; use std::collections::VecDeque; use parking_lot::Mutex; - use key_server_cluster::{NodeId, Error}; + use tokio_core::reactor::Core; + use ethkey::{Random, Generator}; + use key_server_cluster::{NodeId, Error, EncryptionConfiguration, DummyAclStorage, DummyKeyStorage}; use key_server_cluster::message::Message; - use key_server_cluster::cluster::Cluster; + use key_server_cluster::cluster::{Cluster, ClusterCore, ClusterConfiguration}; #[derive(Debug)] pub struct DummyCluster { @@ -87,4 +946,61 @@ pub mod tests { fn blacklist(&self, _node: &NodeId) { } } + + pub fn loop_until(core: &mut Core, timeout: time::Duration, predicate: F) where F: Fn() -> bool { + let start = time::Instant::now(); + loop { + core.turn(Some(time::Duration::from_millis(1))); + if predicate() { + break; + } + + if time::Instant::now() - start > timeout { + panic!("no result in {:?}", timeout); + } + } + } + + pub fn all_connections_established(cluster: &Arc) -> bool { + cluster.config().nodes.keys() + .filter(|p| *p != cluster.config().self_key_pair.public()) + .all(|p| cluster.connection(p).is_some()) + } + + pub fn make_clusters(core: &Core, ports_begin: u16, num_nodes: usize) -> Vec> { + let key_pairs: Vec<_> = (0..num_nodes).map(|_| Random.generate().unwrap()).collect(); + let cluster_params: Vec<_> = (0..num_nodes).map(|i| ClusterConfiguration { + threads: 1, + self_key_pair: key_pairs[i].clone(), + listen_address: ("127.0.0.1".to_owned(), ports_begin + i as u16), + nodes: key_pairs.iter().enumerate() + .map(|(j, kp)| (kp.public().clone(), ("127.0.0.1".into(), ports_begin + j as u16))) + .collect(), + allow_connecting_to_higher_nodes: false, + encryption_config: EncryptionConfiguration { + key_check_timeout_ms: 10, + }, + key_storage: Arc::new(DummyKeyStorage::default()), + acl_storage: Arc::new(DummyAclStorage::default()), + }).collect(); + let clusters: Vec<_> = cluster_params.into_iter().enumerate() + .map(|(_, params)| ClusterCore::new(core.handle(), params).unwrap()) + .collect(); + + clusters + } + + pub fn run_clusters(clusters: &[Arc]) { + for cluster in clusters { + cluster.run().unwrap(); + } + } + + #[test] + fn cluster_connects_to_other_nodes() { + let mut core = Core::new().unwrap(); + let clusters = make_clusters(&core, 6010, 3); + run_clusters(&clusters); + loop_until(&mut core, time::Duration::from_millis(300), || clusters.iter().all(all_connections_established)); + } } diff --git a/secret_store/src/key_server_cluster/decryption_session.rs b/secret_store/src/key_server_cluster/decryption_session.rs index d4160851e..71d8ad26f 100644 --- a/secret_store/src/key_server_cluster/decryption_session.rs +++ b/secret_store/src/key_server_cluster/decryption_session.rs @@ -14,15 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::cmp::{Ord, PartialOrd, Ordering}; use std::collections::{BTreeSet, BTreeMap}; use std::sync::Arc; -use parking_lot::Mutex; +use parking_lot::{Mutex, Condvar}; use ethkey::{self, Secret, Public, Signature}; -use key_server_cluster::{Error, AclStorage, EncryptedData, NodeId, SessionId}; +use key_server_cluster::{Error, AclStorage, DocumentKeyShare, NodeId, SessionId}; use key_server_cluster::cluster::Cluster; use key_server_cluster::math; -use key_server_cluster::message::{Message, InitializeDecryptionSession, ConfirmDecryptionInitialization, - RequestPartialDecryption, PartialDecryption}; +use key_server_cluster::message::{Message, DecryptionMessage, InitializeDecryptionSession, ConfirmDecryptionInitialization, + RequestPartialDecryption, PartialDecryption, DecryptionSessionError}; + +/// Decryption session API. +pub trait Session: Send + Sync + 'static { + /// Wait until session is completed. Returns distributely restored secret key. + fn wait(&self) -> Result; +} /// Distributed decryption session. /// Based on "ECDKG: A Distributed Key Generation Protocol Based on Elliptic Curve Discrete Logarithm" paper: @@ -32,7 +39,7 @@ use key_server_cluster::message::{Message, InitializeDecryptionSession, ConfirmD /// 2) ACL check: all nodes which have received the request are querying ACL-contract to check if requestor has access to the document /// 3) partial decryption: every node which has succussfully checked access for the requestor do a partial decryption /// 4) decryption: master node receives all partial decryptions of the secret and restores the secret -pub struct Session { +pub struct SessionImpl { /// Encryption session id. id: SessionId, /// Decryption session access key. @@ -40,25 +47,36 @@ pub struct Session { /// Public identifier of this node. self_node_id: NodeId, /// Encrypted data. - encrypted_data: EncryptedData, + encrypted_data: DocumentKeyShare, /// ACL storate to check access to the resource. acl_storage: Arc, /// Cluster which allows this node to send messages to other nodes in the cluster. cluster: Arc, + /// SessionImpl completion condvar. + completed: Condvar, /// Mutable session data. data: Mutex, } -/// Session creation parameters -pub struct SessionParams { - /// Session identifier. +/// Decryption session Id. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DecryptionSessionId { + /// Encryption session id. pub id: SessionId, - /// Session access key. + /// Decryption session access key. + pub access_key: Secret, +} + +/// SessionImpl creation parameters +pub struct SessionParams { + /// SessionImpl identifier. + pub id: SessionId, + /// SessionImpl access key. pub access_key: Secret, /// Id of node, on which this session is running. pub self_node_id: Public, - /// Encrypted data (result of running encryption_session::Session). - pub encrypted_data: EncryptedData, + /// Encrypted data (result of running encryption_session::SessionImpl). + pub encrypted_data: DocumentKeyShare, /// ACL storage. pub acl_storage: Arc, /// Cluster @@ -91,16 +109,11 @@ struct SessionData { /// === Values, filled during final decryption === /// Decrypted secret - decrypted_secret: Option, -} - -#[derive(Debug)] -struct NodeData { - /// Node-generated shadow point. - shadow_point: Option, + decrypted_secret: Option>, } #[derive(Debug, Clone, PartialEq)] +/// Decryption session data. pub enum SessionState { /// Every node starts in this state. WaitingForInitialization, @@ -116,18 +129,19 @@ pub enum SessionState { Failed, } -impl Session { +impl SessionImpl { /// Create new decryption session. pub fn new(params: SessionParams) -> Result { check_encrypted_data(¶ms.self_node_id, ¶ms.encrypted_data)?; - Ok(Session { + Ok(SessionImpl { id: params.id, access_key: params.access_key, self_node_id: params.self_node_id, encrypted_data: params.encrypted_data, acl_storage: params.acl_storage, cluster: params.cluster, + completed: Condvar::new(), data: Mutex::new(SessionData { state: SessionState::WaitingForInitialization, master: None, @@ -146,19 +160,22 @@ impl Session { &self.self_node_id } + #[cfg(test)] /// Get this session access key. pub fn access_key(&self) -> &Secret { &self.access_key } + #[cfg(test)] /// Get current session state. pub fn state(&self) -> SessionState { self.data.lock().state.clone() } + #[cfg(test)] /// Get decrypted secret pub fn decrypted_secret(&self) -> Option { - self.data.lock().decrypted_secret.clone() + self.data.lock().decrypted_secret.clone().and_then(|r| r.ok()) } /// Initialize decryption session. @@ -188,15 +205,20 @@ impl Session { // not enough nodes => pass initialization message to all other nodes SessionState::WaitingForInitializationConfirm => { for node in self.encrypted_data.id_numbers.keys().filter(|n| *n != self.node()) { - self.cluster.send(node, Message::InitializeDecryptionSession(InitializeDecryptionSession { - session: self.id.clone(), - sub_session: self.access_key.clone(), - requestor_signature: requestor_signature.clone(), - }))?; + self.cluster.send(node, Message::Decryption(DecryptionMessage::InitializeDecryptionSession(InitializeDecryptionSession { + session: self.id.clone().into(), + sub_session: self.access_key.clone().into(), + requestor_signature: requestor_signature.clone().into(), + })))?; } }, // we can decrypt data on our own - SessionState::WaitingForPartialDecryption => unimplemented!(), + SessionState::WaitingForPartialDecryption => { + data.confirmed_nodes.insert(self.node().clone()); + SessionImpl::start_waiting_for_partial_decryption(self.node().clone(), self.id.clone(), self.access_key.clone(), &self.cluster, &self.encrypted_data, &mut *data)?; + SessionImpl::do_decryption(self.access_key.clone(), &self.encrypted_data, &mut *data)?; + self.completed.notify_all(); + }, // we can not decrypt data SessionState::Failed => (), // cannot reach other states @@ -207,9 +229,9 @@ impl Session { } /// When session initialization message is received. - pub fn on_initialize_session(&self, sender: NodeId, message: InitializeDecryptionSession) -> Result<(), Error> { - debug_assert!(self.id == message.session); - debug_assert!(self.access_key == message.sub_session); + pub fn on_initialize_session(&self, sender: NodeId, message: &InitializeDecryptionSession) -> Result<(), Error> { + debug_assert!(self.id == *message.session); + debug_assert!(self.access_key == *message.sub_session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); @@ -230,17 +252,17 @@ impl Session { // respond to master node data.master = Some(sender.clone()); - self.cluster.send(&sender, Message::ConfirmDecryptionInitialization(ConfirmDecryptionInitialization { - session: self.id.clone(), - sub_session: self.access_key.clone(), + self.cluster.send(&sender, Message::Decryption(DecryptionMessage::ConfirmDecryptionInitialization(ConfirmDecryptionInitialization { + session: self.id.clone().into(), + sub_session: self.access_key.clone().into(), is_confirmed: is_requestor_allowed_to_read, - })) + }))) } /// When session initialization confirmation message is reeived. - pub fn on_confirm_initialization(&self, sender: NodeId, message: ConfirmDecryptionInitialization) -> Result<(), Error> { - debug_assert!(self.id == message.session); - debug_assert!(self.access_key == message.sub_session); + pub fn on_confirm_initialization(&self, sender: NodeId, message: &ConfirmDecryptionInitialization) -> Result<(), Error> { + debug_assert!(self.id == *message.session); + debug_assert!(self.access_key == *message.sub_session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); @@ -260,26 +282,8 @@ impl Session { // we do not yet have enough nodes for decryption SessionState::WaitingForInitializationConfirm => Ok(()), // we have enough nodes for decryption - SessionState::WaitingForPartialDecryption => { - let confirmed_nodes: BTreeSet<_> = data.confirmed_nodes.clone(); - for node in data.confirmed_nodes.iter().filter(|n| n != &self.node()) { - self.cluster.send(node, Message::RequestPartialDecryption(RequestPartialDecryption { - session: self.id.clone(), - sub_session: self.access_key.clone(), - nodes: confirmed_nodes.clone(), - }))?; - } - - assert!(data.confirmed_nodes.remove(self.node())); - - let shadow_point = { - let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryption follows initialization; qed"); - do_partial_decryption(self.node(), &requestor, &data.confirmed_nodes, &self.access_key, &self.encrypted_data)? - }; - data.shadow_points.insert(self.node().clone(), shadow_point); - - Ok(()) - }, + 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(()), // cannot reach other states @@ -288,9 +292,9 @@ impl Session { } /// When partial decryption is requested. - pub fn on_partial_decryption_requested(&self, sender: NodeId, message: RequestPartialDecryption) -> Result<(), Error> { - debug_assert!(self.id == message.session); - debug_assert!(self.access_key == message.sub_session); + pub fn on_partial_decryption_requested(&self, sender: NodeId, message: &RequestPartialDecryption) -> Result<(), Error> { + debug_assert!(self.id == *message.session); + debug_assert!(self.access_key == *message.sub_session); debug_assert!(&sender != self.node()); // check message @@ -311,13 +315,13 @@ impl Session { // calculate shadow point let shadow_point = { let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryptionRequest follows initialization; qed"); - do_partial_decryption(self.node(), &requestor, &message.nodes, &self.access_key, &self.encrypted_data)? + do_partial_decryption(self.node(), &requestor, &message.nodes.iter().cloned().map(Into::into).collect(), &self.access_key, &self.encrypted_data)? }; - self.cluster.send(&sender, Message::PartialDecryption(PartialDecryption { - session: self.id.clone(), - sub_session: self.access_key.clone(), - shadow_point: shadow_point, - }))?; + self.cluster.send(&sender, Message::Decryption(DecryptionMessage::PartialDecryption(PartialDecryption { + session: self.id.clone().into(), + sub_session: self.access_key.clone().into(), + shadow_point: shadow_point.into(), + })))?; // update sate data.state = SessionState::Finished; @@ -326,9 +330,9 @@ impl Session { } /// When partial decryption is received. - pub fn on_partial_decryption(&self, sender: NodeId, message: PartialDecryption) -> Result<(), Error> { - debug_assert!(self.id == message.session); - debug_assert!(self.access_key == message.sub_session); + pub fn on_partial_decryption(&self, sender: NodeId, message: &PartialDecryption) -> Result<(), Error> { + debug_assert!(self.id == *message.session); + debug_assert!(self.access_key == *message.sub_session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); @@ -341,24 +345,113 @@ impl Session { if !data.confirmed_nodes.remove(&sender) { return Err(Error::InvalidStateForRequest); } - data.shadow_points.insert(sender, message.shadow_point); + data.shadow_points.insert(sender, message.shadow_point.clone().into()); // check if we have enough shadow points to decrypt the secret if data.shadow_points.len() != self.encrypted_data.threshold + 1 { return Ok(()); } + SessionImpl::do_decryption(self.access_key.clone(), &self.encrypted_data, &mut *data)?; + self.completed.notify_all(); + + Ok(()) + } + + /// When error has occured on another node. + pub fn on_session_error(&self, sender: NodeId, message: &DecryptionSessionError) { + warn!("{}: decryption session error: {:?} from {}", self.node(), message, sender); + let mut data = self.data.lock(); + data.state = SessionState::Failed; + data.decrypted_secret = Some(Err(Error::Io(message.error.clone()))); + self.completed.notify_all(); + } + + /// When session timeout has occured. + pub fn on_session_timeout(&self, _node: &NodeId) { + warn!("{}: decryption session timeout", self.node()); + let mut data = self.data.lock(); + // TODO: check that node is a part of decryption process + data.state = SessionState::Failed; + data.decrypted_secret = Some(Err(Error::Io("session expired".into()))); + self.completed.notify_all(); + } + + fn start_waiting_for_partial_decryption(self_node_id: NodeId, session_id: SessionId, access_key: Secret, cluster: &Arc, encrypted_data: &DocumentKeyShare, data: &mut SessionData) -> Result<(), Error> { + let confirmed_nodes: BTreeSet<_> = data.confirmed_nodes.clone(); + for node in data.confirmed_nodes.iter().filter(|n| n != &&self_node_id) { + cluster.send(node, Message::Decryption(DecryptionMessage::RequestPartialDecryption(RequestPartialDecryption { + session: session_id.clone().into(), + sub_session: access_key.clone().into(), + nodes: confirmed_nodes.iter().cloned().map(Into::into).collect(), + })))?; + } + + assert!(data.confirmed_nodes.remove(&self_node_id)); + + let shadow_point = { + let requestor = data.requestor.as_ref().expect("requestor public is filled during initialization; WaitingForPartialDecryption follows initialization; qed"); + do_partial_decryption(&self_node_id, &requestor, &data.confirmed_nodes, &access_key, &encrypted_data)? + }; + data.shadow_points.insert(self_node_id.clone(), shadow_point); + + Ok(()) + } + + fn do_decryption(access_key: Secret, encrypted_data: &DocumentKeyShare, data: &mut SessionData) -> Result<(), Error> { // decrypt the secret using shadow points let joint_shadow_point = math::compute_joint_shadow_point(data.shadow_points.values())?; - let decrypted_secret = math::decrypt_with_joint_shadow(&self.access_key, &self.encrypted_data.encrypted_point, &joint_shadow_point)?; - data.decrypted_secret = Some(decrypted_secret); + let decrypted_secret = math::decrypt_with_joint_shadow(encrypted_data.threshold, &access_key, &encrypted_data.encrypted_point, &joint_shadow_point)?; + data.decrypted_secret = Some(Ok(decrypted_secret)); + + // switch to completed state data.state = SessionState::Finished; Ok(()) } } -fn check_encrypted_data(self_node_id: &Public, encrypted_data: &EncryptedData) -> Result<(), Error> { +impl Session for SessionImpl { + fn wait(&self) -> Result { + let mut data = self.data.lock(); + if !data.decrypted_secret.is_some() { + self.completed.wait(&mut data); + } + + data.decrypted_secret.as_ref() + .expect("checked above or waited for completed; completed is only signaled when decrypted_secret.is_some(); qed") + .clone() + } +} + +impl DecryptionSessionId { + /// Create new decryption session Id. + pub fn new(session_id: SessionId, sub_session_id: Secret) -> Self { + DecryptionSessionId { + id: session_id, + access_key: sub_session_id, + } + } +} + +impl PartialOrd for DecryptionSessionId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + + +impl Ord for DecryptionSessionId { + fn cmp(&self, other: &Self) -> Ordering { + match self.id.cmp(&other.id) { + Ordering::Equal => self.access_key.cmp(&other.access_key), + r @ _ => r, + } + } +} + + +fn check_encrypted_data(self_node_id: &Public, encrypted_data: &DocumentKeyShare) -> Result<(), Error> { use key_server_cluster::encryption_session::{check_cluster_nodes, check_threshold}; let nodes = encrypted_data.id_numbers.keys().cloned().collect(); @@ -368,7 +461,7 @@ fn check_encrypted_data(self_node_id: &Public, encrypted_data: &EncryptedData) - Ok(()) } -fn process_initialization_response(encrypted_data: &EncryptedData, data: &mut SessionData, node: &NodeId, check_result: bool) -> Result<(), Error> { +fn process_initialization_response(encrypted_data: &DocumentKeyShare, data: &mut SessionData, node: &NodeId, check_result: bool) -> Result<(), Error> { if !data.requested_nodes.remove(node) { return Err(Error::InvalidMessage); } @@ -395,7 +488,7 @@ fn process_initialization_response(encrypted_data: &EncryptedData, data: &mut Se Ok(()) } -fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants: &BTreeSet, access_key: &Secret, encrypted_data: &EncryptedData) -> Result { +fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants: &BTreeSet, access_key: &Secret, encrypted_data: &DocumentKeyShare) -> Result { let node_id_number = &encrypted_data.id_numbers[node]; let node_secret_share = &encrypted_data.secret_share; let other_id_numbers = participants.iter() @@ -409,43 +502,42 @@ fn do_partial_decryption(node: &NodeId, _requestor_public: &Public, participants #[cfg(test)] mod tests { use std::sync::Arc; - use std::str::FromStr; use std::collections::BTreeMap; use super::super::super::acl_storage::DummyAclStorage; use ethkey::{self, Random, Generator, Public, Secret}; - use key_server_cluster::{NodeId, EncryptedData, SessionId, Error}; + use key_server_cluster::{NodeId, DocumentKeyShare, SessionId, Error}; use key_server_cluster::cluster::tests::DummyCluster; - use key_server_cluster::decryption_session::{Session, SessionParams, SessionState}; - use key_server_cluster::message::{self, Message}; + use key_server_cluster::decryption_session::{SessionImpl, SessionParams, SessionState}; + use key_server_cluster::message::{self, Message, DecryptionMessage}; const SECRET_PLAIN: &'static str = "d2b57ae7619e070af0af6bc8c703c0cd27814c54d5d6a999cacac0da34ede279ca0d9216e85991029e54e2f0c92ee0bd30237725fa765cbdbfc4529489864c5f"; - fn prepare_decryption_sessions() -> (Vec>, Vec>, Vec) { + fn prepare_decryption_sessions() -> (Vec>, Vec>, Vec) { // prepare encrypted data + cluster configuration for scheme 4-of-5 let session_id = SessionId::default(); let access_key = Random.generate().unwrap().secret().clone(); - let secret_shares = vec![ - Secret::from_str("834cb736f02d9c968dfaf0c37658a1d86ff140554fc8b59c9fdad5a8cf810eec").unwrap(), - Secret::from_str("5a3c1d90fafafa66bb808bcc464354a98b05e6b2c95b5f609d4511cdd1b17a0b").unwrap(), - Secret::from_str("71bf61e7848e08e3a8486c308ce521bdacfebcf9116a0151447eb301f3a2d0e9").unwrap(), - Secret::from_str("80c0e5e2bea66fa9b2e07f7ce09630a9563e8242446d5ee63221feb09c4338f4").unwrap(), - Secret::from_str("c06546b5669877ba579ca437a5602e89425c53808c708d44ccd6afcaa4610fad").unwrap(), + let secret_shares: Vec = vec![ + "834cb736f02d9c968dfaf0c37658a1d86ff140554fc8b59c9fdad5a8cf810eec".parse().unwrap(), + "5a3c1d90fafafa66bb808bcc464354a98b05e6b2c95b5f609d4511cdd1b17a0b".parse().unwrap(), + "71bf61e7848e08e3a8486c308ce521bdacfebcf9116a0151447eb301f3a2d0e9".parse().unwrap(), + "80c0e5e2bea66fa9b2e07f7ce09630a9563e8242446d5ee63221feb09c4338f4".parse().unwrap(), + "c06546b5669877ba579ca437a5602e89425c53808c708d44ccd6afcaa4610fad".parse().unwrap(), ]; let id_numbers: Vec<(NodeId, Secret)> = vec![ ("b486d3840218837b035c66196ecb15e6b067ca20101e11bd5e626288ab6806ecc70b8307012626bd512bad1559112d11d21025cef48cc7a1d2f3976da08f36c8".into(), - Secret::from_str("281b6bf43cb86d0dc7b98e1b7def4a80f3ce16d28d2308f934f116767306f06c").unwrap()), + "281b6bf43cb86d0dc7b98e1b7def4a80f3ce16d28d2308f934f116767306f06c".parse().unwrap()), ("1395568277679f7f583ab7c0992da35f26cde57149ee70e524e49bdae62db3e18eb96122501e7cbb798b784395d7bb5a499edead0706638ad056d886e56cf8fb".into(), - Secret::from_str("00125d85a05e5e63e214cb60fe63f132eec8a103aa29266b7e6e6c5b7597230b").unwrap()), + "00125d85a05e5e63e214cb60fe63f132eec8a103aa29266b7e6e6c5b7597230b".parse().unwrap()), ("99e82b163b062d55a64085bacfd407bb55f194ba5fb7a1af9c34b84435455520f1372e0e650a4f91aed0058cb823f62146ccb5599c8d13372c300dea866b69fc".into(), - Secret::from_str("f43ac0fba42a5b6ed95707d2244659e89ba877b1c9b82c0d0a9dcf834e80fc62").unwrap()), + "f43ac0fba42a5b6ed95707d2244659e89ba877b1c9b82c0d0a9dcf834e80fc62".parse().unwrap()), ("7e05df9dd077ec21ed4bc45c9fe9e0a43d65fa4be540630de615ced5e95cf5c3003035eb713317237d7667feeeb64335525158f5f7411f67aca9645169ea554c".into(), - Secret::from_str("5a324938dfb2516800487d25ab7289ba8ec38811f77c3df602e4e65e3c9acd9f").unwrap()), + "5a324938dfb2516800487d25ab7289ba8ec38811f77c3df602e4e65e3c9acd9f".parse().unwrap()), ("321977760d1d8e15b047a309e4c7fe6f355c10bb5a06c68472b676926427f69f229024fa2692c10da167d14cdc77eb95d0fce68af0a0f704f0d3db36baa83bb2".into(), - Secret::from_str("12cf422d50002d04e52bd4906fd7f5f235f051ca36abfe37e061f8da248008d8").unwrap()), + "12cf422d50002d04e52bd4906fd7f5f235f051ca36abfe37e061f8da248008d8".parse().unwrap()), ]; let common_point: Public = "6962be696e1bcbba8e64cc7fddf140f854835354b5804f3bb95ae5a2799130371b589a131bd39699ac7174ccb35fc4342dab05331202209582fc8f3a40916ab0".into(); let encrypted_point: Public = "b07031982bde9890e12eff154765f03c56c3ab646ad47431db5dd2d742a9297679c4c65b998557f8008469afd0c43d40b6c5f6c6a1c7354875da4115237ed87a".into(); - let encrypted_datas: Vec<_> = (0..5).map(|i| EncryptedData { + let encrypted_datas: Vec<_> = (0..5).map(|i| DocumentKeyShare { threshold: 3, id_numbers: id_numbers.clone().into_iter().collect(), secret_share: secret_shares[i].clone(), @@ -454,7 +546,7 @@ mod tests { }).collect(); let acl_storages: Vec<_> = (0..5).map(|_| Arc::new(DummyAclStorage::default())).collect(); let clusters: Vec<_> = (0..5).map(|i| Arc::new(DummyCluster::new(id_numbers.iter().nth(i).clone().unwrap().0))).collect(); - let sessions: Vec<_> = (0..5).map(|i| Session::new(SessionParams { + let sessions: Vec<_> = (0..5).map(|i| SessionImpl::new(SessionParams { id: session_id.clone(), access_key: access_key.clone(), self_node_id: id_numbers.iter().nth(i).clone().unwrap().0, @@ -466,11 +558,11 @@ mod tests { (clusters, acl_storages, sessions) } - fn do_messages_exchange(clusters: &[Arc], sessions: &[Session]) { + fn do_messages_exchange(clusters: &[Arc], sessions: &[SessionImpl]) { do_messages_exchange_until(clusters, sessions, |_, _, _| false); } - fn do_messages_exchange_until(clusters: &[Arc], sessions: &[Session], mut cond: F) where F: FnMut(&NodeId, &NodeId, &Message) -> bool { + fn do_messages_exchange_until(clusters: &[Arc], sessions: &[SessionImpl], mut cond: F) where F: FnMut(&NodeId, &NodeId, &Message) -> bool { while let Some((from, to, message)) = clusters.iter().filter_map(|c| c.take_message().map(|(to, msg)| (c.node(), to, msg))).next() { let session = &sessions[sessions.iter().position(|s| s.node() == &to).unwrap()]; if cond(&from, &to, &message) { @@ -478,10 +570,10 @@ mod tests { } match message { - Message::InitializeDecryptionSession(message) => session.on_initialize_session(from, message).unwrap(), - Message::ConfirmDecryptionInitialization(message) => session.on_confirm_initialization(from, message).unwrap(), - Message::RequestPartialDecryption(message) => session.on_partial_decryption_requested(from, message).unwrap(), - Message::PartialDecryption(message) => session.on_partial_decryption(from, message).unwrap(), + Message::Decryption(DecryptionMessage::InitializeDecryptionSession(message)) => session.on_initialize_session(from, &message).unwrap(), + Message::Decryption(DecryptionMessage::ConfirmDecryptionInitialization(message)) => session.on_confirm_initialization(from, &message).unwrap(), + Message::Decryption(DecryptionMessage::RequestPartialDecryption(message)) => session.on_partial_decryption_requested(from, &message).unwrap(), + Message::Decryption(DecryptionMessage::PartialDecryption(message)) => session.on_partial_decryption(from, &message).unwrap(), _ => panic!("unexpected"), } } @@ -492,11 +584,11 @@ mod tests { let mut nodes = BTreeMap::new(); let self_node_id = Random.generate().unwrap().public().clone(); nodes.insert(self_node_id, Random.generate().unwrap().secret().clone()); - match Session::new(SessionParams { + match SessionImpl::new(SessionParams { id: SessionId::default(), access_key: Random.generate().unwrap().secret().clone(), self_node_id: self_node_id.clone(), - encrypted_data: EncryptedData { + encrypted_data: DocumentKeyShare { threshold: 0, id_numbers: nodes, secret_share: Random.generate().unwrap().secret().clone(), @@ -517,11 +609,11 @@ mod tests { let self_node_id = Random.generate().unwrap().public().clone(); nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()); nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()); - match Session::new(SessionParams { + match SessionImpl::new(SessionParams { id: SessionId::default(), access_key: Random.generate().unwrap().secret().clone(), self_node_id: self_node_id.clone(), - encrypted_data: EncryptedData { + encrypted_data: DocumentKeyShare { threshold: 0, id_numbers: nodes, secret_share: Random.generate().unwrap().secret().clone(), @@ -542,11 +634,11 @@ mod tests { let self_node_id = Random.generate().unwrap().public().clone(); nodes.insert(self_node_id.clone(), Random.generate().unwrap().secret().clone()); nodes.insert(Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()); - match Session::new(SessionParams { + match SessionImpl::new(SessionParams { id: SessionId::default(), access_key: Random.generate().unwrap().secret().clone(), self_node_id: self_node_id.clone(), - encrypted_data: EncryptedData { + encrypted_data: DocumentKeyShare { threshold: 2, id_numbers: nodes, secret_share: Random.generate().unwrap().secret().clone(), @@ -572,70 +664,70 @@ mod tests { fn fails_to_accept_initialization_when_already_initialized() { let (_, _, sessions) = prepare_decryption_sessions(); assert_eq!(sessions[0].initialize(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).unwrap(), ()); - assert_eq!(sessions[0].on_initialize_session(sessions[1].node().clone(), message::InitializeDecryptionSession { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), + assert_eq!(sessions[0].on_initialize_session(sessions[1].node().clone(), &message::InitializeDecryptionSession { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(), }).unwrap_err(), Error::InvalidStateForRequest); } #[test] fn fails_to_partial_decrypt_if_not_waiting() { let (_, _, sessions) = prepare_decryption_sessions(); - assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), + assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), &message::InitializeDecryptionSession { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(), }).unwrap(), ()); - assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(), + assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), &message::RequestPartialDecryption { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + nodes: sessions.iter().map(|s| s.node().clone().into()).take(4).collect(), }).unwrap(), ()); - assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(), + assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), &message::RequestPartialDecryption { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + nodes: sessions.iter().map(|s| s.node().clone().into()).take(4).collect(), }).unwrap_err(), Error::InvalidStateForRequest); } #[test] fn fails_to_partial_decrypt_if_requested_by_slave() { let (_, _, sessions) = prepare_decryption_sessions(); - assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), + assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), &message::InitializeDecryptionSession { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(), }).unwrap(), ()); - assert_eq!(sessions[1].on_partial_decryption_requested(sessions[2].node().clone(), message::RequestPartialDecryption { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - nodes: sessions.iter().map(|s| s.node().clone()).take(4).collect(), + assert_eq!(sessions[1].on_partial_decryption_requested(sessions[2].node().clone(), &message::RequestPartialDecryption { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + nodes: sessions.iter().map(|s| s.node().clone().into()).take(4).collect(), }).unwrap_err(), Error::InvalidMessage); } #[test] fn fails_to_partial_decrypt_if_wrong_number_of_nodes_participating() { let (_, _, sessions) = prepare_decryption_sessions(); - assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), message::InitializeDecryptionSession { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap(), + assert_eq!(sessions[1].on_initialize_session(sessions[0].node().clone(), &message::InitializeDecryptionSession { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + requestor_signature: ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap().into(), }).unwrap(), ()); - assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), message::RequestPartialDecryption { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - nodes: sessions.iter().map(|s| s.node().clone()).take(2).collect(), + assert_eq!(sessions[1].on_partial_decryption_requested(sessions[0].node().clone(), &message::RequestPartialDecryption { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + nodes: sessions.iter().map(|s| s.node().clone().into()).take(2).collect(), }).unwrap_err(), Error::InvalidMessage); } #[test] fn fails_to_accept_partial_decrypt_if_not_waiting() { let (_, _, sessions) = prepare_decryption_sessions(); - assert_eq!(sessions[0].on_partial_decryption(sessions[1].node().clone(), message::PartialDecryption { - session: SessionId::default(), - sub_session: sessions[0].access_key().clone(), - shadow_point: Random.generate().unwrap().public().clone(), + assert_eq!(sessions[0].on_partial_decryption(sessions[1].node().clone(), &message::PartialDecryption { + session: SessionId::default().into(), + sub_session: sessions[0].access_key().clone().into(), + shadow_point: Random.generate().unwrap().public().clone().into(), }).unwrap_err(), Error::InvalidStateForRequest); } @@ -647,7 +739,7 @@ mod tests { let mut pd_from = None; let mut pd_msg = None; do_messages_exchange_until(&clusters, &sessions, |from, _, msg| match msg { - &Message::PartialDecryption(ref msg) => { + &Message::Decryption(DecryptionMessage::PartialDecryption(ref msg)) => { pd_from = Some(from.clone()); pd_msg = Some(msg.clone()); true @@ -655,8 +747,8 @@ mod tests { _ => false, }); - assert_eq!(sessions[0].on_partial_decryption(pd_from.clone().unwrap(), pd_msg.clone().unwrap()).unwrap(), ()); - assert_eq!(sessions[0].on_partial_decryption(pd_from.unwrap(), pd_msg.unwrap()).unwrap_err(), Error::InvalidStateForRequest); + assert_eq!(sessions[0].on_partial_decryption(pd_from.clone().unwrap(), &pd_msg.clone().unwrap()).unwrap(), ()); + assert_eq!(sessions[0].on_partial_decryption(pd_from.unwrap(), &pd_msg.unwrap()).unwrap_err(), Error::InvalidStateForRequest); } #[test] @@ -704,4 +796,9 @@ mod tests { // 3) 0 sessions have decrypted key value assert!(sessions.iter().all(|s| s.decrypted_secret().is_none())); } + + #[test] + fn decryption_session_works_over_network() { + // TODO + } } diff --git a/secret_store/src/key_server_cluster/encryption_session.rs b/secret_store/src/key_server_cluster/encryption_session.rs index 6f5705a73..beca00443 100644 --- a/secret_store/src/key_server_cluster/encryption_session.rs +++ b/secret_store/src/key_server_cluster/encryption_session.rs @@ -17,13 +17,22 @@ use std::collections::{BTreeSet, BTreeMap, VecDeque}; use std::fmt::{Debug, Formatter, Error as FmtError}; use std::sync::Arc; -use parking_lot::Mutex; +use parking_lot::{Condvar, Mutex}; use ethkey::{Public, Secret}; -use key_server_cluster::{Error, NodeId, SessionId}; +use key_server_cluster::{Error, NodeId, SessionId, KeyStorage, DocumentKeyShare}; use key_server_cluster::math; use key_server_cluster::cluster::Cluster; -use key_server_cluster::message::{Message, InitializeSession, ConfirmInitialization, CompleteInitialization, - KeysDissemination, Complaint, ComplaintResponse, PublicKeyShare}; +use key_server_cluster::message::{Message, EncryptionMessage, InitializeSession, ConfirmInitialization, CompleteInitialization, + KeysDissemination, Complaint, ComplaintResponse, PublicKeyShare, SessionError, SessionCompleted}; + +/// Encryption session API. +pub trait Session: Send + Sync + 'static { + #[cfg(test)] + /// Get joint public key (if it is known). + fn joint_public_key(&self) -> Option; + /// Wait until session is completed. Returns distributely generated secret key. + fn wait(&self) -> Result; +} /// Encryption (distributed key generation) session. /// Based on "ECDKG: A Distributed Key Generation Protocol Based on Elliptic Curve Discrete Logarithm" paper: @@ -34,17 +43,34 @@ use key_server_cluster::message::{Message, InitializeSession, ConfirmInitializat /// 3) key verification (KV): all nodes are checking values, received for other nodes and complaining if keys are wrong /// 4) key check phase (KC): nodes are processing complaints, received from another nodes /// 5) key generation phase (KG): nodes are exchanging with information, enough to generate joint public key -pub struct Session { +/// 6) encryption phase: master node generates secret key, encrypts it using joint public && broadcasts encryption result +pub struct SessionImpl { /// Unique session id. id: SessionId, /// Public identifier of this node. self_node_id: NodeId, + /// Key storage. + key_storage: Arc, /// Cluster which allows this node to send messages to other nodes in the cluster. cluster: Arc, + /// SessionImpl completion condvar. + completed: Condvar, /// Mutable session data. data: Mutex, } +/// SessionImpl creation parameters +pub struct SessionParams { + /// SessionImpl identifier. + pub id: SessionId, + /// Id of node, on which this session is running. + pub self_node_id: Public, + /// Key storage. + pub key_storage: Arc, + /// Cluster + pub cluster: Arc, +} + #[derive(Debug)] /// Mutable data of encryption (distributed key generation) session. struct SessionData { @@ -74,7 +100,9 @@ struct SessionData { /// === Values, filled when DKG session is completed successfully === /// Jointly generated public key, which can be used to encrypt secret. Public. - joint_public: Option, + joint_public: Option>, + /// Secret point. + secret_point: Option>, } #[derive(Debug, Clone)] @@ -95,13 +123,17 @@ struct NodeData { /// Public values, which have been received from this node. pub publics: Option>, - /// === Values, filled during KC phase === + // === Values, filled during KC phase === /// Nodes, complaining against this node. pub complaints: BTreeSet, - /// === Values, filled during KG phase === + // === Values, filled during KG phase === /// Public share, which has been received from this node. pub public_share: Option, + + // === Values, filled during encryption phase === + /// Flags marking that node has confirmed session completion (encryption data is stored). + pub completion_confirmed: bool, } #[derive(Debug, Clone, PartialEq)] @@ -139,6 +171,10 @@ pub enum SessionState { /// Node is waiting for joint public key share to be received from every other node. WaitingForPublicKeyShare, + // === Encryption phase states === + /// Node is waiting for session completion/session completion confirmation. + WaitingForEncryptionConfirmation, + // === Final states of the session === /// Joint public key generation is completed. Finished, @@ -146,13 +182,15 @@ pub enum SessionState { Failed, } -impl Session { +impl SessionImpl { /// Create new encryption session. - pub fn new(id: SessionId, self_node_id: Public, cluster: Arc) -> Self { - Session { - id: id, - self_node_id: self_node_id, - cluster: cluster, + pub fn new(params: SessionParams) -> Self { + SessionImpl { + id: params.id, + self_node_id: params.self_node_id, + key_storage: params.key_storage, + cluster: params.cluster, + completed: Condvar::new(), data: Mutex::new(SessionData { state: SessionState::WaitingForInitialization, master: None, @@ -162,10 +200,16 @@ impl Session { secret_coeff: None, secret_share: None, joint_public: None, + secret_point: None, }), } } + /// Get this session Id. + pub fn id(&self) -> &SessionId { + &self.id + } + /// Get this node Id. pub fn node(&self) -> &NodeId { &self.self_node_id @@ -176,11 +220,6 @@ impl Session { self.data.lock().state.clone() } - /// Get joint public key. - pub fn joint_public_key(&self) -> Option { - self.data.lock().joint_public.clone() - } - #[cfg(test)] /// Get derived point. pub fn derived_point(&self) -> Option { @@ -220,15 +259,15 @@ impl Session { // start initialization let derived_point = math::generate_random_point()?; - self.cluster.send(&next_node, Message::InitializeSession(InitializeSession { - session: self.id.clone(), - derived_point: derived_point, - })) + self.cluster.send(&next_node, Message::Encryption(EncryptionMessage::InitializeSession(InitializeSession { + session: self.id.clone().into(), + derived_point: derived_point.into(), + }))) } /// When session initialization message is received. - pub fn on_initialize_session(&self, sender: NodeId, mut message: InitializeSession) -> Result<(), Error> { - debug_assert!(self.id == message.session); + pub fn on_initialize_session(&self, sender: NodeId, message: &InitializeSession) -> Result<(), Error> { + debug_assert!(self.id == *message.session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); @@ -239,13 +278,14 @@ impl Session { } // update derived point with random scalar - math::update_random_point(&mut message.derived_point)?; + let mut derived_point = message.derived_point.clone().into(); + math::update_random_point(&mut derived_point)?; // send confirmation back to master node - self.cluster.send(&sender, Message::ConfirmInitialization(ConfirmInitialization { - session: self.id.clone(), - derived_point: message.derived_point, - }))?; + self.cluster.send(&sender, Message::Encryption(EncryptionMessage::ConfirmInitialization(ConfirmInitialization { + session: self.id.clone().into(), + derived_point: derived_point.into(), + })))?; // update state data.master = Some(sender); @@ -255,8 +295,8 @@ impl Session { } /// When session initialization confirmation message is reeived. - pub fn on_confirm_initialization(&self, sender: NodeId, mut message: ConfirmInitialization) -> Result<(), Error> { - debug_assert!(self.id == message.session); + pub fn on_confirm_initialization(&self, sender: NodeId, message: &ConfirmInitialization) -> Result<(), Error> { + debug_assert!(self.id == *message.session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); @@ -277,25 +317,26 @@ impl Session { // proceed message match next_receiver { Some(next_receiver) => { - return self.cluster.send(&next_receiver, Message::InitializeSession(InitializeSession { - session: self.id.clone(), - derived_point: message.derived_point, - })); + return self.cluster.send(&next_receiver, Message::Encryption(EncryptionMessage::InitializeSession(InitializeSession { + session: self.id.clone().into(), + derived_point: message.derived_point.clone().into(), + }))); }, None => { // update point once again to make sure that derived point is not generated by last node - math::update_random_point(&mut message.derived_point)?; + let mut derived_point = message.derived_point.clone().into(); + math::update_random_point(&mut derived_point)?; // remember derived point - data.derived_point = Some(message.derived_point.clone()); + data.derived_point = Some(derived_point.clone().into()); // broadcast derived point && other session paraeters to every other node - self.cluster.broadcast(Message::CompleteInitialization(CompleteInitialization { - session: self.id.clone(), - nodes: data.nodes.iter().map(|(id, data)| (id.clone(), data.id_number.clone())).collect(), + self.cluster.broadcast(Message::Encryption(EncryptionMessage::CompleteInitialization(CompleteInitialization { + session: self.id.clone().into(), + nodes: data.nodes.iter().map(|(id, data)| (id.clone().into(), data.id_number.clone().into())).collect(), threshold: data.threshold.expect("threshold is filled in initialization phase; KD phase follows initialization phase; qed"), - derived_point: message.derived_point, - }))?; + derived_point: derived_point.into(), + })))?; }, } @@ -305,12 +346,12 @@ impl Session { } /// When session initialization completion message is received. - pub fn on_complete_initialization(&self, sender: NodeId, message: CompleteInitialization) -> Result<(), Error> { - debug_assert!(self.id == message.session); + pub fn on_complete_initialization(&self, sender: NodeId, message: &CompleteInitialization) -> Result<(), Error> { + debug_assert!(self.id == *message.session); debug_assert!(&sender != self.node()); // check message - let nodes_ids = message.nodes.keys().cloned().collect(); + let nodes_ids = message.nodes.keys().cloned().map(Into::into).collect(); check_cluster_nodes(self.node(), &nodes_ids)?; check_threshold(message.threshold, &nodes_ids)?; @@ -326,8 +367,8 @@ impl Session { // remember passed data data.threshold = Some(message.threshold); - data.derived_point = Some(message.derived_point); - data.nodes = message.nodes.into_iter().map(|(id, number)| (id, NodeData::with_id_number(number))).collect(); + data.derived_point = Some(message.derived_point.clone().into()); + data.nodes = message.nodes.iter().map(|(id, number)| (id.clone().into(), NodeData::with_id_number(number.clone().into()))).collect(); // now it is time for keys dissemination (KD) phase drop(data); @@ -335,17 +376,20 @@ impl Session { } /// When keys dissemination message is received. - pub fn on_keys_dissemination(&self, sender: NodeId, message: KeysDissemination) -> Result<(), Error> { - debug_assert!(self.id == message.session); + pub fn on_keys_dissemination(&self, sender: NodeId, message: &KeysDissemination) -> Result<(), Error> { + debug_assert!(self.id == *message.session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); - debug_assert!(data.nodes.contains_key(&sender)); // check state if data.state != SessionState::WaitingForKeysDissemination { - return Err(Error::InvalidStateForRequest); + match data.state { + SessionState::WaitingForInitializationComplete => return Err(Error::TooEarlyForRequest), + _ => return Err(Error::InvalidStateForRequest), + } } + debug_assert!(data.nodes.contains_key(&sender)); // check message let threshold = data.threshold.expect("threshold is filled in initialization phase; KD phase follows initialization phase; qed"); @@ -360,9 +404,9 @@ impl Session { return Err(Error::InvalidStateForRequest); } - node_data.secret1 = Some(message.secret1); - node_data.secret2 = Some(message.secret2); - node_data.publics = Some(message.publics); + node_data.secret1 = Some(message.secret1.clone().into()); + node_data.secret2 = Some(message.secret2.clone().into()); + node_data.publics = Some(message.publics.iter().cloned().map(Into::into).collect()); } // check if we have received keys from every other node @@ -382,10 +426,10 @@ impl Session { if !is_key_verification_ok { node_data.complaints.insert(self.node().clone()); - self.cluster.broadcast(Message::Complaint(Complaint { - session: self.id.clone(), - against: node_id.clone(), - }))?; + self.cluster.broadcast(Message::Encryption(EncryptionMessage::Complaint(Complaint { + session: self.id.clone().into(), + against: node_id.clone().into(), + })))?; } } @@ -396,8 +440,8 @@ impl Session { } /// When complaint is received. - pub fn on_complaint(&self, sender: NodeId, message: Complaint) -> Result<(), Error> { - debug_assert!(self.id == message.session); + pub fn on_complaint(&self, sender: NodeId, message: &Complaint) -> Result<(), Error> { + debug_assert!(self.id == *message.session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); @@ -412,16 +456,16 @@ impl Session { } // respond to complaint - if &message.against == self.node() { + if &*message.against == self.node() { let secret1_sent = data.nodes[&sender].secret1_sent.clone().expect("secrets were sent on KD phase; KC phase follows KD phase; qed"); let secret2_sent = data.nodes[&sender].secret2_sent.clone().expect("secrets were sent on KD phase; KC phase follows KD phase; qed"); // someone is complaining against us => let's respond - return self.cluster.broadcast(Message::ComplaintResponse(ComplaintResponse { - session: self.id.clone(), - secret1: secret1_sent, - secret2: secret2_sent, - })); + return self.cluster.broadcast(Message::Encryption(EncryptionMessage::ComplaintResponse(ComplaintResponse { + session: self.id.clone().into(), + secret1: secret1_sent.into(), + secret2: secret2_sent.into(), + }))); } // someone is complaining against someone else => let's remember this @@ -434,15 +478,15 @@ impl Session { if is_critical_complaints_num { // too many complaints => exclude from session - Session::disqualify_node(&message.against, &*self.cluster, &mut *data); + SessionImpl::disqualify_node(&message.against, &*self.cluster, &mut *data); } Ok(()) } /// When complaint response is received - pub fn on_complaint_response(&self, sender: NodeId, message: ComplaintResponse) -> Result<(), Error> { - debug_assert!(self.id == message.session); + pub fn on_complaint_response(&self, sender: NodeId, message: &ComplaintResponse) -> Result<(), Error> { + debug_assert!(self.id == *message.session); debug_assert!(&sender != self.node()); let mut data = self.data.lock(); @@ -471,11 +515,11 @@ impl Session { }; if !is_key_verification_ok { - Session::disqualify_node(&sender, &*self.cluster, &mut *data); + SessionImpl::disqualify_node(&sender, &*self.cluster, &mut *data); } else { let node_data = data.nodes.get_mut(&sender).expect("cluster guarantees to deliver messages from qualified nodes only; qed"); - node_data.secret1 = Some(message.secret1); - node_data.secret2 = Some(message.secret2); + node_data.secret1 = Some(message.secret1.clone().into()); + node_data.secret2 = Some(message.secret2.clone().into()); node_data.complaints.remove(self.node()); } @@ -510,19 +554,24 @@ impl Session { self_node.public_share = Some(self_public_share.clone()); // broadcast self public key share - self.cluster.broadcast(Message::PublicKeyShare(PublicKeyShare { - session: self.id.clone(), - public_share: self_public_share, - })) + self.cluster.broadcast(Message::Encryption(EncryptionMessage::PublicKeyShare(PublicKeyShare { + session: self.id.clone().into(), + public_share: self_public_share.into(), + }))) } /// When public key share is received. - pub fn on_public_key_share(&self, sender: NodeId, message: PublicKeyShare) -> Result<(), Error> { + pub fn on_public_key_share(&self, sender: NodeId, message: &PublicKeyShare) -> Result<(), Error> { let mut data = self.data.lock(); // check state if data.state != SessionState::WaitingForPublicKeyShare { - return Err(Error::InvalidStateForRequest); + match data.state { + SessionState::WaitingForInitializationComplete | + SessionState::WaitingForKeysDissemination | + SessionState::KeyCheck => return Err(Error::TooEarlyForRequest), + _ => return Err(Error::InvalidStateForRequest), + } } // update node data with received public share @@ -532,7 +581,7 @@ impl Session { return Err(Error::InvalidMessage); } - node_data.public_share = Some(message.public_share); + node_data.public_share = Some(message.public_share.clone().into()); } // if there's also nodes, which has not sent us their public shares - do nothing @@ -540,16 +589,149 @@ impl Session { return Ok(()); } - // else - calculate joint public key && finish session - data.joint_public = { + // else - calculate joint public key + let joint_public = { let public_shares = data.nodes.values().map(|n| n.public_share.as_ref().expect("keys received on KD phase; KG phase follows KD phase; qed")); - Some(math::compute_joint_public(public_shares)?) + math::compute_joint_public(public_shares)? }; - data.state = SessionState::Finished; + + // if we are at the slave node - wait for session completion + if data.master.as_ref() != Some(self.node()) { + data.joint_public = Some(Ok(joint_public)); + data.state = SessionState::WaitingForEncryptionConfirmation; + return Ok(()); + } + + // then generate secret point + // then encrypt secret point with joint public key + let secret_point = math::generate_random_point()?; + let encrypted_secret_point = math::encrypt_secret(&secret_point, &joint_public)?; + + // then save encrypted data to the key storage + let encrypted_data = DocumentKeyShare { + threshold: data.threshold.expect("threshold is filled in initialization phase; KG phase follows initialization phase; qed"), + id_numbers: data.nodes.iter().map(|(node_id, node_data)| (node_id.clone(), node_data.id_number.clone())).collect(), + secret_share: data.secret_share.as_ref().expect("secret_share is filled in KG phase; we are at the end of KG phase; qed").clone(), + common_point: encrypted_secret_point.common_point, + encrypted_point: encrypted_secret_point.encrypted_point, + }; + self.key_storage.insert(self.id.clone(), encrypted_data.clone()) + .map_err(|e| Error::KeyStorage(e.into()))?; + + // then distribute encrypted data to every other node + self.cluster.broadcast(Message::Encryption(EncryptionMessage::SessionCompleted(SessionCompleted { + session: self.id.clone().into(), + common_point: encrypted_data.common_point.clone().into(), + encrypted_point: encrypted_data.encrypted_point.clone().into(), + })))?; + + // then wait for confirmation from all other nodes + { + let self_node = data.nodes.get_mut(self.node()).expect("node is always qualified by himself; qed"); + self_node.completion_confirmed = true; + } + data.joint_public = Some(Ok(joint_public)); + data.secret_point = Some(Ok(secret_point)); + data.state = SessionState::WaitingForEncryptionConfirmation; Ok(()) } + /// When session completion message is received. + pub fn on_session_completed(&self, sender: NodeId, message: &SessionCompleted) -> Result<(), Error> { + debug_assert!(self.id == *message.session); + debug_assert!(&sender != self.node()); + + let mut data = self.data.lock(); + debug_assert!(data.nodes.contains_key(&sender)); + + // check state + if data.state != SessionState::WaitingForEncryptionConfirmation { + match data.state { + SessionState::WaitingForPublicKeyShare => return Err(Error::TooEarlyForRequest), + _ => return Err(Error::InvalidStateForRequest), + } + } + + // if we are not masters, save result and respond with confirmation + if data.master.as_ref() != Some(self.node()) { + // check that we have received message from master + if data.master.as_ref() != Some(&sender) { + return Err(Error::InvalidMessage); + } + + // save encrypted data to key storage + let encrypted_data = DocumentKeyShare { + threshold: data.threshold.expect("threshold is filled in initialization phase; KG phase follows initialization phase; qed"), + id_numbers: data.nodes.iter().map(|(node_id, node_data)| (node_id.clone(), node_data.id_number.clone())).collect(), + secret_share: data.secret_share.as_ref().expect("secret_share is filled in KG phase; we are at the end of KG phase; qed").clone(), + common_point: message.common_point.clone().into(), + encrypted_point: message.encrypted_point.clone().into(), + }; + self.key_storage.insert(self.id.clone(), encrypted_data.clone()) + .map_err(|e| Error::KeyStorage(e.into()))?; + + // then respond with confirmation + data.state = SessionState::Finished; + return self.cluster.send(&sender, Message::Encryption(EncryptionMessage::SessionCompleted(SessionCompleted { + session: self.id.clone().into(), + common_point: encrypted_data.common_point.clone().into(), + encrypted_point: encrypted_data.encrypted_point.clone().into(), + }))); + } + + // remember that we have received confirmation from sender node + { + let sender_node = data.nodes.get_mut(&sender).expect("node is always qualified by himself; qed"); + if sender_node.completion_confirmed { + return Err(Error::InvalidMessage); + } + + sender_node.completion_confirmed = true; + } + + // check if we have received confirmations from all cluster nodes + if data.nodes.iter().any(|(_, node_data)| !node_data.completion_confirmed) { + return Ok(()) + } + + // we have received enough confirmations => complete session + data.state = SessionState::Finished; + self.completed.notify_all(); + + Ok(()) + } + + /// When error has occured on another node. + pub fn on_session_error(&self, sender: NodeId, message: &SessionError) { + warn!("{}: encryption session error: {:?} from {}", self.node(), message, sender); + let mut data = self.data.lock(); + data.state = SessionState::Failed; + data.joint_public = Some(Err(Error::Io(message.error.clone()))); + data.secret_point = Some(Err(Error::Io(message.error.clone()))); + self.completed.notify_all(); + } + + /// When session timeout has occured. + pub fn on_session_timeout(&self, node: &NodeId) { + warn!("{}: encryption session timeout", self.node()); + let mut data = self.data.lock(); + + match data.state { + SessionState::WaitingForInitialization | + SessionState::WaitingForInitializationConfirm(_) | + SessionState::WaitingForInitializationComplete => (), + _ => if !data.nodes.contains_key(node) { + return; + }, + } + + data.state = SessionState::Failed; + data.joint_public = Some(Err(Error::Io("session expired".into()))); + data.secret_point = Some(Err(Error::Io("session expired".into()))); + self.completed.notify_all(); + } + /// Keys dissemination (KD) phase fn disseminate_keys(&self) -> Result<(), Error> { let mut data = self.data.lock(); @@ -576,12 +758,12 @@ impl Session { node_data.secret1_sent = Some(secret1.clone()); node_data.secret2_sent = Some(secret2.clone()); - self.cluster.send(&node, Message::KeysDissemination(KeysDissemination { - session: self.id.clone(), - secret1: secret1, - secret2: secret2, - publics: publics.clone(), - }))?; + self.cluster.send(&node, Message::Encryption(EncryptionMessage::KeysDissemination(KeysDissemination { + session: self.id.clone().into(), + secret1: secret1.into(), + secret2: secret2.into(), + publics: publics.iter().cloned().map(Into::into).collect(), + })))?; } else { node_data.secret1 = Some(secret1); node_data.secret2 = Some(secret2); @@ -599,7 +781,7 @@ impl Session { fn disqualify_node(node: &NodeId, cluster: &Cluster, data: &mut SessionData) { let threshold = data.threshold .expect("threshold is filled on initialization phase; node can only be disqualified during KC phase; KC phase follows initialization phase; qed"); - + // blacklist node cluster.blacklist(&node); // too many complaints => exclude from session @@ -612,6 +794,25 @@ impl Session { } } +impl Session for SessionImpl { + #[cfg(test)] + fn joint_public_key(&self) -> Option { + self.data.lock().joint_public.clone().and_then(|r| r.ok()) + } + + + fn wait(&self) -> Result { + let mut data = self.data.lock(); + if !data.secret_point.is_some() { + self.completed.wait(&mut data); + } + + data.secret_point.as_ref() + .expect("checked above or waited for completed; completed is only signaled when secret_point.is_some(); qed") + .clone() + } +} + impl EveryOtherNodeVisitor { pub fn new(self_id: &NodeId, nodes: I) -> Self where I: Iterator { EveryOtherNodeVisitor { @@ -648,11 +849,12 @@ impl NodeData { secret2: None, publics: None, public_share: None, + completion_confirmed: false, } } } -impl Debug for Session { +impl Debug for SessionImpl { fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { write!(f, "Encryption session {} on {}", self.id, self.self_node_id) } @@ -682,26 +884,29 @@ pub fn check_threshold(threshold: usize, nodes: &BTreeSet) -> Result<(), #[cfg(test)] mod tests { + use std::time; use std::sync::Arc; - use std::collections::{BTreeSet, BTreeMap}; + use std::collections::{BTreeSet, BTreeMap, VecDeque}; + use tokio_core::reactor::Core; use ethkey::{Random, Generator}; - use key_server_cluster::{NodeId, SessionId, Error}; - use key_server_cluster::message::{self, Message}; - use key_server_cluster::cluster::tests::DummyCluster; - use key_server_cluster::encryption_session::{Session, SessionState}; + use key_server_cluster::{NodeId, SessionId, Error, DummyKeyStorage}; + use key_server_cluster::message::{self, Message, EncryptionMessage}; + use key_server_cluster::cluster::tests::{DummyCluster, make_clusters, run_clusters, loop_until, all_connections_established}; + use key_server_cluster::encryption_session::{Session, SessionImpl, SessionState, SessionParams}; use key_server_cluster::math; use key_server_cluster::math::tests::do_encryption_and_decryption; #[derive(Debug)] struct Node { pub cluster: Arc, - pub session: Session, + pub session: SessionImpl, } #[derive(Debug)] struct MessageLoop { pub session_id: SessionId, pub nodes: BTreeMap, + pub queue: VecDeque<(NodeId, NodeId, Message)>, } impl MessageLoop { @@ -712,7 +917,12 @@ mod tests { let key_pair = Random.generate().unwrap(); let node_id = key_pair.public().clone(); let cluster = Arc::new(DummyCluster::new(node_id.clone())); - let session = Session::new(session_id.clone(), node_id.clone(), cluster.clone()); + let session = SessionImpl::new(SessionParams { + id: session_id.clone(), + self_node_id: node_id.clone(), + key_storage: Arc::new(DummyKeyStorage::default()), + cluster: cluster.clone(), + }); nodes.insert(node_id, Node { cluster: cluster, session: session }); } @@ -726,22 +936,23 @@ mod tests { MessageLoop { session_id: session_id, nodes: nodes, + queue: VecDeque::new(), } } - pub fn master(&self) -> &Session { + pub fn master(&self) -> &SessionImpl { &self.nodes.values().nth(0).unwrap().session } - pub fn first_slave(&self) -> &Session { + pub fn first_slave(&self) -> &SessionImpl { &self.nodes.values().nth(1).unwrap().session } - pub fn second_slave(&self) -> &Session { + pub fn second_slave(&self) -> &SessionImpl { &self.nodes.values().nth(2).unwrap().session } - pub fn third_slave(&self) -> &Session { + pub fn third_slave(&self) -> &SessionImpl { &self.nodes.values().nth(3).unwrap().session } @@ -749,18 +960,29 @@ mod tests { self.nodes.values() .filter_map(|n| n.cluster.take_message().map(|m| (n.session.node().clone(), m.0, m.1))) .nth(0) + .or_else(|| self.queue.pop_front()) } pub fn process_message(&mut self, msg: (NodeId, NodeId, Message)) -> Result<(), Error> { - match msg.2 { - Message::InitializeSession(message) => self.nodes[&msg.1].session.on_initialize_session(msg.0, message), - Message::ConfirmInitialization(message) => self.nodes[&msg.1].session.on_confirm_initialization(msg.0, message), - Message::CompleteInitialization(message) => self.nodes[&msg.1].session.on_complete_initialization(msg.0, message), - Message::KeysDissemination(message) => self.nodes[&msg.1].session.on_keys_dissemination(msg.0, message), - Message::Complaint(message) => self.nodes[&msg.1].session.on_complaint(msg.0, message), - Message::ComplaintResponse(message) => self.nodes[&msg.1].session.on_complaint_response(msg.0, message), - Message::PublicKeyShare(message) => self.nodes[&msg.1].session.on_public_key_share(msg.0, message), - _ => panic!("unexpected"), + match { + match msg.2 { + Message::Encryption(EncryptionMessage::InitializeSession(ref message)) => self.nodes[&msg.1].session.on_initialize_session(msg.0.clone(), &message), + Message::Encryption(EncryptionMessage::ConfirmInitialization(ref message)) => self.nodes[&msg.1].session.on_confirm_initialization(msg.0.clone(), &message), + Message::Encryption(EncryptionMessage::CompleteInitialization(ref message)) => self.nodes[&msg.1].session.on_complete_initialization(msg.0.clone(), &message), + Message::Encryption(EncryptionMessage::KeysDissemination(ref message)) => self.nodes[&msg.1].session.on_keys_dissemination(msg.0.clone(), &message), + Message::Encryption(EncryptionMessage::Complaint(ref message)) => self.nodes[&msg.1].session.on_complaint(msg.0.clone(), &message), + Message::Encryption(EncryptionMessage::ComplaintResponse(ref message)) => self.nodes[&msg.1].session.on_complaint_response(msg.0.clone(), &message), + Message::Encryption(EncryptionMessage::PublicKeyShare(ref message)) => self.nodes[&msg.1].session.on_public_key_share(msg.0.clone(), &message), + Message::Encryption(EncryptionMessage::SessionCompleted(ref message)) => self.nodes[&msg.1].session.on_session_completed(msg.0.clone(), &message), + _ => panic!("unexpected"), + } + } { + Ok(_) => Ok(()), + Err(Error::TooEarlyForRequest) => { + self.queue.push_back(msg); + Ok(()) + }, + Err(err) => Err(err), } } @@ -796,7 +1018,12 @@ mod tests { fn fails_to_initialize_if_not_a_part_of_cluster() { let node_id = math::generate_random_point().unwrap(); let cluster = Arc::new(DummyCluster::new(node_id.clone())); - let session = Session::new(SessionId::default(), node_id.clone(), cluster); + let session = SessionImpl::new(SessionParams { + id: SessionId::default(), + self_node_id: node_id.clone(), + key_storage: Arc::new(DummyKeyStorage::default()), + cluster: cluster, + }); let cluster_nodes: BTreeSet<_> = (0..2).map(|_| math::generate_random_point().unwrap()).collect(); assert_eq!(session.initialize(0, cluster_nodes).unwrap_err(), Error::InvalidNodesConfiguration); } @@ -816,9 +1043,9 @@ mod tests { fn fails_to_accept_initialization_when_already_initialized() { let (sid, m, _, mut l) = make_simple_cluster(0, 2).unwrap(); l.take_and_process_message().unwrap(); - assert_eq!(l.first_slave().on_initialize_session(m, message::InitializeSession { - session: sid, - derived_point: math::generate_random_point().unwrap(), + assert_eq!(l.first_slave().on_initialize_session(m, &message::InitializeSession { + session: sid.into(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidStateForRequest); } @@ -826,16 +1053,16 @@ mod tests { fn slave_updates_derived_point_on_initialization() { let (_, _, _, mut l) = make_simple_cluster(0, 2).unwrap(); let passed_point = match l.take_message().unwrap() { - (f, t, Message::InitializeSession(message)) => { + (f, t, Message::Encryption(EncryptionMessage::InitializeSession(message))) => { let point = message.derived_point.clone(); - l.process_message((f, t, Message::InitializeSession(message))).unwrap(); + l.process_message((f, t, Message::Encryption(EncryptionMessage::InitializeSession(message)))).unwrap(); point }, _ => panic!("unexpected"), }; match l.take_message().unwrap() { - (_, _, Message::ConfirmInitialization(message)) => assert!(passed_point != message.derived_point), + (_, _, Message::Encryption(EncryptionMessage::ConfirmInitialization(message))) => assert!(passed_point != message.derived_point), _ => panic!("unexpected"), } } @@ -846,9 +1073,9 @@ mod tests { l.take_and_process_message().unwrap(); l.take_and_process_message().unwrap(); l.take_and_process_message().unwrap(); - assert_eq!(l.master().on_confirm_initialization(s, message::ConfirmInitialization { - session: sid, - derived_point: math::generate_random_point().unwrap(), + assert_eq!(l.master().on_confirm_initialization(s, &message::ConfirmInitialization { + session: sid.into(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidStateForRequest); } @@ -857,9 +1084,9 @@ mod tests { let (sid, _, s, mut l) = make_simple_cluster(0, 2).unwrap(); l.take_and_process_message().unwrap(); l.take_and_process_message().unwrap(); - assert_eq!(l.master().on_confirm_initialization(s, message::ConfirmInitialization { - session: sid, - derived_point: math::generate_random_point().unwrap(), + assert_eq!(l.master().on_confirm_initialization(s, &message::ConfirmInitialization { + session: sid.into(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidStateForRequest); } @@ -868,15 +1095,15 @@ mod tests { let (_, _, _, mut l) = make_simple_cluster(0, 2).unwrap(); l.take_and_process_message().unwrap(); let passed_point = match l.take_message().unwrap() { - (f, t, Message::ConfirmInitialization(message)) => { + (f, t, Message::Encryption(EncryptionMessage::ConfirmInitialization(message))) => { let point = message.derived_point.clone(); - l.process_message((f, t, Message::ConfirmInitialization(message))).unwrap(); + l.process_message((f, t, Message::Encryption(EncryptionMessage::ConfirmInitialization(message)))).unwrap(); point }, _ => panic!("unexpected"), }; - assert!(passed_point != l.master().derived_point().unwrap()); + assert!(l.master().derived_point().unwrap() != passed_point.into()); } #[test] @@ -884,11 +1111,11 @@ mod tests { let (sid, m, s, l) = make_simple_cluster(0, 2).unwrap(); let mut nodes = BTreeMap::new(); nodes.insert(s, math::generate_random_scalar().unwrap()); - assert_eq!(l.first_slave().on_complete_initialization(m, message::CompleteInitialization { - session: sid, - nodes: nodes, + assert_eq!(l.first_slave().on_complete_initialization(m, &message::CompleteInitialization { + session: sid.into(), + nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), threshold: 0, - derived_point: math::generate_random_point().unwrap(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidNodesCount); } @@ -898,11 +1125,11 @@ mod tests { let mut nodes = BTreeMap::new(); nodes.insert(m, math::generate_random_scalar().unwrap()); nodes.insert(math::generate_random_point().unwrap(), math::generate_random_scalar().unwrap()); - assert_eq!(l.first_slave().on_complete_initialization(m, message::CompleteInitialization { - session: sid, - nodes: nodes, + assert_eq!(l.first_slave().on_complete_initialization(m, &message::CompleteInitialization { + session: sid.into(), + nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), threshold: 0, - derived_point: math::generate_random_point().unwrap(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidNodesConfiguration); } @@ -912,11 +1139,11 @@ mod tests { let mut nodes = BTreeMap::new(); nodes.insert(m, math::generate_random_scalar().unwrap()); nodes.insert(s, math::generate_random_scalar().unwrap()); - assert_eq!(l.first_slave().on_complete_initialization(m, message::CompleteInitialization { - session: sid, - nodes: nodes, + assert_eq!(l.first_slave().on_complete_initialization(m, &message::CompleteInitialization { + session: sid.into(), + nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), threshold: 2, - derived_point: math::generate_random_point().unwrap(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidThreshold); } @@ -926,11 +1153,11 @@ mod tests { let mut nodes = BTreeMap::new(); nodes.insert(m, math::generate_random_scalar().unwrap()); nodes.insert(s, math::generate_random_scalar().unwrap()); - assert_eq!(l.first_slave().on_complete_initialization(m, message::CompleteInitialization { - session: sid, - nodes: nodes, + assert_eq!(l.first_slave().on_complete_initialization(m, &message::CompleteInitialization { + session: sid.into(), + nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), threshold: 0, - derived_point: math::generate_random_point().unwrap(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidStateForRequest); } @@ -945,22 +1172,22 @@ mod tests { nodes.insert(m, math::generate_random_scalar().unwrap()); nodes.insert(s, math::generate_random_scalar().unwrap()); nodes.insert(l.second_slave().node().clone(), math::generate_random_scalar().unwrap()); - assert_eq!(l.first_slave().on_complete_initialization(l.second_slave().node().clone(), message::CompleteInitialization { - session: sid, - nodes: nodes, + assert_eq!(l.first_slave().on_complete_initialization(l.second_slave().node().clone(), &message::CompleteInitialization { + session: sid.into(), + nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), threshold: 0, - derived_point: math::generate_random_point().unwrap(), + derived_point: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidMessage); } #[test] fn fails_to_accept_keys_dissemination_if_not_waiting_for_it() { let (sid, _, s, l) = make_simple_cluster(0, 2).unwrap(); - assert_eq!(l.master().on_keys_dissemination(s, message::KeysDissemination { - session: sid, - secret1: math::generate_random_scalar().unwrap(), - secret2: math::generate_random_scalar().unwrap(), - publics: vec![math::generate_random_point().unwrap()], + assert_eq!(l.master().on_keys_dissemination(s, &message::KeysDissemination { + session: sid.into(), + secret1: math::generate_random_scalar().unwrap().into(), + secret2: math::generate_random_scalar().unwrap().into(), + publics: vec![math::generate_random_point().unwrap().into()], }).unwrap_err(), Error::InvalidStateForRequest); } @@ -974,11 +1201,11 @@ mod tests { l.take_and_process_message().unwrap(); // m -> s1: CompleteInitialization l.take_and_process_message().unwrap(); // m -> s2: CompleteInitialization l.take_and_process_message().unwrap(); // m -> s1: KeysDissemination - assert_eq!(l.first_slave().on_keys_dissemination(m, message::KeysDissemination { - session: sid, - secret1: math::generate_random_scalar().unwrap(), - secret2: math::generate_random_scalar().unwrap(), - publics: vec![math::generate_random_point().unwrap(), math::generate_random_point().unwrap()], + assert_eq!(l.first_slave().on_keys_dissemination(m, &message::KeysDissemination { + session: sid.into(), + secret1: math::generate_random_scalar().unwrap().into(), + secret2: math::generate_random_scalar().unwrap().into(), + publics: vec![math::generate_random_point().unwrap().into(), math::generate_random_point().unwrap().into()], }).unwrap_err(), Error::InvalidMessage); } @@ -992,11 +1219,11 @@ mod tests { l.take_and_process_message().unwrap(); // m -> s1: CompleteInitialization l.take_and_process_message().unwrap(); // m -> s2: CompleteInitialization l.take_and_process_message().unwrap(); // m -> s1: KeysDissemination - assert_eq!(l.first_slave().on_keys_dissemination(m, message::KeysDissemination { - session: sid, - secret1: math::generate_random_scalar().unwrap(), - secret2: math::generate_random_scalar().unwrap(), - publics: vec![math::generate_random_point().unwrap()], + assert_eq!(l.first_slave().on_keys_dissemination(m, &message::KeysDissemination { + session: sid.into(), + secret1: math::generate_random_scalar().unwrap().into(), + secret2: math::generate_random_scalar().unwrap().into(), + publics: vec![math::generate_random_point().unwrap().into()], }).unwrap_err(), Error::InvalidStateForRequest); } @@ -1004,12 +1231,12 @@ mod tests { fn defends_if_receives_complain_on_himself() { let (sid, m, s, mut l) = make_simple_cluster(1, 3).unwrap(); l.take_and_process_all_messages().unwrap(); - l.master().on_complaint(s, message::Complaint { - session: sid, - against: m, + l.master().on_complaint(s, &message::Complaint { + session: sid.into(), + against: m.into(), }).unwrap(); match l.take_message().unwrap() { - (_, _, Message::ComplaintResponse(_)) => (), + (_, _, Message::Encryption(EncryptionMessage::ComplaintResponse(_))) => (), _ => panic!("unexpected"), } } @@ -1018,13 +1245,13 @@ mod tests { fn node_is_disqualified_if_enough_complaints_received() { let (sid, _, s, mut l) = make_simple_cluster(1, 4).unwrap(); l.take_and_process_all_messages().unwrap(); - l.master().on_complaint(l.second_slave().node().clone(), message::Complaint { - session: sid, - against: s.clone(), + l.master().on_complaint(l.second_slave().node().clone(), &message::Complaint { + session: sid.into(), + against: s.clone().into(), }).unwrap(); - l.master().on_complaint(l.third_slave().node().clone(), message::Complaint { - session: sid, - against: s, + l.master().on_complaint(l.third_slave().node().clone(), &message::Complaint { + session: sid.into(), + against: s.into(), }).unwrap(); assert_eq!(l.master().qualified_nodes().len(), 3); } @@ -1033,13 +1260,13 @@ mod tests { fn node_is_not_disqualified_if_enough_complaints_received_from_the_same_node() { let (sid, _, s, mut l) = make_simple_cluster(1, 4).unwrap(); l.take_and_process_all_messages().unwrap(); - l.master().on_complaint(l.second_slave().node().clone(), message::Complaint { - session: sid, - against: s.clone(), + l.master().on_complaint(l.second_slave().node().clone(), &message::Complaint { + session: sid.into(), + against: s.clone().into(), }).unwrap(); - l.master().on_complaint(l.second_slave().node().clone(), message::Complaint { - session: sid, - against: s, + l.master().on_complaint(l.second_slave().node().clone(), &message::Complaint { + session: sid.into(), + against: s.into(), }).unwrap(); assert_eq!(l.master().qualified_nodes().len(), 4); } @@ -1058,17 +1285,17 @@ mod tests { l.take_and_process_message().unwrap(); // s1 -> m: KeysDissemination l.take_and_process_message().unwrap(); // s1 -> s2: KeysDissemination let s2 = l.second_slave().node().clone(); - l.master().on_keys_dissemination(s2.clone(), message::KeysDissemination { - session: sid.clone(), - secret1: math::generate_random_scalar().unwrap(), - secret2: math::generate_random_scalar().unwrap(), - publics: vec![math::generate_random_point().unwrap(), math::generate_random_point().unwrap()], + l.master().on_keys_dissemination(s2.clone(), &message::KeysDissemination { + session: sid.clone().into(), + secret1: math::generate_random_scalar().unwrap().into(), + secret2: math::generate_random_scalar().unwrap().into(), + publics: vec![math::generate_random_point().unwrap().into(), math::generate_random_point().unwrap().into()], }).unwrap(); assert_eq!(l.master().qualified_nodes().len(), 3); - l.master().on_complaint_response(s2, message::ComplaintResponse { - session: sid, - secret1: math::generate_random_scalar().unwrap(), - secret2: math::generate_random_scalar().unwrap(), + l.master().on_complaint_response(s2, &message::ComplaintResponse { + session: sid.into(), + secret1: math::generate_random_scalar().unwrap().into(), + secret2: math::generate_random_scalar().unwrap().into(), }).unwrap(); assert_eq!(l.master().qualified_nodes().len(), 2); } @@ -1087,22 +1314,22 @@ mod tests { l.take_and_process_message().unwrap(); // s1 -> m: KeysDissemination l.take_and_process_message().unwrap(); // s1 -> s2: KeysDissemination let (f, t, msg) = match l.take_message() { - Some((f, t, Message::KeysDissemination(msg))) => (f, t, msg), + Some((f, t, Message::Encryption(EncryptionMessage::KeysDissemination(msg)))) => (f, t, msg), _ => panic!("unexpected"), }; assert_eq!(&f, l.second_slave().node()); assert_eq!(&t, l.master().node()); - l.master().on_keys_dissemination(f.clone(), message::KeysDissemination { - session: sid.clone(), - secret1: math::generate_random_scalar().unwrap(), - secret2: math::generate_random_scalar().unwrap(), - publics: msg.publics.clone(), + l.master().on_keys_dissemination(f.clone(), &message::KeysDissemination { + session: sid.clone().into(), + secret1: math::generate_random_scalar().unwrap().into(), + secret2: math::generate_random_scalar().unwrap().into(), + publics: msg.publics.clone().into(), }).unwrap(); assert_eq!(l.master().qualified_nodes().len(), 3); - l.master().on_complaint_response(f, message::ComplaintResponse { - session: sid, - secret1: msg.secret1, - secret2: msg.secret2, + l.master().on_complaint_response(f, &message::ComplaintResponse { + session: sid.into(), + secret1: msg.secret1.into(), + secret2: msg.secret2.into(), }).unwrap(); assert_eq!(l.master().qualified_nodes().len(), 3); } @@ -1116,9 +1343,9 @@ mod tests { #[test] fn should_not_accept_public_key_share_when_is_not_waiting_for_it() { let (sid, _, s, l) = make_simple_cluster(1, 3).unwrap(); - assert_eq!(l.master().on_public_key_share(s, message::PublicKeyShare { - session: sid, - public_share: math::generate_random_point().unwrap(), + assert_eq!(l.master().on_public_key_share(s, &message::PublicKeyShare { + session: sid.into(), + public_share: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidStateForRequest); } @@ -1129,24 +1356,21 @@ mod tests { l.master().start_key_generation_phase().unwrap(); l.first_slave().start_key_generation_phase().unwrap(); let (f, t, msg) = match l.take_message() { - Some((f, t, Message::PublicKeyShare(msg))) => (f, t, msg), + Some((f, t, Message::Encryption(EncryptionMessage::PublicKeyShare(msg)))) => (f, t, msg), _ => panic!("unexpected"), }; assert_eq!(&f, l.master().node()); assert_eq!(&t, l.first_slave().node()); - l.process_message((f, t, Message::PublicKeyShare(msg.clone()))).unwrap(); - assert_eq!(l.first_slave().on_public_key_share(m, message::PublicKeyShare { - session: sid, - public_share: math::generate_random_point().unwrap(), + l.process_message((f, t, Message::Encryption(EncryptionMessage::PublicKeyShare(msg.clone())))).unwrap(); + assert_eq!(l.first_slave().on_public_key_share(m, &message::PublicKeyShare { + session: sid.into(), + public_share: math::generate_random_point().unwrap().into(), }).unwrap_err(), Error::InvalidMessage); } #[test] fn complete_enc_dec_session() { - // TODO: when number of nodes, needed to decrypt message is odd, algorithm won't work - // let test_cases = [/*(0, 2), */(1, 2), (1, 3), (2, 3), (1, 4), (2, 4), (3, 4), (1, 5), (2, 5), (3, 5), (4, 5), - // (1, 10), (2, 10), (3, 10), (4, 10), (5, 10), (6, 10), (7, 10), (8, 10), (9, 10)]; - let test_cases = [(3, 5)]; + let test_cases = [(0, 5), (2, 5), (3, 5)]; for &(threshold, num_nodes) in &test_cases { let mut l = MessageLoop::new(num_nodes); l.master().initialize(threshold, l.nodes.keys().cloned().collect()).unwrap(); @@ -1194,4 +1418,26 @@ mod tests { } // TODO: add test where some nodes are disqualified from session + + #[test] + fn encryption_session_works_over_network() { + //::util::log::init_log(); + + let test_cases = [(1, 3)]; + for &(threshold, num_nodes) in &test_cases { + let mut core = Core::new().unwrap(); + + // prepare cluster objects for each node + let clusters = make_clusters(&core, 6020, num_nodes); + run_clusters(&clusters); + + // establish connections + loop_until(&mut core, time::Duration::from_millis(300), || clusters.iter().all(all_connections_established)); + + // run session to completion + let session_id = SessionId::default(); + let session = clusters[0].client().new_encryption_session(session_id, threshold).unwrap(); + loop_until(&mut core, time::Duration::from_millis(1000), || session.joint_public_key().is_some()); + } + } } diff --git a/secret_store/src/key_server_cluster/io/deadline.rs b/secret_store/src/key_server_cluster/io/deadline.rs new file mode 100644 index 000000000..7b8c4d0ed --- /dev/null +++ b/secret_store/src/key_server_cluster/io/deadline.rs @@ -0,0 +1,85 @@ +// 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 . + +use std::io; +use std::time::Duration; +use futures::{Future, Select, BoxFuture, Poll, Async}; +use tokio_core::reactor::{Handle, Timeout}; + +type DeadlineBox where F: Future = BoxFuture, F::Error>; + +/// Complete a passed future or fail if it is not completed within timeout. +pub fn deadline(duration: Duration, handle: &Handle, future: F) -> Result, io::Error> + where F: Future + Send + 'static, T: 'static { + let timeout = try!(Timeout::new(duration, handle)).map(|_| DeadlineStatus::Timeout).boxed(); + let future = future.map(DeadlineStatus::Meet).boxed(); + let deadline = Deadline { + future: timeout.select(future), + }; + Ok(deadline) +} + +#[derive(Debug, PartialEq)] +/// Deadline future completion status. +pub enum DeadlineStatus { + /// Completed a future. + Meet(T), + /// Faled with timeout. + Timeout, +} + +/// Future, which waits for passed future completion within given period, or fails with timeout. +pub struct Deadline where F: Future { + future: Select, DeadlineBox>, +} + +impl Future for Deadline where F: Future { + type Item = DeadlineStatus; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + match self.future.poll() { + Ok(Async::Ready((result, _other))) => Ok(Async::Ready(result)), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err((err, _other)) => Err(err), + } + } +} + +#[cfg(test)] +mod tests { + use std::io; + use std::time::Duration; + use futures::{Future, empty, done}; + use tokio_core::reactor::Core; + use super::{deadline, DeadlineStatus}; + + //#[test] TODO: not working + fn _deadline_timeout_works() { + let mut core = Core::new().unwrap(); + let deadline = deadline(Duration::from_millis(1), &core.handle(), empty::<(), io::Error>()).unwrap(); + core.turn(Some(Duration::from_millis(3))); + assert_eq!(deadline.wait().unwrap(), DeadlineStatus::Timeout); + } + + #[test] + fn deadline_result_works() { + let mut core = Core::new().unwrap(); + let deadline = deadline(Duration::from_millis(1000), &core.handle(), done(Ok(()))).unwrap(); + core.turn(Some(Duration::from_millis(3))); + assert_eq!(deadline.wait().unwrap(), DeadlineStatus::Meet(())); + } +} \ No newline at end of file diff --git a/secret_store/src/key_server_cluster/io/handshake.rs b/secret_store/src/key_server_cluster/io/handshake.rs new file mode 100644 index 000000000..0d71d25aa --- /dev/null +++ b/secret_store/src/key_server_cluster/io/handshake.rs @@ -0,0 +1,320 @@ +// 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 . + +use std::io; +use std::collections::BTreeSet; +use futures::{Future, Poll, Async}; +use ethkey::{Random, Generator, KeyPair, Secret, sign, verify_public}; +use util::H256; +use key_server_cluster::{NodeId, Error}; +use key_server_cluster::message::{Message, ClusterMessage, NodePublicKey, NodePrivateKeySignature}; +use key_server_cluster::io::{write_message, write_encrypted_message, WriteMessage, ReadMessage, + read_message, compute_shared_key}; + +/// Start handshake procedure with another node from the cluster. +pub fn handshake(a: A, self_key_pair: KeyPair, trusted_nodes: BTreeSet) -> Handshake where A: io::Write + io::Read { + let self_confirmation_plain = Random.generate().map(|kp| *kp.secret().clone()).map_err(Into::into); + handshake_with_plain_confirmation(a, self_confirmation_plain, self_key_pair, trusted_nodes) +} + +/// Start handshake procedure with another node from the cluster and given plain confirmation. +pub fn handshake_with_plain_confirmation(a: A, self_confirmation_plain: Result, self_key_pair: KeyPair, trusted_nodes: BTreeSet) -> Handshake where A: io::Write + io::Read { + let (error, state) = match self_confirmation_plain.clone() + .and_then(|c| Handshake::::make_public_key_message(self_key_pair.public().clone(), c)) { + Ok(message) => (None, HandshakeState::SendPublicKey(write_message(a, message))), + Err(err) => (Some((a, Err(err))), HandshakeState::Finished), + }; + + Handshake { + is_active: true, + error: error, + state: state, + self_key_pair: self_key_pair, + self_confirmation_plain: self_confirmation_plain.unwrap_or(Default::default()), + trusted_nodes: trusted_nodes, + other_node_id: None, + other_confirmation_plain: None, + shared_key: None, + } +} + +/// Wait for handshake procedure to be started by another node from the cluster. +pub fn accept_handshake(a: A, self_key_pair: KeyPair, trusted_nodes: BTreeSet) -> Handshake where A: io::Write + io::Read { + let self_confirmation_plain = Random.generate().map(|kp| *kp.secret().clone()).map_err(Into::into); + let (error, state) = match self_confirmation_plain.clone() { + Ok(_) => (None, HandshakeState::ReceivePublicKey(read_message(a))), + Err(err) => (Some((a, Err(err))), HandshakeState::Finished), + }; + + Handshake { + is_active: false, + error: error, + state: state, + self_key_pair: self_key_pair, + self_confirmation_plain: self_confirmation_plain.unwrap_or(Default::default()), + trusted_nodes: trusted_nodes, + other_node_id: None, + other_confirmation_plain: None, + shared_key: None, + } +} + +#[derive(Debug, PartialEq)] +/// Result of handshake procedure. +pub struct HandshakeResult { + /// Node id. + pub node_id: NodeId, + /// Shared key. + pub shared_key: Secret, +} + +/// Future handshake procedure. +pub struct Handshake { + is_active: bool, + error: Option<(A, Result)>, + state: HandshakeState, + self_key_pair: KeyPair, + self_confirmation_plain: H256, + trusted_nodes: BTreeSet, + other_node_id: Option, + other_confirmation_plain: Option, + shared_key: Option, +} + +/// Active handshake state. +enum HandshakeState { + SendPublicKey(WriteMessage), + ReceivePublicKey(ReadMessage), + SendPrivateKeySignature(WriteMessage), + ReceivePrivateKeySignature(ReadMessage), + Finished, +} + +impl Handshake where A: io::Read + io::Write { + #[cfg(test)] + pub fn set_self_confirmation_plain(&mut self, self_confirmation_plain: H256) { + self.self_confirmation_plain = self_confirmation_plain; + } + + pub fn make_public_key_message(self_node_id: NodeId, confirmation_plain: H256) -> Result { + Ok(Message::Cluster(ClusterMessage::NodePublicKey(NodePublicKey { + node_id: self_node_id.into(), + confirmation_plain: confirmation_plain.into(), + }))) + } + + fn make_private_key_signature_message(secret: &Secret, confirmation_plain: &H256) -> Result { + Ok(Message::Cluster(ClusterMessage::NodePrivateKeySignature(NodePrivateKeySignature { + confirmation_signed: sign(secret, confirmation_plain)?.into(), + }))) + } +} + +impl Future for Handshake where A: io::Read + io::Write { + type Item = (A, Result); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + if let Some(error_result) = self.error.take() { + return Ok(error_result.into()); + } + + let (next, result) = match self.state { + HandshakeState::SendPublicKey(ref mut future) => { + let (stream, _) = try_ready!(future.poll()); + + if self.is_active { + (HandshakeState::ReceivePublicKey( + read_message(stream) + ), Async::NotReady) + } else { + self.shared_key = match compute_shared_key(self.self_key_pair.secret(), + self.other_node_id.as_ref().expect("we are in passive mode; in passive mode SendPublicKey follows ReceivePublicKey; other_node_id is filled in ReceivePublicKey; qed") + ) { + Ok(shared_key) => Some(shared_key), + Err(err) => return Ok((stream, Err(err)).into()), + }; + + let message = match Handshake::::make_private_key_signature_message( + self.self_key_pair.secret(), + self.other_confirmation_plain.as_ref().expect("we are in passive mode; in passive mode SendPublicKey follows ReceivePublicKey; other_confirmation_plain is filled in ReceivePublicKey; qed") + ) { + Ok(message) => message, + Err(err) => return Ok((stream, Err(err)).into()), + }; + (HandshakeState::SendPrivateKeySignature(write_encrypted_message(stream, + self.shared_key.as_ref().expect("filled couple of lines above; qed"), + message)), Async::NotReady) + } + }, + HandshakeState::ReceivePublicKey(ref mut future) => { + let (stream, message) = try_ready!(future.poll()); + + let message = match message { + Ok(message) => match message { + Message::Cluster(ClusterMessage::NodePublicKey(message)) => message, + _ => return Ok((stream, Err(Error::InvalidMessage)).into()), + }, + Err(err) => return Ok((stream, Err(err.into())).into()), + }; + + if !self.trusted_nodes.contains(&*message.node_id) { + return Ok((stream, Err(Error::InvalidNodeId)).into()); + } + + self.other_node_id = Some(message.node_id.into()); + self.other_confirmation_plain = Some(message.confirmation_plain.into()); + if self.is_active { + self.shared_key = match compute_shared_key(self.self_key_pair.secret(), + self.other_node_id.as_ref().expect("filled couple of lines above; qed") + ) { + Ok(shared_key) => Some(shared_key), + Err(err) => return Ok((stream, Err(err)).into()), + }; + + let message = match Handshake::::make_private_key_signature_message( + self.self_key_pair.secret(), + self.other_confirmation_plain.as_ref().expect("filled couple of lines above; qed") + ) { + Ok(message) => message, + Err(err) => return Ok((stream, Err(err)).into()), + }; + (HandshakeState::SendPrivateKeySignature(write_encrypted_message(stream, + self.shared_key.as_ref().expect("filled couple of lines above; qed"), + message)), Async::NotReady) + } else { + let message = match Handshake::::make_public_key_message(self.self_key_pair.public().clone(), self.self_confirmation_plain.clone()) { + Ok(message) => message, + Err(err) => return Ok((stream, Err(err)).into()), + }; + (HandshakeState::SendPublicKey(write_message(stream, message)), Async::NotReady) + } + }, + HandshakeState::SendPrivateKeySignature(ref mut future) => { + let (stream, _) = try_ready!(future.poll()); + + (HandshakeState::ReceivePrivateKeySignature( + read_message(stream) + ), Async::NotReady) + }, + HandshakeState::ReceivePrivateKeySignature(ref mut future) => { + let (stream, message) = try_ready!(future.poll()); + + let message = match message { + Ok(message) => match message { + Message::Cluster(ClusterMessage::NodePrivateKeySignature(message)) => message, + _ => return Ok((stream, Err(Error::InvalidMessage)).into()), + }, + Err(err) => return Ok((stream, Err(err.into())).into()), + }; + + let other_node_public = self.other_node_id.as_ref().expect("other_node_id is filled in ReceivePublicKey; ReceivePrivateKeySignature follows ReceivePublicKey; qed"); + if !verify_public(other_node_public, &*message.confirmation_signed, &self.self_confirmation_plain).unwrap_or(false) { + return Ok((stream, Err(Error::InvalidMessage)).into()); + } + + (HandshakeState::Finished, Async::Ready((stream, Ok(HandshakeResult { + node_id: self.other_node_id.expect("other_node_id is filled in ReceivePublicKey; ReceivePrivateKeySignature follows ReceivePublicKey; qed"), + shared_key: self.shared_key.clone().expect("shared_key is filled in Send/ReceivePublicKey; ReceivePrivateKeySignature follows Send/ReceivePublicKey; qed"), + })))) + }, + HandshakeState::Finished => panic!("poll Handshake after it's done"), + }; + + self.state = next; + match result { + // by polling again, we register new future + Async::NotReady => self.poll(), + result => Ok(result) + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + use futures::Future; + use ethcrypto::ecdh::agree; + use ethkey::{Random, Generator, sign}; + use util::H256; + use key_server_cluster::io::message::tests::TestIo; + use key_server_cluster::message::{Message, ClusterMessage, NodePublicKey, NodePrivateKeySignature}; + use super::{handshake_with_plain_confirmation, accept_handshake, HandshakeResult}; + + fn prepare_test_io() -> (H256, TestIo) { + let self_key_pair = Random.generate().unwrap(); + let peer_key_pair = Random.generate().unwrap(); + let mut io = TestIo::new(self_key_pair.clone(), peer_key_pair.public().clone()); + + let self_confirmation_plain = *Random.generate().unwrap().secret().clone(); + let peer_confirmation_plain = *Random.generate().unwrap().secret().clone(); + + let self_confirmation_signed = sign(peer_key_pair.secret(), &self_confirmation_plain).unwrap(); + let peer_confirmation_signed = sign(self_key_pair.secret(), &peer_confirmation_plain).unwrap(); + + io.add_input_message(Message::Cluster(ClusterMessage::NodePublicKey(NodePublicKey { + node_id: peer_key_pair.public().clone().into(), + confirmation_plain: peer_confirmation_plain.into(), + }))); + io.add_input_message(Message::Cluster(ClusterMessage::NodePrivateKeySignature(NodePrivateKeySignature { + confirmation_signed: self_confirmation_signed.into(), + }))); + + io.add_output_message(Message::Cluster(ClusterMessage::NodePublicKey(NodePublicKey { + node_id: self_key_pair.public().clone().into(), + confirmation_plain: self_confirmation_plain.clone().into(), + }))); + io.add_output_message(Message::Cluster(ClusterMessage::NodePrivateKeySignature(NodePrivateKeySignature { + confirmation_signed: peer_confirmation_signed.into(), + }))); + + (self_confirmation_plain, io) + } + + #[test] + fn active_handshake_works() { + let (self_confirmation_plain, io) = prepare_test_io(); + let self_key_pair = io.self_key_pair().clone(); + let trusted_nodes: BTreeSet<_> = vec![io.peer_public().clone()].into_iter().collect(); + let shared_key = agree(self_key_pair.secret(), trusted_nodes.iter().nth(0).unwrap()).unwrap(); + + let handshake = handshake_with_plain_confirmation(io, Ok(self_confirmation_plain), self_key_pair, trusted_nodes); + let handshake_result = handshake.wait().unwrap(); + assert_eq!(handshake_result.1, Ok(HandshakeResult { + node_id: handshake_result.0.peer_public().clone(), + shared_key: shared_key, + })); + handshake_result.0.assert_output(); + } + + #[test] + fn passive_handshake_works() { + let (self_confirmation_plain, io) = prepare_test_io(); + let self_key_pair = io.self_key_pair().clone(); + let trusted_nodes: BTreeSet<_> = vec![io.peer_public().clone()].into_iter().collect(); + let shared_key = agree(self_key_pair.secret(), io.peer_public()).unwrap(); + + let mut handshake = accept_handshake(io, self_key_pair, trusted_nodes); + handshake.set_self_confirmation_plain(self_confirmation_plain); + + let handshake_result = handshake.wait().unwrap(); + assert_eq!(handshake_result.1, Ok(HandshakeResult { + node_id: handshake_result.0.peer_public().clone(), + shared_key: shared_key, + })); + handshake_result.0.assert_output(); + } +} diff --git a/secret_store/src/key_server_cluster/io/message.rs b/secret_store/src/key_server_cluster/io/message.rs new file mode 100644 index 000000000..bcabebf76 --- /dev/null +++ b/secret_store/src/key_server_cluster/io/message.rs @@ -0,0 +1,247 @@ +// 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 . + +use std::io::Cursor; +use std::u16; +use std::ops::Deref; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use serde_json; +use ethcrypto::ecdh::agree; +use ethkey::{Public, Secret}; +use key_server_cluster::Error; +use key_server_cluster::message::{Message, ClusterMessage, EncryptionMessage, DecryptionMessage}; + +/// Size of serialized header. +pub const MESSAGE_HEADER_SIZE: usize = 4; + +#[derive(Debug, PartialEq)] +/// Message header. +pub struct MessageHeader { + /// Message/Header version. + pub version: u8, + /// Message kind. + pub kind: u8, + /// Message payload size (without header). + pub size: u16, +} + +#[derive(Debug, Clone, PartialEq)] +/// Serialized message. +pub struct SerializedMessage(Vec); + +impl Deref for SerializedMessage { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl Into> for SerializedMessage { + fn into(self) -> Vec { + self.0 + } +} + +/// Serialize message. +pub fn serialize_message(message: Message) -> Result { + let (message_kind, payload) = match message { + Message::Cluster(ClusterMessage::NodePublicKey(payload)) => (1, serde_json::to_vec(&payload)), + Message::Cluster(ClusterMessage::NodePrivateKeySignature(payload)) => (2, serde_json::to_vec(&payload)), + Message::Cluster(ClusterMessage::KeepAlive(payload)) => (3, serde_json::to_vec(&payload)), + Message::Cluster(ClusterMessage::KeepAliveResponse(payload)) => (4, serde_json::to_vec(&payload)), + + Message::Encryption(EncryptionMessage::InitializeSession(payload)) => (50, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::ConfirmInitialization(payload)) => (51, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::CompleteInitialization(payload)) => (52, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::KeysDissemination(payload)) => (53, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::Complaint(payload)) => (54, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::ComplaintResponse(payload)) => (55, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::PublicKeyShare(payload)) => (56, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::SessionError(payload)) => (57, serde_json::to_vec(&payload)), + Message::Encryption(EncryptionMessage::SessionCompleted(payload)) => (58, serde_json::to_vec(&payload)), + + Message::Decryption(DecryptionMessage::InitializeDecryptionSession(payload)) => (100, serde_json::to_vec(&payload)), + Message::Decryption(DecryptionMessage::ConfirmDecryptionInitialization(payload)) => (101, serde_json::to_vec(&payload)), + Message::Decryption(DecryptionMessage::RequestPartialDecryption(payload)) => (102, serde_json::to_vec(&payload)), + Message::Decryption(DecryptionMessage::PartialDecryption(payload)) => (103, serde_json::to_vec(&payload)), + Message::Decryption(DecryptionMessage::DecryptionSessionError(payload)) => (104, serde_json::to_vec(&payload)), + }; + + let payload = payload.map_err(|err| Error::Serde(err.to_string()))?; + let payload_len = payload.len(); + if payload_len > u16::MAX as usize { + return Err(Error::InvalidMessage); + } + + let header = MessageHeader { + kind: message_kind, + version: 1, + size: payload_len as u16, + }; + + let mut serialized_message = serialize_header(&header)?; + serialized_message.extend(payload); + Ok(SerializedMessage(serialized_message)) +} + +/// Deserialize message. +pub fn deserialize_message(header: &MessageHeader, payload: Vec) -> Result { + Ok(match header.kind { + 1 => Message::Cluster(ClusterMessage::NodePublicKey(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 2 => Message::Cluster(ClusterMessage::NodePrivateKeySignature(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 3 => Message::Cluster(ClusterMessage::KeepAlive(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 4 => Message::Cluster(ClusterMessage::KeepAliveResponse(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + + 50 => Message::Encryption(EncryptionMessage::InitializeSession(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 51 => Message::Encryption(EncryptionMessage::ConfirmInitialization(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 52 => Message::Encryption(EncryptionMessage::CompleteInitialization(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 53 => Message::Encryption(EncryptionMessage::KeysDissemination(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 54 => Message::Encryption(EncryptionMessage::Complaint(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 55 => Message::Encryption(EncryptionMessage::ComplaintResponse(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 56 => Message::Encryption(EncryptionMessage::PublicKeyShare(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 57 => Message::Encryption(EncryptionMessage::SessionError(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 58 => Message::Encryption(EncryptionMessage::SessionCompleted(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + + 100 => Message::Decryption(DecryptionMessage::InitializeDecryptionSession(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 101 => Message::Decryption(DecryptionMessage::ConfirmDecryptionInitialization(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 102 => Message::Decryption(DecryptionMessage::RequestPartialDecryption(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 103 => Message::Decryption(DecryptionMessage::PartialDecryption(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + 104 => Message::Decryption(DecryptionMessage::DecryptionSessionError(serde_json::from_slice(&payload).map_err(|err| Error::Serde(err.to_string()))?)), + + _ => return Err(Error::Serde(format!("unknown message type {}", header.kind))), + }) +} + +/// Encrypt serialized message. +pub fn encrypt_message(_key: &Secret, message: SerializedMessage) -> Result { + Ok(message) // TODO: implement me +} + +/// Decrypt serialized message. +pub fn decrypt_message(_key: &Secret, payload: Vec) -> Result, Error> { + Ok(payload) // TODO: implement me +} + +/// Compute shared encryption key. +pub fn compute_shared_key(self_secret: &Secret, other_public: &Public) -> Result { + Ok(agree(self_secret, other_public)?) +} + +/// Serialize message header. +fn serialize_header(header: &MessageHeader) -> Result, Error> { + let mut buffer = Vec::with_capacity(MESSAGE_HEADER_SIZE); + buffer.write_u8(header.version)?; + buffer.write_u8(header.kind)?; + buffer.write_u16::(header.size)?; + Ok(buffer) +} + +/// Deserialize message header. +pub fn deserialize_header(data: &[u8]) -> Result { + let mut reader = Cursor::new(data); + Ok(MessageHeader { + version: reader.read_u8()?, + kind: reader.read_u8()?, + size: reader.read_u16::()?, + }) +} + +#[cfg(test)] +pub mod tests { + use std::io; + use ethkey::{KeyPair, Public}; + use key_server_cluster::message::Message; + use super::{MESSAGE_HEADER_SIZE, MessageHeader, serialize_message, serialize_header, deserialize_header}; + + pub struct TestIo { + self_key_pair: KeyPair, + peer_public: Public, + input_buffer: io::Cursor>, + output_buffer: Vec, + expected_output_buffer: Vec, + } + + impl TestIo { + pub fn new(self_key_pair: KeyPair, peer_public: Public) -> Self { + TestIo { + self_key_pair: self_key_pair, + peer_public: peer_public, + input_buffer: io::Cursor::new(Vec::new()), + output_buffer: Vec::new(), + expected_output_buffer: Vec::new(), + } + } + + pub fn self_key_pair(&self) -> &KeyPair { + &self.self_key_pair + } + + pub fn peer_public(&self) -> &Public { + &self.peer_public + } + + pub fn add_input_message(&mut self, message: Message) { + let serialized_message = serialize_message(message).unwrap(); + let serialized_message: Vec<_> = serialized_message.into(); + let input_buffer = self.input_buffer.get_mut(); + for b in serialized_message { + input_buffer.push(b); + } + } + + pub fn add_output_message(&mut self, message: Message) { + let serialized_message = serialize_message(message).unwrap(); + let serialized_message: Vec<_> = serialized_message.into(); + self.expected_output_buffer.extend(serialized_message); + } + + pub fn assert_output(&self) { + assert_eq!(self.output_buffer, self.expected_output_buffer); + } + } + + impl io::Read for TestIo { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + io::Read::read(&mut self.input_buffer, buf) + } + } + + impl io::Write for TestIo { + fn write(&mut self, buf: &[u8]) -> io::Result { + io::Write::write(&mut self.output_buffer, buf) + } + + fn flush(&mut self) -> io::Result<()> { + io::Write::flush(&mut self.output_buffer) + } + } + + #[test] + fn header_serialization_works() { + let header = MessageHeader { + kind: 1, + version: 2, + size: 3, + }; + + let serialized_header = serialize_header(&header).unwrap(); + assert_eq!(serialized_header.len(), MESSAGE_HEADER_SIZE); + + let deserialized_header = deserialize_header(&serialized_header).unwrap(); + assert_eq!(deserialized_header, header); + } +} diff --git a/secret_store/src/key_server_cluster/io/mod.rs b/secret_store/src/key_server_cluster/io/mod.rs new file mode 100644 index 000000000..57071038e --- /dev/null +++ b/secret_store/src/key_server_cluster/io/mod.rs @@ -0,0 +1,34 @@ +// 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 . + +mod deadline; +mod handshake; +mod message; +mod read_header; +mod read_payload; +mod read_message; +mod shared_tcp_stream; +mod write_message; + +pub use self::deadline::{deadline, Deadline, DeadlineStatus}; +pub use self::handshake::{handshake, accept_handshake, Handshake, HandshakeResult}; +pub use self::message::{MessageHeader, SerializedMessage, serialize_message, deserialize_message, + encrypt_message, compute_shared_key}; +pub use self::read_header::{read_header, ReadHeader}; +pub use self::read_payload::{read_payload, read_encrypted_payload, ReadPayload}; +pub use self::read_message::{read_message, read_encrypted_message, ReadMessage}; +pub use self::shared_tcp_stream::SharedTcpStream; +pub use self::write_message::{write_message, write_encrypted_message, WriteMessage}; diff --git a/secret_store/src/key_server_cluster/io/read_header.rs b/secret_store/src/key_server_cluster/io/read_header.rs new file mode 100644 index 000000000..ab7ce360e --- /dev/null +++ b/secret_store/src/key_server_cluster/io/read_header.rs @@ -0,0 +1,44 @@ +// 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 . + +use std::io; +use futures::{Future, Poll, Async}; +use tokio_core::io::{ReadExact, read_exact}; +use key_server_cluster::Error; +use key_server_cluster::io::message::{MESSAGE_HEADER_SIZE, MessageHeader, deserialize_header}; + +/// Create future for read single message header from the stream. +pub fn read_header(a: A) -> ReadHeader where A: io::Read { + ReadHeader { + reader: read_exact(a, [0; MESSAGE_HEADER_SIZE]), + } +} + +/// Future for read single message header from the stream. +pub struct ReadHeader { + reader: ReadExact, +} + +impl Future for ReadHeader where A: io::Read { + type Item = (A, Result); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let (read, data) = try_ready!(self.reader.poll()); + let header = deserialize_header(&data); + Ok(Async::Ready((read, header))) + } +} diff --git a/secret_store/src/key_server_cluster/io/read_message.rs b/secret_store/src/key_server_cluster/io/read_message.rs new file mode 100644 index 000000000..418e5e31d --- /dev/null +++ b/secret_store/src/key_server_cluster/io/read_message.rs @@ -0,0 +1,86 @@ +// 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 . + +use std::io; +use futures::{Poll, Future, Async}; +use ethkey::Secret; +use key_server_cluster::Error; +use key_server_cluster::message::Message; +use key_server_cluster::io::{read_header, ReadHeader, read_payload, read_encrypted_payload, ReadPayload}; + +/// Create future for read single message from the stream. +pub fn read_message(a: A) -> ReadMessage where A: io::Read { + ReadMessage { + key: None, + state: ReadMessageState::ReadHeader(read_header(a)), + } +} + +/// Create future for read single encrypted message from the stream. +pub fn read_encrypted_message(a: A, key: Secret) -> ReadMessage where A: io::Read { + ReadMessage { + key: Some(key), + state: ReadMessageState::ReadHeader(read_header(a)), + } +} + +enum ReadMessageState { + ReadHeader(ReadHeader), + ReadPayload(ReadPayload), + Finished, +} + +/// Future for read single message from the stream. +pub struct ReadMessage { + key: Option, + state: ReadMessageState, +} + +impl Future for ReadMessage where A: io::Read { + type Item = (A, Result); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let (next, result) = match self.state { + ReadMessageState::ReadHeader(ref mut future) => { + let (read, header) = try_ready!(future.poll()); + let header = match header { + Ok(header) => header, + Err(err) => return Ok((read, Err(err)).into()), + }; + + let future = match self.key.take() { + Some(key) => read_encrypted_payload(read, header, key), + None => read_payload(read, header), + }; + let next = ReadMessageState::ReadPayload(future); + (next, Async::NotReady) + }, + ReadMessageState::ReadPayload(ref mut future) => { + let (read, payload) = try_ready!(future.poll()); + (ReadMessageState::Finished, Async::Ready((read, payload))) + }, + ReadMessageState::Finished => panic!("poll ReadMessage after it's done"), + }; + + self.state = next; + match result { + // by polling again, we register new future + Async::NotReady => self.poll(), + result => Ok(result) + } + } +} diff --git a/secret_store/src/key_server_cluster/io/read_payload.rs b/secret_store/src/key_server_cluster/io/read_payload.rs new file mode 100644 index 000000000..f6df3155e --- /dev/null +++ b/secret_store/src/key_server_cluster/io/read_payload.rs @@ -0,0 +1,64 @@ +// 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 . + +use std::io; +use futures::{Poll, Future}; +use tokio_core::io::{read_exact, ReadExact}; +use ethkey::Secret; +use key_server_cluster::Error; +use key_server_cluster::message::Message; +use key_server_cluster::io::message::{MessageHeader, deserialize_message, decrypt_message}; + +/// Create future for read single message payload from the stream. +pub fn read_payload(a: A, header: MessageHeader) -> ReadPayload where A: io::Read { + ReadPayload { + reader: read_exact(a, vec![0; header.size as usize]), + header: header, + key: None, + } +} + +/// Create future for read single encrypted message payload from the stream. +pub fn read_encrypted_payload(a: A, header: MessageHeader, key: Secret) -> ReadPayload where A: io::Read { + ReadPayload { + reader: read_exact(a, vec![0; header.size as usize]), + header: header, + key: Some(key), + } +} + +/// Future for read single message payload from the stream. +pub struct ReadPayload { + reader: ReadExact>, + header: MessageHeader, + key: Option, +} + +impl Future for ReadPayload where A: io::Read { + type Item = (A, Result); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let (read, data) = try_ready!(self.reader.poll()); + let payload = if let Some(key) = self.key.take() { + decrypt_message(&key, data) + .and_then(|data| deserialize_message(&self.header, data)) + } else { + deserialize_message(&self.header, data) + }; + Ok((read, payload).into()) + } +} diff --git a/secret_store/src/key_server_cluster/io/shared_tcp_stream.rs b/secret_store/src/key_server_cluster/io/shared_tcp_stream.rs new file mode 100644 index 000000000..82933c8a2 --- /dev/null +++ b/secret_store/src/key_server_cluster/io/shared_tcp_stream.rs @@ -0,0 +1,60 @@ +// 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 . + +use std::sync::Arc; +use std::io::{Read, Write, Error}; +use tokio_core::net::TcpStream; + +/// Read+Write implementation for Arc. +pub struct SharedTcpStream { + io: Arc, +} + +impl SharedTcpStream { + pub fn new(a: Arc) -> Self { + SharedTcpStream { + io: a, + } + } +} + +impl From for SharedTcpStream { + fn from(a: TcpStream) -> Self { + SharedTcpStream::new(Arc::new(a)) + } +} + +impl Read for SharedTcpStream { + fn read(&mut self, buf: &mut [u8]) -> Result { + Read::read(&mut (&*self.io as &TcpStream), buf) + } +} + +impl Write for SharedTcpStream { + fn write(&mut self, buf: &[u8]) -> Result { + Write::write(&mut (&*self.io as &TcpStream), buf) + } + + fn flush(&mut self) -> Result<(), Error> { + Write::flush(&mut (&*self.io as &TcpStream)) + } +} + +impl Clone for SharedTcpStream { + fn clone(&self) -> Self { + SharedTcpStream::new(self.io.clone()) + } +} diff --git a/secret_store/src/key_server_cluster/io/write_message.rs b/secret_store/src/key_server_cluster/io/write_message.rs new file mode 100644 index 000000000..457673676 --- /dev/null +++ b/secret_store/src/key_server_cluster/io/write_message.rs @@ -0,0 +1,70 @@ +// 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 . + +use std::io; +use futures::{Future, Poll}; +use tokio_core::io::{WriteAll, write_all}; +use ethkey::Secret; +use key_server_cluster::message::Message; +use key_server_cluster::io::{serialize_message, encrypt_message}; + +/// Write plain message to the channel. +pub fn write_message(a: A, message: Message) -> WriteMessage where A: io::Write { + let (error, future) = match serialize_message(message) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) { + Ok(message) => (None, write_all(a, message.into())), + Err(error) => (Some(error), write_all(a, Vec::new())), + }; + WriteMessage { + error: error, + future: future, + } +} + +/// Write encrypted message to the channel. +pub fn write_encrypted_message(a: A, key: &Secret, message: Message) -> WriteMessage where A: io::Write { + let (error, future) = match serialize_message(message) + .and_then(|message| encrypt_message(key, message)) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) { + Ok(message) => (None, write_all(a, message.into())), + Err(error) => (Some(error), write_all(a, Vec::new())), + }; + + + WriteMessage { + error: error, + future: future, + } +} + +/// Future message write. +pub struct WriteMessage { + error: Option, + future: WriteAll>, +} + +impl Future for WriteMessage where A: io::Write { + type Item = (A, Vec); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + if let Some(err) = self.error.take() { + return Err(err); + } + + self.future.poll() + } +} diff --git a/secret_store/src/key_server_cluster/math.rs b/secret_store/src/key_server_cluster/math.rs index 4da17ebc7..fdda08746 100644 --- a/secret_store/src/key_server_cluster/math.rs +++ b/secret_store/src/key_server_cluster/math.rs @@ -160,7 +160,7 @@ pub fn compute_joint_secret<'a, I>(mut secret_coeffs: I) -> Result Result { +pub fn encrypt_secret(secret: &Public, joint_public: &Public) -> Result { // this is performed by KS-cluster client (or KS master) let key_pair = Random.generate()?; @@ -171,7 +171,7 @@ pub fn encrypt_secret(secret: Public, joint_public: &Public) -> Result Result(node_number: &Secret, node_secret_share: &Secret, mut other_nodes_numbers: I) -> Result where I: Iterator { - let other_node_number = other_nodes_numbers.next().expect("compute_node_shadow is called when at least two nodes are required to decrypt secret; qed"); + let other_node_number = match other_nodes_numbers.next() { + Some(other_node_number) => other_node_number, + None => return Ok(node_secret_share.clone()), + }; + let mut shadow = node_number.clone(); shadow.sub(other_node_number)?; shadow.inv()?; @@ -231,17 +235,24 @@ pub fn compute_joint_shadow_point_test<'a, I>(access_key: &Secret, common_point: } /// Decrypt data using joint shadow point. -pub fn decrypt_with_joint_shadow(access_key: &Secret, encrypted_point: &Public, joint_shadow_point: &Public) -> Result { +pub fn decrypt_with_joint_shadow(threshold: usize, access_key: &Secret, encrypted_point: &Public, joint_shadow_point: &Public) -> Result { let mut inv_access_key = access_key.clone(); inv_access_key.inv()?; - - let mut decrypted_point = joint_shadow_point.clone(); - math::public_mul_secret(&mut decrypted_point, &inv_access_key)?; - math::public_add(&mut decrypted_point, encrypted_point)?; + + let mut mul = joint_shadow_point.clone(); + math::public_mul_secret(&mut mul, &inv_access_key)?; + + let mut decrypted_point = encrypted_point.clone(); + if threshold % 2 != 0 { + math::public_add(&mut decrypted_point, &mul)?; + } else { + math::public_sub(&mut decrypted_point, &mul)?; + } Ok(decrypted_point) } +#[cfg(test)] /// Decrypt data using joint secret (version for tests). pub fn decrypt_with_joint_secret(encrypted_point: &Public, common_point: &Public, joint_secret: &Secret) -> Result { let mut common_point_mul = common_point.clone(); @@ -262,7 +273,7 @@ pub mod tests { // === PART2: encryption using joint public key === // the next line is executed on KeyServer-client - let encrypted_secret = encrypt_secret(document_secret_plain.clone(), &joint_public).unwrap(); + let encrypted_secret = encrypt_secret(&document_secret_plain, &joint_public).unwrap(); // === PART3: decryption === @@ -285,7 +296,7 @@ pub mod tests { assert_eq!(joint_shadow_point, joint_shadow_point_test); // decrypt encrypted secret using joint shadow point - let document_secret_decrypted = decrypt_with_joint_shadow(&access_key, &encrypted_secret.encrypted_point, &joint_shadow_point).unwrap(); + let document_secret_decrypted = decrypt_with_joint_shadow(t, &access_key, &encrypted_secret.encrypted_point, &joint_shadow_point).unwrap(); // decrypt encrypted secret using joint secret [just for test] let document_secret_decrypted_test = match joint_secret { @@ -298,7 +309,8 @@ pub mod tests { #[test] fn full_encryption_math_session() { - let test_cases = [(1, 3)]; + let test_cases = [(0, 2), (1, 2), (1, 3), (2, 3), (1, 4), (2, 4), (3, 4), (1, 5), (2, 5), (3, 5), (4, 5), + (1, 10), (2, 10), (3, 10), (4, 10), (5, 10), (6, 10), (7, 10), (8, 10), (9, 10)]; for &(t, n) in &test_cases { // === PART1: DKG === diff --git a/secret_store/src/key_server_cluster/message.rs b/secret_store/src/key_server_cluster/message.rs index 800dcf705..9958884a4 100644 --- a/secret_store/src/key_server_cluster/message.rs +++ b/secret_store/src/key_server_cluster/message.rs @@ -14,13 +14,42 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::fmt; use std::collections::{BTreeSet, BTreeMap}; -use ethkey::{Public, Secret, Signature}; -use key_server_cluster::{NodeId, SessionId}; +use ethkey::Secret; +use key_server_cluster::SessionId; +use super::{SerializableH256, SerializablePublic, SerializableSecret, SerializableSignature}; + +pub type MessageSessionId = SerializableH256; +pub type MessageNodeId = SerializablePublic; #[derive(Clone, Debug)] -/// All possible messages that can be sent during DKG. +/// All possible messages that can be sent during encryption/decryption sessions. pub enum Message { + /// Cluster message. + Cluster(ClusterMessage), + /// Encryption message. + Encryption(EncryptionMessage), + /// Decryption message. + Decryption(DecryptionMessage), +} + +#[derive(Clone, Debug)] +/// All possible cluster-level messages. +pub enum ClusterMessage { + /// Introduce node public key. + NodePublicKey(NodePublicKey), + /// Confirm that node owns its private key. + NodePrivateKeySignature(NodePrivateKeySignature), + /// Keep alive message. + KeepAlive(KeepAlive), + /// Keep alive message response. + KeepAliveResponse(KeepAliveResponse), +} + +#[derive(Clone, Debug)] +/// All possible messages that can be sent during encryption session. +pub enum EncryptionMessage { /// Initialize new DKG session. InitializeSession(InitializeSession), /// Confirm DKG session initialization. @@ -35,7 +64,15 @@ pub enum Message { ComplaintResponse(ComplaintResponse), /// Broadcast self public key portion. PublicKeyShare(PublicKeyShare), + /// When session error has occured. + SessionError(SessionError), + /// When session is completed. + SessionCompleted(SessionCompleted), +} +#[derive(Clone, Debug)] +/// All possible messages that can be sent during decryption session. +pub enum DecryptionMessage { /// Initialize decryption session. InitializeDecryptionSession(InitializeDecryptionSession), /// Confirm/reject decryption session initialization. @@ -44,125 +81,272 @@ pub enum Message { RequestPartialDecryption(RequestPartialDecryption), /// Partial decryption is completed PartialDecryption(PartialDecryption), + /// When decryption session error has occured. + DecryptionSessionError(DecryptionSessionError), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +/// Introduce node public key. +pub struct NodePublicKey { + /// Node identifier (aka node public key). + pub node_id: MessageNodeId, + /// Data, which must be signed by peer to prove that he owns the corresponding private key. + pub confirmation_plain: SerializableH256, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +/// Confirm that node owns the private key of previously passed public key (aka node id). +pub struct NodePrivateKeySignature { + /// Previously passed `confirmation_plain`, signed with node private key. + pub confirmation_signed: SerializableSignature, +} + + +#[derive(Clone, Debug, Serialize, Deserialize)] +/// Ask if the node is still alive. +pub struct KeepAlive { +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +/// Confirm that the node is still alive. +pub struct KeepAliveResponse { +} + +#[derive(Clone, Debug, Serialize, Deserialize)] /// Initialize new DKG session. pub struct InitializeSession { /// Session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Derived generation point. Starting from originator, every node must multiply this /// point by random scalar (unknown by other nodes). At the end of initialization /// `point` will be some (k1 * k2 * ... * kn) * G = `point` where `(k1 * k2 * ... * kn)` /// is unknown for every node. - pub derived_point: Public, + pub derived_point: SerializablePublic, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Confirm DKG session initialization. pub struct ConfirmInitialization { /// Session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Derived generation point. - pub derived_point: Public, + pub derived_point: SerializablePublic, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Broadcast generated point to every other node. pub struct CompleteInitialization { /// Session Id. - pub session: SessionId, + pub session: MessageSessionId, /// All session participants along with their identification numbers. - pub nodes: BTreeMap, + pub nodes: BTreeMap, /// Decryption threshold. During decryption threshold-of-route.len() nodes must came to /// consensus to successfully decrypt message. pub threshold: usize, /// Derived generation point. - pub derived_point: Public, + pub derived_point: SerializablePublic, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Generated keys are sent to every node. pub struct KeysDissemination { /// Session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Secret 1. - pub secret1: Secret, + pub secret1: SerializableSecret, /// Secret 2. - pub secret2: Secret, + pub secret2: SerializableSecret, /// Public values. - pub publics: Vec, + pub publics: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Complaint against node is broadcasted. pub struct Complaint { /// Session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Public values. - pub against: NodeId, + pub against: MessageNodeId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Node is responding to complaint. pub struct ComplaintResponse { /// Session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Secret 1. - pub secret1: Secret, + pub secret1: SerializableSecret, /// Secret 2. - pub secret2: Secret, + pub secret2: SerializableSecret, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Node is sharing its public key share. pub struct PublicKeyShare { /// Session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Public key share. - pub public_share: Public, + pub public_share: SerializablePublic, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +/// When session error has occured. +pub struct SessionError { + /// Session Id. + pub session: MessageSessionId, + /// Public key share. + pub error: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +/// When session is completed. +pub struct SessionCompleted { + /// Session Id. + pub session: MessageSessionId, + /// Common (shared) encryption point. + pub common_point: SerializablePublic, + /// Encrypted point. + pub encrypted_point: SerializablePublic, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] /// Node is requested to decrypt data, encrypted in given session. pub struct InitializeDecryptionSession { /// Encryption session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Decryption session Id. - pub sub_session: Secret, + pub sub_session: SerializableSecret, /// Requestor signature. - pub requestor_signature: Signature, + pub requestor_signature: SerializableSignature, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Node is responding to decryption request. pub struct ConfirmDecryptionInitialization { /// Encryption session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Decryption session Id. - pub sub_session: Secret, + pub sub_session: SerializableSecret, /// Is node confirmed to make a decryption?. pub is_confirmed: bool, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Node is requested to do a partial decryption. pub struct RequestPartialDecryption { /// Encryption session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Decryption session Id. - pub sub_session: Secret, + pub sub_session: SerializableSecret, /// Nodes that are agreed to do a decryption. - pub nodes: BTreeSet, + pub nodes: BTreeSet, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] /// Node has partially decrypted the secret. pub struct PartialDecryption { /// Encryption session Id. - pub session: SessionId, + pub session: MessageSessionId, /// Decryption session Id. - pub sub_session: Secret, + pub sub_session: SerializableSecret, /// Partially decrypted secret. - pub shadow_point: Public, + pub shadow_point: SerializablePublic, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +/// When decryption session error has occured. +pub struct DecryptionSessionError { + /// Encryption session Id. + pub session: MessageSessionId, + /// Decryption session Id. + pub sub_session: SerializableSecret, + /// Public key share. + pub error: String, +} + +impl EncryptionMessage { + pub fn session_id(&self) -> &SessionId { + match *self { + EncryptionMessage::InitializeSession(ref msg) => &msg.session, + EncryptionMessage::ConfirmInitialization(ref msg) => &msg.session, + EncryptionMessage::CompleteInitialization(ref msg) => &msg.session, + EncryptionMessage::KeysDissemination(ref msg) => &msg.session, + EncryptionMessage::Complaint(ref msg) => &msg.session, + EncryptionMessage::ComplaintResponse(ref msg) => &msg.session, + EncryptionMessage::PublicKeyShare(ref msg) => &msg.session, + EncryptionMessage::SessionError(ref msg) => &msg.session, + EncryptionMessage::SessionCompleted(ref msg) => &msg.session, + } + } +} + +impl DecryptionMessage { + pub fn session_id(&self) -> &SessionId { + match *self { + DecryptionMessage::InitializeDecryptionSession(ref msg) => &msg.session, + DecryptionMessage::ConfirmDecryptionInitialization(ref msg) => &msg.session, + DecryptionMessage::RequestPartialDecryption(ref msg) => &msg.session, + DecryptionMessage::PartialDecryption(ref msg) => &msg.session, + DecryptionMessage::DecryptionSessionError(ref msg) => &msg.session, + } + } + + pub fn sub_session_id(&self) -> &Secret { + match *self { + DecryptionMessage::InitializeDecryptionSession(ref msg) => &msg.sub_session, + DecryptionMessage::ConfirmDecryptionInitialization(ref msg) => &msg.sub_session, + DecryptionMessage::RequestPartialDecryption(ref msg) => &msg.sub_session, + DecryptionMessage::PartialDecryption(ref msg) => &msg.sub_session, + DecryptionMessage::DecryptionSessionError(ref msg) => &msg.sub_session, + } + } +} + +impl fmt::Display for Message { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Message::Cluster(ref message) => write!(f, "Cluster.{}", message), + Message::Encryption(ref message) => write!(f, "Encryption.{}", message), + Message::Decryption(ref message) => write!(f, "Decryption.{}", message), + } + } +} + +impl fmt::Display for ClusterMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ClusterMessage::NodePublicKey(_) => write!(f, "NodePublicKey"), + ClusterMessage::NodePrivateKeySignature(_) => write!(f, "NodePrivateKeySignature"), + ClusterMessage::KeepAlive(_) => write!(f, "KeepAlive"), + ClusterMessage::KeepAliveResponse(_) => write!(f, "KeepAliveResponse"), + } + } +} + +impl fmt::Display for EncryptionMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + EncryptionMessage::InitializeSession(_) => write!(f, "InitializeSession"), + EncryptionMessage::ConfirmInitialization(_) => write!(f, "ConfirmInitialization"), + EncryptionMessage::CompleteInitialization(_) => write!(f, "CompleteInitialization"), + EncryptionMessage::KeysDissemination(_) => write!(f, "KeysDissemination"), + EncryptionMessage::Complaint(_) => write!(f, "Complaint"), + EncryptionMessage::ComplaintResponse(_) => write!(f, "ComplaintResponse"), + EncryptionMessage::PublicKeyShare(_) => write!(f, "PublicKeyShare"), + EncryptionMessage::SessionError(ref msg) => write!(f, "SessionError({})", msg.error), + EncryptionMessage::SessionCompleted(_) => write!(f, "SessionCompleted"), + } + } +} + +impl fmt::Display for DecryptionMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + DecryptionMessage::InitializeDecryptionSession(_) => write!(f, "InitializeDecryptionSession"), + DecryptionMessage::ConfirmDecryptionInitialization(_) => write!(f, "ConfirmDecryptionInitialization"), + DecryptionMessage::RequestPartialDecryption(_) => write!(f, "RequestPartialDecryption"), + DecryptionMessage::PartialDecryption(_) => write!(f, "PartialDecryption"), + DecryptionMessage::DecryptionSessionError(_) => write!(f, "DecryptionSessionError"), + } + } } diff --git a/secret_store/src/key_server_cluster/mod.rs b/secret_store/src/key_server_cluster/mod.rs index 5d0dacd11..8b33e06f7 100644 --- a/secret_store/src/key_server_cluster/mod.rs +++ b/secret_store/src/key_server_cluster/mod.rs @@ -14,21 +14,36 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -#![allow(dead_code)] // TODO: remove me - -use std::collections::BTreeMap; -use ethkey::{self, Public, Secret, Signature}; +use std::fmt; +use std::io::Error as IoError; +use ethkey; +use ethcrypto; use super::types::all::DocumentAddress; -pub use super::acl_storage::AclStorage; +pub use super::types::all::{NodeId, EncryptionConfiguration}; +pub use super::acl_storage::{AclStorage, DummyAclStorage}; +pub use super::key_storage::{KeyStorage, DocumentKeyShare}; +pub use super::serialization::{SerializableSignature, SerializableH256, SerializableSecret, SerializablePublic}; +pub use self::cluster::{ClusterCore, ClusterConfiguration, ClusterClient}; +pub use self::encryption_session::Session as EncryptionSession; +pub use self::decryption_session::Session as DecryptionSession; + +#[cfg(test)] +pub use super::key_storage::tests::DummyKeyStorage; -pub type NodeId = Public; pub type SessionId = DocumentAddress; -pub type SessionIdSignature = Signature; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] /// Errors which can occur during encryption/decryption session pub enum Error { + /// Invalid node address has been passed. + InvalidNodeAddress, + /// Invalid node id has been passed. + InvalidNodeId, + /// Session with the given id already exists. + DuplicateSessionId, + /// Session with the given id is unknown. + InvalidSessionId, /// Invalid number of nodes. /// There must be at least two nodes participating in encryption. /// There must be at least one node participating in decryption. @@ -39,28 +54,24 @@ pub enum Error { /// Threshold value must be in [0; n - 1], where n is a number of nodes participating in the encryption. InvalidThreshold, /// Current state of encryption/decryption session does not allow to proceed request. + /// Reschedule this request for later processing. + TooEarlyForRequest, + /// Current state of encryption/decryption session does not allow to proceed request. /// This means that either there is some comm-failure or node is misbehaving/cheating. InvalidStateForRequest, - /// Some data in passed message was recognized as invalid. + /// Message or some data in the message was recognized as invalid. /// This means that node is misbehaving/cheating. InvalidMessage, + /// Connection to node, required for this session is not established. + NodeDisconnected, /// Cryptographic error. EthKey(String), -} - -#[derive(Debug, Clone)] -/// Data, which is stored on every node after DKG && encryption is completed. -pub struct EncryptedData { - /// Decryption threshold (at least threshold + 1 nodes are required to decrypt data). - threshold: usize, - /// Nodes ids numbers. - id_numbers: BTreeMap, - /// Node secret share. - secret_share: Secret, - /// Common (shared) encryption point. - common_point: Public, - /// Encrypted point. - encrypted_point: Public, + /// I/O error has occured. + Io(String), + /// Deserialization error has occured. + Serde(String), + /// Key storage error. + KeyStorage(String), } impl From for Error { @@ -69,8 +80,50 @@ impl From for Error { } } +impl From for Error { + fn from(err: ethcrypto::Error) -> Self { + Error::EthKey(err.into()) + } +} + +impl From for Error { + fn from(err: IoError) -> Self { + Error::Io(err.to_string()) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::InvalidNodeAddress => write!(f, "invalid node address has been passed"), + Error::InvalidNodeId => write!(f, "invalid node id has been passed"), + Error::DuplicateSessionId => write!(f, "session with the same id is already registered"), + Error::InvalidSessionId => write!(f, "invalid session id has been passed"), + Error::InvalidNodesCount => write!(f, "invalid nodes count"), + Error::InvalidNodesConfiguration => write!(f, "invalid nodes configuration"), + Error::InvalidThreshold => write!(f, "invalid threshold value has been passed"), + Error::TooEarlyForRequest => write!(f, "session is not yet ready to process this request"), + Error::InvalidStateForRequest => write!(f, "session is in invalid state for processing this request"), + Error::InvalidMessage => write!(f, "invalid message is received"), + Error::NodeDisconnected => write!(f, "node required for this operation is currently disconnected"), + Error::EthKey(ref e) => write!(f, "cryptographic error {}", e), + 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), + } + } +} + +impl Into for Error { + fn into(self) -> String { + format!("{}", self) + } +} + mod cluster; mod decryption_session; mod encryption_session; +mod io; mod math; mod message; +mod net; diff --git a/secret_store/src/key_server_cluster/net/accept_connection.rs b/secret_store/src/key_server_cluster/net/accept_connection.rs new file mode 100644 index 000000000..0daa8b2da --- /dev/null +++ b/secret_store/src/key_server_cluster/net/accept_connection.rs @@ -0,0 +1,63 @@ +// 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 . + +use std::io; +use std::net::SocketAddr; +use std::time::Duration; +use std::collections::BTreeSet; +use futures::{Future, Poll}; +use tokio_core::reactor::Handle; +use tokio_core::net::TcpStream; +use ethkey::KeyPair; +use key_server_cluster::{Error, NodeId}; +use key_server_cluster::io::{accept_handshake, Handshake, Deadline, deadline}; +use key_server_cluster::net::Connection; + +/// Create future for accepting incoming connection. +pub fn accept_connection(address: SocketAddr, stream: TcpStream, handle: &Handle, self_key_pair: KeyPair, trusted_nodes: BTreeSet) -> Deadline { + let accept = AcceptConnection { + handshake: accept_handshake(stream, self_key_pair, trusted_nodes), + address: address, + }; + + deadline(Duration::new(5, 0), handle, accept).expect("Failed to create timeout") +} + +/// Future for accepting incoming connection. +pub struct AcceptConnection { + handshake: Handshake, + address: SocketAddr, +} + +impl Future for AcceptConnection { + type Item = Result; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let (stream, result) = try_ready!(self.handshake.poll()); + let result = match result { + Ok(result) => result, + Err(err) => return Ok(Err(err).into()), + }; + let connection = Connection { + stream: stream.into(), + address: self.address, + node_id: result.node_id, + key: result.shared_key, + }; + Ok(Ok(connection).into()) + } +} diff --git a/secret_store/src/key_server_cluster/net/connect.rs b/secret_store/src/key_server_cluster/net/connect.rs new file mode 100644 index 000000000..449168ab2 --- /dev/null +++ b/secret_store/src/key_server_cluster/net/connect.rs @@ -0,0 +1,90 @@ +// 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 . + +use std::collections::BTreeSet; +use std::io; +use std::time::Duration; +use std::net::SocketAddr; +use futures::{Future, Poll, Async}; +use tokio_core::reactor::Handle; +use tokio_core::net::{TcpStream, TcpStreamNew}; +use ethkey::KeyPair; +use key_server_cluster::{Error, NodeId}; +use key_server_cluster::io::{handshake, Handshake, Deadline, deadline}; +use key_server_cluster::net::Connection; + +/// Create future for connecting to other node. +pub fn connect(address: &SocketAddr, handle: &Handle, self_key_pair: KeyPair, trusted_nodes: BTreeSet) -> Deadline { + let connect = Connect { + state: ConnectState::TcpConnect(TcpStream::connect(address, handle)), + address: address.clone(), + self_key_pair: self_key_pair, + trusted_nodes: trusted_nodes, + }; + + deadline(Duration::new(5, 0), handle, connect).expect("Failed to create timeout") +} + +enum ConnectState { + TcpConnect(TcpStreamNew), + Handshake(Handshake), + Connected, +} + +/// Future for connecting to other node. +pub struct Connect { + state: ConnectState, + address: SocketAddr, + self_key_pair: KeyPair, + trusted_nodes: BTreeSet, +} + +impl Future for Connect { + type Item = Result; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let (next, result) = match self.state { + ConnectState::TcpConnect(ref mut future) => { + let stream = try_ready!(future.poll()); + let handshake = handshake(stream, self.self_key_pair.clone(), self.trusted_nodes.clone()); + (ConnectState::Handshake(handshake), Async::NotReady) + }, + ConnectState::Handshake(ref mut future) => { + let (stream, result) = try_ready!(future.poll()); + let result = match result { + Ok(result) => result, + Err(err) => return Ok(Async::Ready(Err(err))), + }; + let connection = Connection { + stream: stream.into(), + address: self.address, + node_id: result.node_id, + key: result.shared_key, + }; + (ConnectState::Connected, Async::Ready(Ok(connection))) + }, + ConnectState::Connected => panic!("poll Connect after it's done"), + }; + + self.state = next; + match result { + // by polling again, we register new future + Async::NotReady => self.poll(), + result => Ok(result) + } + } +} diff --git a/secret_store/src/key_server_cluster/net/connection.rs b/secret_store/src/key_server_cluster/net/connection.rs new file mode 100644 index 000000000..8125b81d3 --- /dev/null +++ b/secret_store/src/key_server_cluster/net/connection.rs @@ -0,0 +1,32 @@ +// 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 . + +use std::net; +use ethkey::Secret; +use key_server_cluster::NodeId; +use key_server_cluster::io::SharedTcpStream; + +/// Established connection data +pub struct Connection { + /// Peer address. + pub address: net::SocketAddr, + /// Connection stream. + pub stream: SharedTcpStream, + /// Peer node id. + pub node_id: NodeId, + /// Encryption key. + pub key: Secret, +} diff --git a/secret_store/src/key_server_cluster/net/mod.rs b/secret_store/src/key_server_cluster/net/mod.rs new file mode 100644 index 000000000..6abf83ceb --- /dev/null +++ b/secret_store/src/key_server_cluster/net/mod.rs @@ -0,0 +1,23 @@ +// 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 . + +mod accept_connection; +mod connect; +mod connection; + +pub use self::accept_connection::{AcceptConnection, accept_connection}; +pub use self::connect::{Connect, connect}; +pub use self::connection::Connection; diff --git a/secret_store/src/key_storage.rs b/secret_store/src/key_storage.rs index fe7777410..e3106f221 100644 --- a/secret_store/src/key_storage.rs +++ b/secret_store/src/key_storage.rs @@ -15,15 +15,34 @@ // along with Parity. If not, see . use std::path::PathBuf; +use std::collections::BTreeMap; +use serde_json; +use ethkey::{Secret, Public}; use util::Database; -use types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey}; +use types::all::{Error, ServiceConfiguration, DocumentAddress, NodeId}; +use serialization::{SerializablePublic, SerializableSecret}; + +#[derive(Debug, Clone, PartialEq)] +/// Encrypted key share, stored by key storage on the single key server. +pub struct DocumentKeyShare { + /// Decryption threshold (at least threshold + 1 nodes are required to decrypt data). + pub threshold: usize, + /// Nodes ids numbers. + pub id_numbers: BTreeMap, + /// Node secret share. + pub secret_share: Secret, + /// Common (shared) encryption point. + pub common_point: Public, + /// Encrypted point. + pub encrypted_point: Public, +} /// Document encryption keys storage pub trait KeyStorage: Send + Sync { /// Insert document encryption key - fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error>; + fn insert(&self, document: DocumentAddress, key: DocumentKeyShare) -> Result<(), Error>; /// Get document encryption key - fn get(&self, document: &DocumentAddress) -> Result; + fn get(&self, document: &DocumentAddress) -> Result; } /// Persistent document encryption keys storage @@ -31,6 +50,21 @@ pub struct PersistentKeyStorage { db: Database, } +#[derive(Serialize, Deserialize)] +/// Encrypted key share, as it is stored by key storage on the single key server. +struct SerializableDocumentKeyShare { + /// Decryption threshold (at least threshold + 1 nodes are required to decrypt data). + pub threshold: usize, + /// Nodes ids numbers. + pub id_numbers: BTreeMap, + /// Node secret share. + pub secret_share: SerializableSecret, + /// Common (shared) encryption point. + pub common_point: SerializablePublic, + /// Encrypted point. + pub encrypted_point: SerializablePublic, +} + impl PersistentKeyStorage { /// Create new persistent document encryption keys storage pub fn new(config: &ServiceConfiguration) -> Result { @@ -45,41 +79,71 @@ impl PersistentKeyStorage { } impl KeyStorage for PersistentKeyStorage { - fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> { + fn insert(&self, document: DocumentAddress, key: DocumentKeyShare) -> Result<(), Error> { + let key: SerializableDocumentKeyShare = key.into(); + let key = serde_json::to_vec(&key).map_err(|e| Error::Database(e.to_string()))?; let mut batch = self.db.transaction(); batch.put(None, &document, &key); self.db.write(batch).map_err(Error::Database) } - fn get(&self, document: &DocumentAddress) -> Result { + fn get(&self, document: &DocumentAddress) -> Result { self.db.get(None, document) .map_err(Error::Database)? .ok_or(Error::DocumentNotFound) .map(|key| key.to_vec()) + .and_then(|key| serde_json::from_slice::(&key).map_err(|e| Error::Database(e.to_string()))) + .map(Into::into) + } +} + +impl From for SerializableDocumentKeyShare { + fn from(key: DocumentKeyShare) -> Self { + SerializableDocumentKeyShare { + threshold: key.threshold, + id_numbers: key.id_numbers.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), + secret_share: key.secret_share.into(), + common_point: key.common_point.into(), + encrypted_point: key.encrypted_point.into(), + } + } +} + +impl From for DocumentKeyShare { + fn from(key: SerializableDocumentKeyShare) -> Self { + DocumentKeyShare { + threshold: key.threshold, + id_numbers: key.id_numbers.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), + secret_share: key.secret_share.into(), + common_point: key.common_point.into(), + encrypted_point: key.encrypted_point.into(), + } } } #[cfg(test)] pub mod tests { - use std::collections::HashMap; + use std::collections::{BTreeMap, HashMap}; use parking_lot::RwLock; use devtools::RandomTempPath; - use super::super::types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey}; - use super::{KeyStorage, PersistentKeyStorage}; + use ethkey::{Random, Generator}; + use super::super::types::all::{Error, NodeAddress, ServiceConfiguration, ClusterConfiguration, + DocumentAddress, EncryptionConfiguration}; + use super::{KeyStorage, PersistentKeyStorage, DocumentKeyShare}; #[derive(Default)] /// In-memory document encryption keys storage pub struct DummyKeyStorage { - keys: RwLock>, + keys: RwLock>, } impl KeyStorage for DummyKeyStorage { - fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> { + fn insert(&self, document: DocumentAddress, key: DocumentKeyShare) -> Result<(), Error> { self.keys.write().insert(document, key); Ok(()) } - fn get(&self, document: &DocumentAddress) -> Result { + fn get(&self, document: &DocumentAddress) -> Result { self.keys.read().get(document).cloned().ok_or(Error::DocumentNotFound) } } @@ -88,15 +152,46 @@ pub mod tests { fn persistent_key_storage() { let path = RandomTempPath::create_dir(); let config = ServiceConfiguration { - listener_addr: "0.0.0.0".to_owned(), - listener_port: 8082, + listener_address: NodeAddress { + address: "0.0.0.0".to_owned(), + port: 8082, + }, data_path: path.as_str().to_owned(), + cluster_config: ClusterConfiguration { + threads: 1, + self_private: (**Random.generate().unwrap().secret().clone()).into(), + listener_address: NodeAddress { + address: "0.0.0.0".to_owned(), + port: 8083, + }, + nodes: BTreeMap::new(), + allow_connecting_to_higher_nodes: false, + encryption_config: EncryptionConfiguration { + key_check_timeout_ms: 10, + }, + }, }; let key1 = DocumentAddress::from(1); - let value1: DocumentKey = vec![0x77, 0x88]; + let value1 = DocumentKeyShare { + threshold: 100, + id_numbers: vec![ + (Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()) + ].into_iter().collect(), + secret_share: Random.generate().unwrap().secret().clone(), + common_point: Random.generate().unwrap().public().clone(), + encrypted_point: Random.generate().unwrap().public().clone(), + }; let key2 = DocumentAddress::from(2); - let value2: DocumentKey = vec![0x11, 0x22]; + let value2 = DocumentKeyShare { + threshold: 200, + id_numbers: vec![ + (Random.generate().unwrap().public().clone(), Random.generate().unwrap().secret().clone()) + ].into_iter().collect(), + secret_share: Random.generate().unwrap().secret().clone(), + common_point: Random.generate().unwrap().public().clone(), + encrypted_point: Random.generate().unwrap().public().clone(), + }; let key3 = DocumentAddress::from(3); let key_storage = PersistentKeyStorage::new(&config).unwrap(); diff --git a/secret_store/src/lib.rs b/secret_store/src/lib.rs index 41d658963..bbb8474d4 100644 --- a/secret_store/src/lib.rs +++ b/secret_store/src/lib.rs @@ -14,10 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +extern crate byteorder; #[macro_use] extern crate log; +#[macro_use] +extern crate futures; +extern crate futures_cpupool; extern crate hyper; extern crate parking_lot; +extern crate rustc_serialize; +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; +extern crate tokio_core; +extern crate tokio_service; +extern crate tokio_proto; extern crate url; extern crate ethcore_devtools as devtools; @@ -38,16 +50,19 @@ mod acl_storage; mod http_listener; mod key_server; mod key_storage; +mod serialization; pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public, - Error, ServiceConfiguration}; + Error, NodeAddress, ServiceConfiguration, ClusterConfiguration, EncryptionConfiguration}; pub use traits::{KeyServer}; /// Start new key server instance pub fn start(config: ServiceConfiguration) -> Result, Error> { - let acl_storage = acl_storage::DummyAclStorage::default(); - let key_storage = key_storage::PersistentKeyStorage::new(&config)?; - let key_server = key_server::KeyServerImpl::new(acl_storage, key_storage); + use std::sync::Arc; + + let acl_storage = Arc::new(acl_storage::DummyAclStorage::default()); + 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)?; Ok(Box::new(listener)) } diff --git a/secret_store/src/serialization.rs b/secret_store/src/serialization.rs new file mode 100644 index 000000000..0d0e904a7 --- /dev/null +++ b/secret_store/src/serialization.rs @@ -0,0 +1,260 @@ +// 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 . + +use std::fmt; +use std::cmp::{Ord, PartialOrd, Ordering}; +use std::ops::Deref; +use rustc_serialize::hex::ToHex; +use serde::{Serialize, Deserialize, Serializer, Deserializer}; +use serde::de::{Visitor, Error as SerdeError}; +use ethkey::{Public, Secret, Signature}; +use util::H256; + +#[derive(Clone, Debug)] +/// Serializable Signature. +pub struct SerializableSignature(Signature); + +impl From for SerializableSignature where Signature: From { + fn from(s: T) -> SerializableSignature { + SerializableSignature(s.into()) + } +} + +impl Into for SerializableSignature { + fn into(self) -> Signature { + self.0 + } +} + +impl Deref for SerializableSignature { + type Target = Signature; + + fn deref(&self) -> &Signature { + &self.0 + } +} + +impl Serialize for SerializableSignature { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + serializer.serialize_str(&(*self.0).to_hex()) + } +} + +impl Deserialize for SerializableSignature { + fn deserialize(deserializer: D) -> Result where D: Deserializer { + struct HashVisitor; + + impl Visitor for HashVisitor { + type Value = SerializableSignature; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a hex-encoded Signature") + } + + fn visit_str(self, value: &str) -> Result where E: SerdeError { + value.parse().map(|s| SerializableSignature(s)).map_err(SerdeError::custom) + } + + fn visit_string(self, value: String) -> Result where E: SerdeError { + self.visit_str(value.as_ref()) + } + } + + deserializer.deserialize(HashVisitor) + } +} + +#[derive(Clone, Debug)] +/// Serializable H256. +pub struct SerializableH256(H256); + +impl From for SerializableH256 where H256: From { + fn from(s: T) -> SerializableH256 { + SerializableH256(s.into()) + } +} + +impl Into for SerializableH256 { + fn into(self) -> H256 { + self.0 + } +} + +impl Deref for SerializableH256 { + type Target = H256; + + fn deref(&self) -> &H256 { + &self.0 + } +} + +impl Serialize for SerializableH256 { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + serializer.serialize_str(&(*self.0).to_hex()) + } +} + +impl Deserialize for SerializableH256 { + fn deserialize(deserializer: D) -> Result where D: Deserializer { + struct HashVisitor; + + impl Visitor for HashVisitor { + type Value = SerializableH256; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a hex-encoded H256") + } + + fn visit_str(self, value: &str) -> Result where E: SerdeError { + value.parse().map(|s| SerializableH256(s)).map_err(SerdeError::custom) + } + + fn visit_string(self, value: String) -> Result where E: SerdeError { + self.visit_str(value.as_ref()) + } + } + + deserializer.deserialize(HashVisitor) + } +} + +#[derive(Clone, Debug)] +/// Serializable EC scalar/secret key. +pub struct SerializableSecret(Secret); + +impl From for SerializableSecret where Secret: From { + fn from(s: T) -> SerializableSecret { + SerializableSecret(s.into()) + } +} + +impl Into for SerializableSecret { + fn into(self) -> Secret { + self.0 + } +} + +impl Deref for SerializableSecret { + type Target = Secret; + + fn deref(&self) -> &Secret { + &self.0 + } +} + +impl Serialize for SerializableSecret { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + serializer.serialize_str(&(*self.0).to_hex()) + } +} + +impl Deserialize for SerializableSecret { + fn deserialize(deserializer: D) -> Result where D: Deserializer { + struct HashVisitor; + + impl Visitor for HashVisitor { + type Value = SerializableSecret; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a hex-encoded EC scalar") + } + + fn visit_str(self, value: &str) -> Result where E: SerdeError { + value.parse().map(|s| SerializableSecret(s)).map_err(SerdeError::custom) + } + + fn visit_string(self, value: String) -> Result where E: SerdeError { + self.visit_str(value.as_ref()) + } + } + + deserializer.deserialize(HashVisitor) + } +} + +#[derive(Clone, Debug)] +/// Serializable EC point/public key. +pub struct SerializablePublic(Public); + +impl From for SerializablePublic where Public: From { + fn from(p: T) -> SerializablePublic { + SerializablePublic(p.into()) + } +} + +impl Into for SerializablePublic { + fn into(self) -> Public { + self.0 + } +} + +impl Deref for SerializablePublic { + type Target = Public; + + fn deref(&self) -> &Public { + &self.0 + } +} + +impl Eq for SerializablePublic { } + +impl PartialEq for SerializablePublic { + fn eq(&self, other: &SerializablePublic) -> bool { + self.0.eq(&other.0) + } +} + +impl Ord for SerializablePublic { + fn cmp(&self, other: &SerializablePublic) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for SerializablePublic { + fn partial_cmp(&self, other: &SerializablePublic) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Serialize for SerializablePublic { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + serializer.serialize_str(&(*self.0).to_hex()) + } +} + +impl Deserialize for SerializablePublic { + fn deserialize(deserializer: D) -> Result where D: Deserializer { + struct HashVisitor; + + impl Visitor for HashVisitor { + type Value = SerializablePublic; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a hex-encoded EC point") + } + + fn visit_str(self, value: &str) -> Result where E: SerdeError { + value.parse().map(|s| SerializablePublic(s)).map_err(SerdeError::custom) + } + + fn visit_string(self, value: String) -> Result where E: SerdeError { + self.visit_str(value.as_ref()) + } + } + + deserializer.deserialize(HashVisitor) + } +} diff --git a/secret_store/src/traits.rs b/secret_store/src/traits.rs index 9a68e9c4d..1a407e5c7 100644 --- a/secret_store/src/traits.rs +++ b/secret_store/src/traits.rs @@ -19,6 +19,8 @@ use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey} #[ipc(client_ident="RemoteKeyServer")] /// Secret store key server pub trait KeyServer: Send + Sync { + /// Generate encryption key for given document. + fn generate_document_key(&self, signature: &RequestSignature, document: &DocumentAddress, threshold: usize) -> Result; /// Request encryption key of given document for given requestor fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result; } diff --git a/secret_store/src/types/all.rs b/secret_store/src/types/all.rs index f318e6543..514b4eb6b 100644 --- a/secret_store/src/types/all.rs +++ b/secret_store/src/types/all.rs @@ -15,10 +15,14 @@ // along with Parity. If not, see . use std::fmt; +use std::collections::BTreeMap; use ethkey; use util; +use key_server_cluster; +/// Node id. +pub type NodeId = ethkey::Public; /// Document address type. pub type DocumentAddress = util::H256; /// Document key type. @@ -46,16 +50,53 @@ pub enum Error { Internal(String), } +#[derive(Debug)] +#[binary] +/// Secret store configuration +pub struct NodeAddress { + /// IP address. + pub address: String, + /// IP port. + pub port: u16, +} + #[derive(Debug)] #[binary] /// Secret store configuration pub struct ServiceConfiguration { - /// Interface to listen to - pub listener_addr: String, - /// Port to listen to - pub listener_port: u16, + /// HTTP listener address. + pub listener_address: NodeAddress, /// Data directory path for secret store pub data_path: String, + /// Cluster configuration. + pub cluster_config: ClusterConfiguration, +} + +#[derive(Debug)] +#[binary] +/// Key server cluster configuration +pub struct ClusterConfiguration { + /// Number of threads reserved by cluster. + pub threads: usize, + /// Private key this node holds. + pub self_private: Vec, // holds ethkey::Secret + /// This node address. + pub listener_address: NodeAddress, + /// All cluster nodes addresses. + pub nodes: BTreeMap, + /// Allow outbound connections to 'higher' nodes. + /// This is useful for tests, but slower a bit for production. + pub allow_connecting_to_higher_nodes: bool, + /// Encryption session configuration. + pub encryption_config: EncryptionConfiguration, +} + +#[derive(Clone, Debug)] +#[binary] +/// Encryption parameters. +pub struct EncryptionConfiguration { + /// Key check timeout. + pub key_check_timeout_ms: u64, } impl fmt::Display for Error { @@ -70,6 +111,18 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(err: ethkey::Error) -> Self { + Error::Internal(err.into()) + } +} + +impl From for Error { + fn from(err: key_server_cluster::Error) -> Self { + Error::Internal(err.into()) + } +} + impl Into for Error { fn into(self) -> String { format!("{}", self)