From ec96091369dc254df3c3762134136aa4ab6d2577 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 3 Apr 2018 17:54:34 +0300 Subject: [PATCH] SecretStore: generating and retrieving decryption keys via service contract (#8029) * SecretStore: started document keys generation via contract * fixed Cargo.lock * SecretStore: doc key contract gen tests * SecretStore: fixed log parsing * SecretStore: flush * SecretStore: secretstore_generateDocumentKey RPC * SecretStore: return encrypted_key from secretstore_generateDocumentKey * prepare to GenerateDocKey -> StoreDocKey * SecretStore: ability to identify requester via Public/Address * SecretStore: store author address instead of public in db * flush * SecretStore: flush * SecretStore: fixed test * SecretStore: flush * SecretStore: flush * SecretStore: flush * SecretStore: flush * SecretStore: start async generation session * SecretStore: process StoreDocumentKey service tasks * SecretStore: flush * SecretStore: update service contact ABI * SecretStore: flush * SecretStore: flush * SecretStore: fixed event * SecretStore: flush * SecretStore: fixed tests * SecretStore: fix broadcast shadows decryption * SecretStore: finally decryption via service contract works * SecretStore: fix for updated contract * SecretStore: restored pending requests reqding * SecretStore: fixed some TODOs * SecretStore: OnChainServiceContractAggregate * SecretStore: different names for different contracts types * SecretStore: updated contracts interfaces * SecretStore: utilize aggregate service contract * fixed compilation * SecretStore: fixes for updated contract * SecretStore: service fixes after testing * fixed cli test compilation * SecretStore: decryption_session_origin_is_known_to_all_initialized_nodes * SecretStore: added new contract listener tests * SecretStore: session_listener_works * removed optional TODO * SecretStore: fixed KeyServer shutdown * fixed warn + grumble * const durations --- parity/cli/mod.rs | 30 +- parity/cli/tests/config.full.toml | 4 + parity/configuration.rs | 34 +- parity/secretstore.rs | 28 +- secret_store/res/key_server_set.json | 25 +- secret_store/res/service.json | 35 +- secret_store/src/acl_storage.rs | 26 +- secret_store/src/key_server.rs | 152 ++-- .../key_version_negotiation_session.rs | 7 +- .../servers_set_change_session.rs | 5 +- .../client_sessions/decryption_session.rs | 237 ++++-- .../client_sessions/encryption_session.rs | 59 +- .../client_sessions/generation_session.rs | 39 +- .../client_sessions/signing_session_ecdsa.rs | 19 +- .../signing_session_schnorr.rs | 25 +- .../src/key_server_cluster/cluster.rs | 70 +- .../key_server_cluster/cluster_sessions.rs | 40 +- .../jobs/consensus_session.rs | 30 +- .../key_server_cluster/jobs/decryption_job.rs | 4 +- .../key_server_cluster/jobs/job_session.rs | 75 +- .../key_server_cluster/jobs/key_access_job.rs | 2 +- .../src/key_server_cluster/message.rs | 6 + secret_store/src/key_server_cluster/mod.rs | 4 +- secret_store/src/key_storage.rs | 4 + secret_store/src/lib.rs | 81 +- secret_store/src/listener/http_listener.rs | 45 +- secret_store/src/listener/mod.rs | 60 +- secret_store/src/listener/service_contract.rs | 787 +++++++++++++++--- .../listener/service_contract_aggregate.rs | 100 +++ .../src/listener/service_contract_listener.rs | 724 ++++++++++++---- secret_store/src/traits.rs | 28 +- secret_store/src/types/all.rs | 44 +- 32 files changed, 2132 insertions(+), 697 deletions(-) create mode 100644 secret_store/src/listener/service_contract_aggregate.rs diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 7b26aa6bd..6e21238e6 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -573,7 +573,23 @@ usage! { ARG arg_secretstore_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract.clone(), "--secretstore-contract=[SOURCE]", - "Secret Store Service contract address source: none, registry (contract address is read from registry) or address.", + "Secret Store Service contract address source: none, registry (contract address is read from secretstore_service entry in registry) or address.", + + ARG arg_secretstore_srv_gen_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_srv_gen.clone(), + "--secretstore-srv-gen-contract=[SOURCE]", + "Secret Store Service server key generation contract address source: none, registry (contract address is read from secretstore_service_srv_gen entry in registry) or address.", + + ARG arg_secretstore_srv_retr_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_srv_retr.clone(), + "--secretstore-srv-retr-contract=[SOURCE]", + "Secret Store Service server key retrieval contract address source: none, registry (contract address is read from secretstore_service_srv_retr entry in registry) or address.", + + ARG arg_secretstore_doc_store_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_doc_store.clone(), + "--secretstore-doc-store-contract=[SOURCE]", + "Secret Store Service document key store contract address source: none, registry (contract address is read from secretstore_service_doc_store entry in registry) or address.", + + ARG arg_secretstore_doc_sretr_contract: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.service_contract_doc_sretr.clone(), + "--secretstore-doc-sretr-contract=[SOURCE]", + "Secret Store Service document key shadow retrieval contract address source: none, registry (contract address is read from secretstore_service_doc_sretr entry in registry) or address.", ARG arg_secretstore_nodes: (String) = "", or |c: &Config| c.secretstore.as_ref()?.nodes.as_ref().map(|vec| vec.join(",")), "--secretstore-nodes=[NODES]", @@ -1133,6 +1149,10 @@ struct SecretStore { disable_acl_check: Option, disable_auto_migrate: Option, service_contract: Option, + service_contract_srv_gen: Option, + service_contract_srv_retr: Option, + service_contract_doc_store: Option, + service_contract_doc_sretr: Option, self_secret: Option, admin_public: Option, nodes: Option>, @@ -1554,6 +1574,10 @@ mod tests { flag_no_secretstore_acl_check: false, flag_no_secretstore_auto_migrate: false, arg_secretstore_contract: "none".into(), + arg_secretstore_srv_gen_contract: "none".into(), + arg_secretstore_srv_retr_contract: "none".into(), + arg_secretstore_doc_store_contract: "none".into(), + arg_secretstore_doc_sretr_contract: "none".into(), arg_secretstore_secret: None, arg_secretstore_admin_public: None, arg_secretstore_nodes: "".into(), @@ -1812,6 +1836,10 @@ mod tests { disable_acl_check: None, disable_auto_migrate: None, service_contract: None, + service_contract_srv_gen: None, + service_contract_srv_retr: None, + service_contract_doc_store: None, + service_contract_doc_sretr: None, self_secret: None, admin_public: None, nodes: None, diff --git a/parity/cli/tests/config.full.toml b/parity/cli/tests/config.full.toml index 749ffd851..702a18631 100644 --- a/parity/cli/tests/config.full.toml +++ b/parity/cli/tests/config.full.toml @@ -83,6 +83,10 @@ disable = false disable_http = false disable_acl_check = false service_contract = "none" +service_contract_srv_gen = "none" +service_contract_srv_retr = "none" +service_contract_doc_store = "none" +service_contract_doc_sretr = "none" nodes = [] http_interface = "local" http_port = 8082 diff --git a/parity/configuration.rs b/parity/configuration.rs index db8759a71..f242edb0f 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -635,6 +635,10 @@ impl Configuration { acl_check_enabled: self.secretstore_acl_check_enabled(), auto_migrate_enabled: self.secretstore_auto_migrate_enabled(), service_contract_address: self.secretstore_service_contract_address()?, + service_contract_srv_gen_address: self.secretstore_service_contract_srv_gen_address()?, + service_contract_srv_retr_address: self.secretstore_service_contract_srv_retr_address()?, + service_contract_doc_store_address: self.secretstore_service_contract_doc_store_address()?, + service_contract_doc_sretr_address: self.secretstore_service_contract_doc_sretr_address()?, self_secret: self.secretstore_self_secret()?, nodes: self.secretstore_nodes()?, interface: self.secretstore_interface(), @@ -1127,11 +1131,23 @@ impl Configuration { } fn secretstore_service_contract_address(&self) -> Result, String> { - Ok(match self.args.arg_secretstore_contract.as_ref() { - "none" => None, - "registry" => Some(SecretStoreContractAddress::Registry), - a => Some(SecretStoreContractAddress::Address(a.parse().map_err(|e| format!("{}", e))?)), - }) + into_secretstore_service_contract_address(self.args.arg_secretstore_contract.as_ref()) + } + + fn secretstore_service_contract_srv_gen_address(&self) -> Result, String> { + into_secretstore_service_contract_address(self.args.arg_secretstore_srv_gen_contract.as_ref()) + } + + fn secretstore_service_contract_srv_retr_address(&self) -> Result, String> { + into_secretstore_service_contract_address(self.args.arg_secretstore_srv_retr_contract.as_ref()) + } + + fn secretstore_service_contract_doc_store_address(&self) -> Result, String> { + into_secretstore_service_contract_address(self.args.arg_secretstore_doc_store_contract.as_ref()) + } + + fn secretstore_service_contract_doc_sretr_address(&self) -> Result, String> { + into_secretstore_service_contract_address(self.args.arg_secretstore_doc_sretr_contract.as_ref()) } fn ui_enabled(&self) -> bool { @@ -1164,6 +1180,14 @@ impl Configuration { } } +fn into_secretstore_service_contract_address(s: &str) -> Result, String> { + match s { + "none" => Ok(None), + "registry" => Ok(Some(SecretStoreContractAddress::Registry)), + a => Ok(Some(SecretStoreContractAddress::Address(a.parse().map_err(|e| format!("{}", e))?))), + } +} + #[cfg(test)] mod tests { use std::io::Write; diff --git a/parity/secretstore.rs b/parity/secretstore.rs index c71f2c14d..797f8673b 100644 --- a/parity/secretstore.rs +++ b/parity/secretstore.rs @@ -55,6 +55,14 @@ pub struct Configuration { pub auto_migrate_enabled: bool, /// Service contract address. pub service_contract_address: Option, + /// Server key generation service contract address. + pub service_contract_srv_gen_address: Option, + /// Server key retrieval service contract address. + pub service_contract_srv_retr_address: Option, + /// Document key store service contract address. + pub service_contract_doc_store_address: Option, + /// Document key shadow retrieval service contract address. + pub service_contract_doc_sretr_address: Option, /// This node secret. pub self_secret: Option, /// Other nodes IDs + addresses. @@ -108,6 +116,13 @@ mod server { use ansi_term::Colour::Red; use super::{Configuration, Dependencies, NodeSecretKey, ContractAddress}; + fn into_service_contract_address(address: ContractAddress) -> ethcore_secretstore::ContractAddress { + match address { + ContractAddress::Registry => ethcore_secretstore::ContractAddress::Registry, + ContractAddress::Address(address) => ethcore_secretstore::ContractAddress::Address(address), + } + } + /// Key server pub struct KeyServer { _key_server: Box, @@ -150,10 +165,11 @@ mod server { address: conf.http_interface.clone(), port: conf.http_port, }) } else { None }, - service_contract_address: conf.service_contract_address.map(|c| match c { - ContractAddress::Registry => ethcore_secretstore::ContractAddress::Registry, - ContractAddress::Address(address) => ethcore_secretstore::ContractAddress::Address(address), - }), + service_contract_address: conf.service_contract_address.map(into_service_contract_address), + service_contract_srv_gen_address: conf.service_contract_srv_gen_address.map(into_service_contract_address), + service_contract_srv_retr_address: conf.service_contract_srv_retr_address.map(into_service_contract_address), + service_contract_doc_store_address: conf.service_contract_doc_store_address.map(into_service_contract_address), + service_contract_doc_sretr_address: conf.service_contract_doc_sretr_address.map(into_service_contract_address), data_path: conf.data_path.clone(), acl_check_enabled: conf.acl_check_enabled, cluster_config: ethcore_secretstore::ClusterConfiguration { @@ -195,6 +211,10 @@ impl Default for Configuration { acl_check_enabled: true, auto_migrate_enabled: true, service_contract_address: None, + service_contract_srv_gen_address: None, + service_contract_srv_retr_address: None, + service_contract_doc_store_address: None, + service_contract_doc_sretr_address: None, self_secret: None, admin_public: None, nodes: BTreeMap::new(), diff --git a/secret_store/res/key_server_set.json b/secret_store/res/key_server_set.json index 0fb9502b1..28530e353 100644 --- a/secret_store/res/key_server_set.json +++ b/secret_store/res/key_server_set.json @@ -1 +1,24 @@ -[{"constant":true,"inputs":[],"name":"getMigrationMaster","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"startMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMigrationId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNewKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"confirmMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getMigrationKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"isMigrationConfirmed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerRemoved","type":"event"},{"anonymous":false,"inputs":[],"name":"MigrationStarted","type":"event"},{"anonymous":false,"inputs":[],"name":"MigrationCompleted","type":"event"}] \ No newline at end of file +[ + {"constant":true,"inputs":[],"name":"getMigrationMaster","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"startMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerIndex","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getMigrationKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[],"name":"getMigrationId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[],"name":"getNewKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"confirmMigration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[],"name":"getMigrationKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"isMigrationConfirmed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[],"name":"getCurrentKeyServersCount","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[],"name":"getCurrentKeyServers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[],"name":"getCurrentLastChange","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getCurrentKeyServerAddress","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"getNewKeyServerPublic","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"index","type":"uint8"}],"name":"getCurrentKeyServer","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}, + {"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerAdded","type":"event"}, + {"anonymous":false,"inputs":[{"indexed":false,"name":"keyServer","type":"address"}],"name":"KeyServerRemoved","type":"event"}, + {"anonymous":false,"inputs":[],"name":"MigrationStarted","type":"event"}, + {"anonymous":false,"inputs":[],"name":"MigrationCompleted","type":"event"} +] \ No newline at end of file diff --git a/secret_store/res/service.json b/secret_store/res/service.json index 37d45350b..d79c38e7a 100644 --- a/secret_store/res/service.json +++ b/secret_store/res/service.json @@ -1,8 +1,33 @@ [ + {"constant":true,"inputs":[{"name":"keyServer","type":"address"}],"name":"requireKeyServer","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[],"name":"serverKeyGenerationRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, - {"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getServerKeyId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}, - {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"serverKeyPublic","type":"bytes"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"name":"serverKeyGenerated","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, - {"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"getServerKeyThreshold","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, - {"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"authority","type":"address"}],"name":"getServerKeyConfirmationStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}, - {"anonymous":false,"inputs":[{"indexed":true,"name":"serverKeyId","type":"bytes32"},{"indexed":true,"name":"threshold","type":"uint256"}],"name":"ServerKeyRequested","type":"event"} + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"serverKeyGenerationError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getServerKeyGenerationRequest","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"serverKeyPublic","type":"bytes"}],"name":"serverKeyGenerated","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"}],"name":"isServerKeyGenerationResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}, + {"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"author","type":"address"},{"indexed":false,"name":"threshold","type":"uint8"}],"name":"ServerKeyGenerationRequested","type":"event"}, + + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"serverKeyRetrievalError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[],"name":"serverKeyRetrievalRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"}],"name":"isServerKeyRetrievalResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getServerKeyRetrievalRequest","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"serverKeyPublic","type":"bytes"},{"name":"threshold","type":"uint8"}],"name":"serverKeyRetrieved","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"}],"name":"ServerKeyRetrievalRequested","type":"event"}, + + {"constant":true,"inputs":[],"name":"documentKeyStoreRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"documentKeyStoreError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"}],"name":"documentKeyStored","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"}],"name":"isDocumentKeyStoreResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getDocumentKeyStoreRequest","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"address"},{"name":"","type":"bytes"},{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"}, + {"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"author","type":"address"},{"indexed":false,"name":"commonPoint","type":"bytes"},{"indexed":false,"name":"encryptedPoint","type":"bytes"}],"name":"DocumentKeyStoreRequested","type":"event"}, + + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"requester","type":"address"},{"name":"commonPoint","type":"bytes"},{"name":"threshold","type":"uint8"}],"name":"documentKeyCommonRetrieved","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"keyServer","type":"address"},{"name":"requester","type":"address"}],"name":"isDocumentKeyShadowRetrievalResponseRequired","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"requester","type":"address"},{"name":"participants","type":"uint256"},{"name":"decryptedSecret","type":"bytes"},{"name":"shadow","type":"bytes"}],"name":"documentKeyPersonalRetrieved","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":false,"inputs":[{"name":"serverKeyId","type":"bytes32"},{"name":"requester","type":"address"}],"name":"documentKeyShadowRetrievalError","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":true,"inputs":[],"name":"documentKeyShadowRetrievalRequestsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}, + {"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getDocumentKeyShadowRetrievalRequest","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bytes"},{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}, + {"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"requester","type":"address"}],"name":"DocumentKeyCommonRetrievalRequested","type":"event"}, + {"anonymous":false,"inputs":[{"indexed":false,"name":"serverKeyId","type":"bytes32"},{"indexed":false,"name":"requesterPublic","type":"bytes"}],"name":"DocumentKeyPersonalRetrievalRequested","type":"event"} ] \ No newline at end of file diff --git a/secret_store/src/acl_storage.rs b/secret_store/src/acl_storage.rs index f3d116145..0ff8a2f30 100644 --- a/secret_store/src/acl_storage.rs +++ b/secret_store/src/acl_storage.rs @@ -17,12 +17,11 @@ use std::sync::Arc; use std::collections::{HashMap, HashSet}; use parking_lot::{Mutex, RwLock}; -use ethkey::public_to_address; use ethcore::client::{BlockId, ChainNotify, CallContract, RegistryInfo}; use ethereum_types::{H256, Address}; use bytes::Bytes; use trusted_client::TrustedClient; -use types::all::{Error, ServerKeyId, Public}; +use types::all::{Error, ServerKeyId}; use_contract!(acl_storage, "AclStorage", "res/acl_storage.json"); @@ -30,8 +29,8 @@ const ACL_CHECKER_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_acl_checke /// ACL storage of Secret Store pub trait AclStorage: Send + Sync { - /// Check if requestor with `public` key can access document with hash `document` - fn check(&self, public: &Public, document: &ServerKeyId) -> Result; + /// Check if requestor can access document with hash `document` + fn check(&self, requester: Address, document: &ServerKeyId) -> Result; } /// On-chain ACL storage implementation. @@ -53,7 +52,7 @@ struct CachedContract { /// Dummy ACL storage implementation (check always passed). #[derive(Default, Debug)] pub struct DummyAclStorage { - prohibited: RwLock>>, + prohibited: RwLock>>, } impl OnChainAclStorage { @@ -70,8 +69,8 @@ impl OnChainAclStorage { } impl AclStorage for OnChainAclStorage { - fn check(&self, public: &Public, document: &ServerKeyId) -> Result { - self.contract.lock().check(public, document) + fn check(&self, requester: Address, document: &ServerKeyId) -> Result { + self.contract.lock().check(requester, document) } } @@ -104,16 +103,15 @@ impl CachedContract { } } - pub fn check(&mut self, public: &Public, document: &ServerKeyId) -> Result { + pub fn check(&mut self, requester: Address, document: &ServerKeyId) -> Result { if let Some(client) = self.client.get() { // call contract to check accesss match self.contract_addr { Some(contract_address) => { - let address = public_to_address(&public); let do_call = |data| client.call_contract(BlockId::Latest, contract_address, data); self.contract.functions() .check_permissions() - .call(address, document.clone(), &do_call) + .call(requester, document.clone(), &do_call) .map_err(|e| Error::Internal(e.to_string())) }, None => Err(Error::Internal("ACL checker contract is not configured".to_owned())), @@ -127,18 +125,18 @@ impl CachedContract { impl DummyAclStorage { /// Prohibit given requestor access to given documents #[cfg(test)] - pub fn prohibit(&self, public: Public, document: ServerKeyId) { + pub fn prohibit(&self, requester: Address, document: ServerKeyId) { self.prohibited.write() - .entry(public) + .entry(requester) .or_insert_with(Default::default) .insert(document); } } impl AclStorage for DummyAclStorage { - fn check(&self, public: &Public, document: &ServerKeyId) -> Result { + fn check(&self, requester: Address, document: &ServerKeyId) -> Result { Ok(self.prohibited.read() - .get(public) + .get(&requester) .map(|docs| !docs.contains(document)) .unwrap_or(true)) } diff --git a/secret_store/src/key_server.rs b/secret_store/src/key_server.rs index 181df0caa..02d19eede 100644 --- a/secret_store/src/key_server.rs +++ b/secret_store/src/key_server.rs @@ -22,13 +22,12 @@ 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 super::key_server_set::KeyServerSet; use key_server_cluster::{math, ClusterCore}; use traits::{AdminSessionsServer, ServerKeyGenerator, DocumentKeyServer, MessageSigner, KeyServer, NodeKeyPair}; -use types::all::{Error, Public, RequestSignature, ServerKeyId, EncryptedDocumentKey, EncryptedDocumentKeyShadow, +use types::all::{Error, Public, RequestSignature, Requester, ServerKeyId, EncryptedDocumentKey, EncryptedDocumentKeyShadow, ClusterConfiguration, MessageHash, EncryptedMessageSignature, NodeId}; use key_server_cluster::{ClusterClient, ClusterConfiguration as NetClusterConfiguration}; @@ -71,39 +70,39 @@ impl AdminSessionsServer for KeyServerImpl { } impl ServerKeyGenerator for KeyServerImpl { - fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { + fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result { // recover requestor' public key from signature - let public = ethkey::recover(signature, key_id) - .map_err(|_| Error::BadSignature)?; + let address = author.address(key_id).map_err(Error::InsufficientRequesterData)?; // generate server key - let generation_session = self.data.lock().cluster.new_generation_session(key_id.clone(), public, threshold)?; - generation_session.wait(None).map_err(Into::into) + let generation_session = self.data.lock().cluster.new_generation_session(key_id.clone(), None, address, threshold)?; + generation_session.wait(None) + .expect("when wait is called without timeout it always returns Some; qed") + .map_err(Into::into) } } impl DocumentKeyServer for KeyServerImpl { - fn store_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> { + fn store_document_key(&self, key_id: &ServerKeyId, author: &Requester, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> { // store encrypted key let encryption_session = self.data.lock().cluster.new_encryption_session(key_id.clone(), - signature.clone().into(), common_point, encrypted_document_key)?; + author.clone(), common_point, encrypted_document_key)?; encryption_session.wait(None).map_err(Into::into) } - fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { + fn generate_document_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result { // recover requestor' public key from signature - let public = ethkey::recover(signature, key_id) - .map_err(|_| Error::BadSignature)?; + let public = author.public(key_id).map_err(Error::InsufficientRequesterData)?; // generate server key - let server_key = self.generate_key(key_id, signature, threshold)?; + let server_key = self.generate_key(key_id, author, threshold)?; // generate random document key let document_key = math::generate_random_point()?; let encrypted_document_key = math::encrypt_secret(&document_key, &server_key)?; // store document key in the storage - self.store_document_key(key_id, signature, encrypted_document_key.common_point, encrypted_document_key.encrypted_point)?; + self.store_document_key(key_id, author, encrypted_document_key.common_point, encrypted_document_key.encrypted_point)?; // encrypt document key with requestor public key let document_key = ethcrypto::ecies::encrypt(&public, ðcrypto::DEFAULT_MAC, &document_key) @@ -111,15 +110,16 @@ impl DocumentKeyServer for KeyServerImpl { Ok(document_key) } - fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { + fn restore_document_key(&self, key_id: &ServerKeyId, requester: &Requester) -> Result { // recover requestor' public key from signature - let public = ethkey::recover(signature, key_id) - .map_err(|_| Error::BadSignature)?; + let public = requester.public(key_id).map_err(Error::InsufficientRequesterData)?; // decrypt document key let decryption_session = self.data.lock().cluster.new_decryption_session(key_id.clone(), - signature.clone().into(), None, false)?; - let document_key = decryption_session.wait()?.decrypted_secret; + None, requester.clone(), None, false, false)?; + let document_key = decryption_session.wait(None) + .expect("when wait is called without timeout it always returns Some; qed")? + .decrypted_secret; // encrypt document key with requestor public key let document_key = ethcrypto::ecies::encrypt(&public, ðcrypto::DEFAULT_MAC, &document_key) @@ -127,22 +127,23 @@ impl DocumentKeyServer for KeyServerImpl { Ok(document_key) } - fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { + fn restore_document_key_shadow(&self, key_id: &ServerKeyId, requester: &Requester) -> Result { let decryption_session = self.data.lock().cluster.new_decryption_session(key_id.clone(), - signature.clone().into(), None, true)?; - decryption_session.wait().map_err(Into::into) + None, requester.clone(), None, true, false)?; + decryption_session.wait(None) + .expect("when wait is called without timeout it always returns Some; qed") + .map_err(Into::into) } } impl MessageSigner for KeyServerImpl { - fn sign_message_schnorr(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result { + fn sign_message_schnorr(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result { // recover requestor' public key from signature - let public = ethkey::recover(signature, key_id) - .map_err(|_| Error::BadSignature)?; + let public = requester.public(key_id).map_err(Error::InsufficientRequesterData)?; // sign message let signing_session = self.data.lock().cluster.new_schnorr_signing_session(key_id.clone(), - signature.clone().into(), None, message)?; + requester.clone().into(), None, message)?; let message_signature = signing_session.wait()?; // compose two message signature components into single one @@ -156,14 +157,13 @@ impl MessageSigner for KeyServerImpl { Ok(message_signature) } - fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result { + fn sign_message_ecdsa(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result { // recover requestor' public key from signature - let public = ethkey::recover(signature, key_id) - .map_err(|_| Error::BadSignature)?; + let public = requester.public(key_id).map_err(Error::InsufficientRequesterData)?; // sign message let signing_session = self.data.lock().cluster.new_ecdsa_signing_session(key_id.clone(), - signature.clone().into(), None, message)?; + requester.clone().into(), None, message)?; let message_signature = signing_session.wait()?; // encrypt combined signature with requestor public key @@ -177,7 +177,7 @@ impl KeyServerCore { pub fn new(config: &ClusterConfiguration, key_server_set: Arc, self_key_pair: Arc, acl_storage: Arc, key_storage: Arc) -> Result { let config = NetClusterConfiguration { threads: config.threads, - self_key_pair: self_key_pair, + self_key_pair: self_key_pair.clone(), listen_address: (config.listener_address.address.clone(), config.listener_address.port), key_server_set: key_server_set, allow_connecting_to_higher_nodes: config.allow_connecting_to_higher_nodes, @@ -189,7 +189,7 @@ impl KeyServerCore { let (stop, stopped) = futures::oneshot(); let (tx, rx) = mpsc::channel(); - let handle = thread::spawn(move || { + let handle = thread::Builder::new().name("KeyServerLoop".into()).spawn(move || { let mut el = match Core::new() { Ok(el) => el, Err(e) => { @@ -202,7 +202,9 @@ impl KeyServerCore { 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)); - }); + + trace!(target: "secretstore_net", "{}: KeyServerLoop thread stopped", self_key_pair.public()); + }).map_err(|e| Error::Internal(format!("{}", e)))?; let cluster = rx.recv().map_err(|e| Error::Internal(format!("error initializing event loop: {}", e)))??; Ok(KeyServerCore { @@ -225,26 +227,25 @@ pub mod tests { use std::collections::BTreeSet; use std::time; use std::sync::Arc; - use std::sync::atomic::{AtomicUsize, Ordering}; use std::net::SocketAddr; use std::collections::BTreeMap; use ethcrypto; use ethkey::{self, Secret, Random, Generator, verify_public}; use acl_storage::DummyAclStorage; + use key_storage::KeyStorage; use key_storage::tests::DummyKeyStorage; use node_key_pair::PlainNodeKeyPair; use key_server_set::tests::MapKeyServerSet; use key_server_cluster::math; use ethereum_types::{H256, H520}; use types::all::{Error, Public, ClusterConfiguration, NodeAddress, RequestSignature, ServerKeyId, - EncryptedDocumentKey, EncryptedDocumentKeyShadow, MessageHash, EncryptedMessageSignature, NodeId}; + EncryptedDocumentKey, EncryptedDocumentKeyShadow, MessageHash, EncryptedMessageSignature, + Requester, NodeId}; use traits::{AdminSessionsServer, ServerKeyGenerator, DocumentKeyServer, MessageSigner, KeyServer}; use super::KeyServerImpl; #[derive(Default)] - pub struct DummyKeyServer { - pub generation_requests_count: AtomicUsize, - } + pub struct DummyKeyServer; impl KeyServer for DummyKeyServer {} @@ -255,41 +256,40 @@ pub mod tests { } impl ServerKeyGenerator for DummyKeyServer { - fn generate_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _threshold: usize) -> Result { - self.generation_requests_count.fetch_add(1, Ordering::Relaxed); - Err(Error::Internal("test error".into())) + fn generate_key(&self, _key_id: &ServerKeyId, _author: &Requester, _threshold: usize) -> Result { + unimplemented!("test-only") } } impl DocumentKeyServer for DummyKeyServer { - fn store_document_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _common_point: Public, _encrypted_document_key: Public) -> Result<(), Error> { + fn store_document_key(&self, _key_id: &ServerKeyId, _author: &Requester, _common_point: Public, _encrypted_document_key: Public) -> Result<(), Error> { unimplemented!("test-only") } - fn generate_document_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _threshold: usize) -> Result { + fn generate_document_key(&self, _key_id: &ServerKeyId, _author: &Requester, _threshold: usize) -> Result { unimplemented!("test-only") } - fn restore_document_key(&self, _key_id: &ServerKeyId, _signature: &RequestSignature) -> Result { + fn restore_document_key(&self, _key_id: &ServerKeyId, _requester: &Requester) -> Result { unimplemented!("test-only") } - fn restore_document_key_shadow(&self, _key_id: &ServerKeyId, _signature: &RequestSignature) -> Result { + fn restore_document_key_shadow(&self, _key_id: &ServerKeyId, _requester: &Requester) -> Result { unimplemented!("test-only") } } impl MessageSigner for DummyKeyServer { - fn sign_message_schnorr(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _message: MessageHash) -> Result { + fn sign_message_schnorr(&self, _key_id: &ServerKeyId, _requester: &Requester, _message: MessageHash) -> Result { unimplemented!("test-only") } - fn sign_message_ecdsa(&self, _key_id: &ServerKeyId, _signature: &RequestSignature, _message: MessageHash) -> Result { + fn sign_message_ecdsa(&self, _key_id: &ServerKeyId, _requester: &Requester, _message: MessageHash) -> Result { unimplemented!("test-only") } } - fn make_key_servers(start_port: u16, num_nodes: usize) -> Vec { + fn make_key_servers(start_port: u16, num_nodes: usize) -> (Vec, Vec>) { let key_pairs: Vec<_> = (0..num_nodes).map(|_| Random.generate().unwrap()).collect(); let configs: Vec<_> = (0..num_nodes).map(|i| ClusterConfiguration { threads: 1, @@ -309,11 +309,12 @@ pub mod tests { let key_servers_set: BTreeMap = configs[0].nodes.iter() .map(|(k, a)| (k.clone(), format!("{}:{}", a.address, a.port).parse().unwrap())) .collect(); + let key_storages = (0..num_nodes).map(|_| Arc::new(DummyKeyStorage::default())).collect::>(); let key_servers: Vec<_> = configs.into_iter().enumerate().map(|(i, cfg)| KeyServerImpl::new(&cfg, Arc::new(MapKeyServerSet::new(key_servers_set.clone())), Arc::new(PlainNodeKeyPair::new(key_pairs[i].clone())), Arc::new(DummyAclStorage::default()), - Arc::new(DummyKeyStorage::default())).unwrap() + key_storages[i].clone()).unwrap() ).collect(); // wait until connections are established. It is fast => do not bother with events here @@ -343,25 +344,25 @@ pub mod tests { } } - key_servers + (key_servers, key_storages) } #[test] fn document_key_generation_and_retrievement_works_over_network_with_single_node() { //::logger::init_log(); - let key_servers = make_key_servers(6070, 1); + let (key_servers, _) = make_key_servers(6070, 1); // generate document key let threshold = 0; let document = Random.generate().unwrap().secret().clone(); let secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&secret, &document).unwrap(); - let generated_key = key_servers[0].generate_document_key(&document, &signature, threshold).unwrap(); + let generated_key = key_servers[0].generate_document_key(&document, &signature.clone().into(), threshold).unwrap(); let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap(); // now let's try to retrieve key back for key_server in key_servers.iter() { - let retrieved_key = key_server.restore_document_key(&document, &signature).unwrap(); + let retrieved_key = key_server.restore_document_key(&document, &signature.clone().into()).unwrap(); let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap(); assert_eq!(retrieved_key, generated_key); } @@ -370,7 +371,7 @@ pub mod tests { #[test] fn document_key_generation_and_retrievement_works_over_network_with_3_nodes() { //::logger::init_log(); - let key_servers = make_key_servers(6080, 3); + let (key_servers, key_storages) = make_key_servers(6080, 3); let test_cases = [0, 1, 2]; for threshold in &test_cases { @@ -378,14 +379,18 @@ pub mod tests { let document = Random.generate().unwrap().secret().clone(); let secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&secret, &document).unwrap(); - let generated_key = key_servers[0].generate_document_key(&document, &signature, *threshold).unwrap(); + let generated_key = key_servers[0].generate_document_key(&document, &signature.clone().into(), *threshold).unwrap(); let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap(); // now let's try to retrieve key back - for key_server in key_servers.iter() { - let retrieved_key = key_server.restore_document_key(&document, &signature).unwrap(); + for (i, key_server) in key_servers.iter().enumerate() { + let retrieved_key = key_server.restore_document_key(&document, &signature.clone().into()).unwrap(); let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap(); assert_eq!(retrieved_key, generated_key); + + let key_share = key_storages[i].get(&document).unwrap().unwrap(); + assert!(key_share.common_point.is_some()); + assert!(key_share.encrypted_point.is_some()); } } } @@ -393,7 +398,7 @@ pub mod tests { #[test] fn server_key_generation_and_storing_document_key_works_over_network_with_3_nodes() { //::logger::init_log(); - let key_servers = make_key_servers(6090, 3); + let (key_servers, _) = make_key_servers(6090, 3); let test_cases = [0, 1, 2]; for threshold in &test_cases { @@ -401,18 +406,19 @@ pub mod tests { let server_key_id = Random.generate().unwrap().secret().clone(); let requestor_secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap(); - let server_public = key_servers[0].generate_key(&server_key_id, &signature, *threshold).unwrap(); + let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), *threshold).unwrap(); // generate document key (this is done by KS client so that document key is unknown to any KS) let generated_key = Random.generate().unwrap().public().clone(); let encrypted_document_key = math::encrypt_secret(&generated_key, &server_public).unwrap(); // store document key - key_servers[0].store_document_key(&server_key_id, &signature, encrypted_document_key.common_point, encrypted_document_key.encrypted_point).unwrap(); + key_servers[0].store_document_key(&server_key_id, &signature.clone().into(), + encrypted_document_key.common_point, encrypted_document_key.encrypted_point).unwrap(); // now let's try to retrieve key back for key_server in key_servers.iter() { - let retrieved_key = key_server.restore_document_key(&server_key_id, &signature).unwrap(); + let retrieved_key = key_server.restore_document_key(&server_key_id, &signature.clone().into()).unwrap(); let retrieved_key = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap(); let retrieved_key = Public::from_slice(&retrieved_key); assert_eq!(retrieved_key, generated_key); @@ -423,7 +429,7 @@ pub mod tests { #[test] fn server_key_generation_and_message_signing_works_over_network_with_3_nodes() { //::logger::init_log(); - let key_servers = make_key_servers(6100, 3); + let (key_servers, _) = make_key_servers(6100, 3); let test_cases = [0, 1, 2]; for threshold in &test_cases { @@ -431,11 +437,11 @@ pub mod tests { let server_key_id = Random.generate().unwrap().secret().clone(); let requestor_secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap(); - let server_public = key_servers[0].generate_key(&server_key_id, &signature, *threshold).unwrap(); + let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), *threshold).unwrap(); // sign message let message_hash = H256::from(42); - let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature, message_hash.clone()).unwrap(); + let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature.into(), message_hash.clone()).unwrap(); let combined_signature = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &combined_signature).unwrap(); let signature_c = Secret::from_slice(&combined_signature[..32]); let signature_s = Secret::from_slice(&combined_signature[32..]); @@ -448,21 +454,21 @@ pub mod tests { #[test] fn decryption_session_is_delegated_when_node_does_not_have_key_share() { //::logger::init_log(); - let key_servers = make_key_servers(6110, 3); + let (key_servers, _) = make_key_servers(6110, 3); // generate document key let threshold = 0; let document = Random.generate().unwrap().secret().clone(); let secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&secret, &document).unwrap(); - let generated_key = key_servers[0].generate_document_key(&document, &signature, threshold).unwrap(); + let generated_key = key_servers[0].generate_document_key(&document, &signature.clone().into(), threshold).unwrap(); let generated_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &generated_key).unwrap(); // remove key from node0 key_servers[0].cluster().key_storage().remove(&document).unwrap(); // now let's try to retrieve key back by requesting it from node0, so that session must be delegated - let retrieved_key = key_servers[0].restore_document_key(&document, &signature).unwrap(); + let retrieved_key = key_servers[0].restore_document_key(&document, &signature.into()).unwrap(); let retrieved_key = ethcrypto::ecies::decrypt(&secret, ðcrypto::DEFAULT_MAC, &retrieved_key).unwrap(); assert_eq!(retrieved_key, generated_key); } @@ -470,21 +476,21 @@ pub mod tests { #[test] fn schnorr_signing_session_is_delegated_when_node_does_not_have_key_share() { //::logger::init_log(); - let key_servers = make_key_servers(6114, 3); + let (key_servers, _) = make_key_servers(6114, 3); let threshold = 1; // generate server key let server_key_id = Random.generate().unwrap().secret().clone(); let requestor_secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap(); - let server_public = key_servers[0].generate_key(&server_key_id, &signature, threshold).unwrap(); + let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), threshold).unwrap(); // remove key from node0 key_servers[0].cluster().key_storage().remove(&server_key_id).unwrap(); // sign message let message_hash = H256::from(42); - let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature, message_hash.clone()).unwrap(); + let combined_signature = key_servers[0].sign_message_schnorr(&server_key_id, &signature.into(), message_hash.clone()).unwrap(); let combined_signature = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &combined_signature).unwrap(); let signature_c = Secret::from_slice(&combined_signature[..32]); let signature_s = Secret::from_slice(&combined_signature[32..]); @@ -496,21 +502,21 @@ pub mod tests { #[test] fn ecdsa_signing_session_is_delegated_when_node_does_not_have_key_share() { //::logger::init_log(); - let key_servers = make_key_servers(6117, 4); + let (key_servers, _) = make_key_servers(6117, 4); let threshold = 1; // generate server key let server_key_id = Random.generate().unwrap().secret().clone(); let requestor_secret = Random.generate().unwrap().secret().clone(); let signature = ethkey::sign(&requestor_secret, &server_key_id).unwrap(); - let server_public = key_servers[0].generate_key(&server_key_id, &signature, threshold).unwrap(); + let server_public = key_servers[0].generate_key(&server_key_id, &signature.clone().into(), threshold).unwrap(); // remove key from node0 key_servers[0].cluster().key_storage().remove(&server_key_id).unwrap(); // sign message let message_hash = H256::random(); - let signature = key_servers[0].sign_message_ecdsa(&server_key_id, &signature, message_hash.clone()).unwrap(); + let signature = key_servers[0].sign_message_ecdsa(&server_key_id, &signature.into(), message_hash.clone()).unwrap(); let signature = ethcrypto::ecies::decrypt(&requestor_secret, ðcrypto::DEFAULT_MAC, &signature).unwrap(); let signature: H520 = signature[0..65].into(); diff --git a/secret_store/src/key_server_cluster/admin_sessions/key_version_negotiation_session.rs b/secret_store/src/key_server_cluster/admin_sessions/key_version_negotiation_session.rs index b7291c25c..15abdce89 100644 --- a/secret_store/src/key_server_cluster/admin_sessions/key_version_negotiation_session.rs +++ b/secret_store/src/key_server_cluster/admin_sessions/key_version_negotiation_session.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use std::collections::{BTreeSet, BTreeMap}; -use ethereum_types::H256; +use ethereum_types::{Address, H256}; use ethkey::Secret; use parking_lot::{Mutex, Condvar}; use key_server_cluster::{Error, SessionId, NodeId, DocumentKeyShare}; @@ -55,8 +55,8 @@ pub struct SessionImpl { /// Action after key version is negotiated. #[derive(Clone)] pub enum ContinueAction { - /// Decryption session + is_shadow_decryption. - Decrypt(Arc, bool), + /// Decryption session + origin + is_shadow_decryption + is_broadcast_decryption. + Decrypt(Arc, Option
, bool, bool), /// Schnorr signing session + message hash. SchnorrSign(Arc, H256), /// ECDSA signing session + message hash. @@ -202,6 +202,7 @@ impl SessionImpl where T: SessionTransport { /// Wait for session completion. pub fn wait(&self) -> Result<(H256, NodeId), Error> { Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone()) + .expect("wait_session returns Some if called without timeout; qed") } /// Initialize session. diff --git a/secret_store/src/key_server_cluster/admin_sessions/servers_set_change_session.rs b/secret_store/src/key_server_cluster/admin_sessions/servers_set_change_session.rs index 01f339237..d701a57da 100644 --- a/secret_store/src/key_server_cluster/admin_sessions/servers_set_change_session.rs +++ b/secret_store/src/key_server_cluster/admin_sessions/servers_set_change_session.rs @@ -221,6 +221,7 @@ impl SessionImpl { /// Wait for session completion. pub fn wait(&self) -> Result<(), Error> { Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone()) + .expect("wait_session returns Some if called without timeout; qed") } /// Initialize servers set change session on master node. @@ -337,7 +338,7 @@ impl SessionImpl { } let unknown_sessions_job = UnknownSessionsJob::new_on_master(self.core.key_storage.clone(), self.core.meta.self_node_id.clone()); - consensus_session.disseminate_jobs(unknown_sessions_job, self.unknown_sessions_transport(), false) + consensus_session.disseminate_jobs(unknown_sessions_job, self.unknown_sessions_transport(), false).map(|_| ()) } /// When unknown sessions are requested. @@ -1166,7 +1167,7 @@ pub mod tests { pub fn generate_key(threshold: usize, nodes_ids: BTreeSet) -> GenerationMessageLoop { let mut gml = GenerationMessageLoop::with_nodes_ids(nodes_ids); - gml.master().initialize(Default::default(), false, threshold, gml.nodes.keys().cloned().collect::>().into()).unwrap(); + gml.master().initialize(Default::default(), Default::default(), false, threshold, gml.nodes.keys().cloned().collect::>().into()).unwrap(); while let Some((from, to, message)) = gml.take_message() { gml.process_message((from, to, message)).unwrap(); } diff --git a/secret_store/src/key_server_cluster/client_sessions/decryption_session.rs b/secret_store/src/key_server_cluster/client_sessions/decryption_session.rs index c3946f746..49c4e26d6 100644 --- a/secret_store/src/key_server_cluster/client_sessions/decryption_session.rs +++ b/secret_store/src/key_server_cluster/client_sessions/decryption_session.rs @@ -14,10 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use std::collections::BTreeSet; +use std::collections::{BTreeSet, BTreeMap}; use std::sync::Arc; +use std::time; use parking_lot::{Mutex, Condvar}; -use ethereum_types::H256; +use ethereum_types::{Address, H256}; use ethkey::Secret; use key_server_cluster::{Error, AclStorage, DocumentKeyShare, NodeId, SessionId, Requester, EncryptedDocumentKeyShadow, SessionMeta}; @@ -26,7 +27,7 @@ use key_server_cluster::cluster_sessions::{SessionIdWithSubSession, ClusterSessi use key_server_cluster::message::{Message, DecryptionMessage, DecryptionConsensusMessage, RequestPartialDecryption, PartialDecryption, DecryptionSessionError, DecryptionSessionCompleted, ConsensusMessage, InitializeConsensusSession, ConfirmConsensusInitialization, DecryptionSessionDelegation, DecryptionSessionDelegationCompleted}; -use key_server_cluster::jobs::job_session::{JobSession, JobTransport}; +use key_server_cluster::jobs::job_session::{JobSession, JobSessionState, JobTransport}; use key_server_cluster::jobs::key_access_job::KeyAccessJob; use key_server_cluster::jobs::decryption_job::{PartialDecryptionRequest, PartialDecryptionResponse, DecryptionJob}; use key_server_cluster::jobs::consensus_session::{ConsensusSessionParams, ConsensusSessionState, ConsensusSession}; @@ -71,6 +72,8 @@ type BroadcastDecryptionJobSession = JobSession, + /// Session origin (if any). + pub origin: Option
, /// Consensus-based decryption session. pub consensus_session: DecryptionConsensusSession, /// Broadcast decryption job. @@ -110,6 +113,8 @@ struct DecryptionConsensusTransport { access_key: Secret, /// Session-level nonce. nonce: u64, + /// Session origin (if any). + origin: Option
, /// Selected key version (on master node). version: Option, /// Cluster. @@ -157,6 +162,7 @@ impl SessionImpl { id: params.meta.id.clone(), access_key: params.access_key.clone(), nonce: params.nonce, + origin: None, version: None, cluster: params.cluster.clone(), }; @@ -180,6 +186,7 @@ impl SessionImpl { }, data: Mutex::new(SessionData { version: None, + origin: None, consensus_session: consensus_session, broadcast_job_session: None, is_shadow_decryption: None, @@ -214,13 +221,42 @@ impl SessionImpl { self.data.lock().result.clone() } + /// Get key requester. + pub fn requester(&self) -> Option { + self.data.lock().consensus_session.consensus_job().executor().requester().cloned() + } + + /// Get session origin. + pub fn origin(&self) -> Option
{ + self.data.lock().origin.clone() + } + /// Wait for session completion. - pub fn wait(&self) -> Result { - Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone()) + pub fn wait(&self, timeout: Option) -> Option> { + Self::wait_session(&self.core.completed, &self.data, timeout, |data| data.result.clone()) + } + + /// Get broadcasted shadows. + pub fn broadcast_shadows(&self) -> Option>> { + let data = self.data.lock(); + + if data.result.is_none() || (data.is_broadcast_session, data.is_shadow_decryption) != (Some(true), Some(true)) { + return None; + } + + let proof = "data.is_shadow_decryption is true; decrypt_shadow.is_some() is checked in DecryptionJob::check_partial_response; qed"; + Some(match self.core.meta.master_node_id == self.core.meta.self_node_id { + true => data.consensus_session.computation_job().responses().iter() + .map(|(n, r)| (n.clone(), r.decrypt_shadow.clone().expect(proof))) + .collect(), + false => data.broadcast_job_session.as_ref().expect("session completed; is_shadow_decryption == true; we're on non-master node; qed").responses().iter() + .map(|(n, r)| (n.clone(), r.decrypt_shadow.clone().expect(proof))) + .collect(), + }) } /// Delegate session to other node. - pub fn delegate(&self, master: NodeId, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> { + pub fn delegate(&self, master: NodeId, origin: Option
, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> { if self.core.meta.master_node_id != self.core.meta.self_node_id { return Err(Error::InvalidStateForRequest); } @@ -235,6 +271,7 @@ impl SessionImpl { session: self.core.meta.id.clone().into(), sub_session: self.core.access_key.clone().into(), session_nonce: self.core.nonce, + origin: origin.map(Into::into), requester: data.consensus_session.consensus_job().executor().requester() .expect("signature is passed to master node on creation; session can be delegated from master node only; qed") .clone().into(), @@ -247,7 +284,7 @@ impl SessionImpl { } /// Initialize decryption session on master node. - pub fn initialize(&self, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> { + pub fn initialize(&self, origin: Option
, version: H256, is_shadow_decryption: bool, is_broadcast_session: bool) -> Result<(), Error> { debug_assert_eq!(self.core.meta.self_node_id, self.core.meta.master_node_id); // check if version exists @@ -268,6 +305,8 @@ impl SessionImpl { } data.consensus_session.consensus_job_mut().transport_mut().version = Some(version.clone()); + data.consensus_session.consensus_job_mut().transport_mut().origin = origin.clone(); + data.origin = origin; data.version = Some(version.clone()); data.is_shadow_decryption = Some(is_shadow_decryption); data.is_broadcast_session = Some(is_broadcast_session); @@ -323,7 +362,7 @@ impl SessionImpl { data.delegation_status = Some(DelegationStatus::DelegatedFrom(sender.clone(), message.session_nonce)); } - self.initialize(message.version.clone().into(), message.is_shadow_decryption, message.is_broadcast_session) + self.initialize(message.origin.clone().map(Into::into), message.version.clone().into(), message.is_shadow_decryption, message.is_broadcast_session) } /// When delegated session is completed on other node. @@ -364,6 +403,7 @@ impl SessionImpl { .unwrap_or(false); data.consensus_session.consensus_job_mut().executor_mut().set_has_key_share(has_key_share); data.version = Some(version); + data.origin = message.origin.clone().map(Into::into); } data.consensus_session.on_consensus_message(&sender, &message.message)?; @@ -397,13 +437,19 @@ impl SessionImpl { let requester_public = data.consensus_session.consensus_job().executor().requester() .ok_or(Error::InvalidStateForRequest)? .public(&self.core.meta.id) - .ok_or(Error::InsufficientRequesterData)?; + .map_err(Error::InsufficientRequesterData)?; let decryption_job = DecryptionJob::new_on_slave(self.core.meta.self_node_id.clone(), self.core.access_key.clone(), requester_public.clone(), key_share.clone(), key_version)?; let decryption_transport = self.core.decryption_transport(false); + // update flags if not on master + if self.core.meta.self_node_id != self.core.meta.master_node_id { + data.is_shadow_decryption = Some(message.is_shadow_decryption); + data.is_broadcast_session = Some(message.is_broadcast_session); + } + // respond to request - data.consensus_session.on_job_request(sender, PartialDecryptionRequest { + let partial_decryption = data.consensus_session.on_job_request(sender, PartialDecryptionRequest { id: message.request_id.clone().into(), is_shadow_decryption: message.is_shadow_decryption, is_broadcast_session: message.is_broadcast_session, @@ -417,7 +463,7 @@ impl SessionImpl { self.core.access_key.clone(), requester_public, key_share.clone(), key_version, message.is_shadow_decryption, message.is_broadcast_session)?; Self::create_broadcast_decryption_job(&self.core, &mut *data, consensus_group, broadcast_decryption_job, - message.request_id.clone().into())?; + message.request_id.clone().into(), Some(partial_decryption.take_response()))?; } Ok(()) @@ -430,38 +476,52 @@ impl SessionImpl { debug_assert!(sender != &self.core.meta.self_node_id); let mut data = self.data.lock(); - if self.core.meta.self_node_id == self.core.meta.master_node_id { + let is_master_node = self.core.meta.self_node_id == self.core.meta.master_node_id; + let result = if is_master_node { data.consensus_session.on_job_response(sender, PartialDecryptionResponse { request_id: message.request_id.clone().into(), shadow_point: message.shadow_point.clone().into(), decrypt_shadow: message.decrypt_shadow.clone(), })?; + + if data.consensus_session.state() != ConsensusSessionState::Finished && + data.consensus_session.state() != ConsensusSessionState::Failed { + return Ok(()); + } + + // send completion signal to all nodes, except for rejected nodes + if is_master_node { + for node in data.consensus_session.consensus_non_rejected_nodes() { + self.core.cluster.send(&node, Message::Decryption(DecryptionMessage::DecryptionSessionCompleted(DecryptionSessionCompleted { + session: self.core.meta.id.clone().into(), + sub_session: self.core.access_key.clone().into(), + session_nonce: self.core.nonce, + })))?; + } + } + + data.consensus_session.result() } else { match data.broadcast_job_session.as_mut() { - Some(broadcast_job_session) => broadcast_job_session.on_partial_response(sender, PartialDecryptionResponse { - request_id: message.request_id.clone().into(), - shadow_point: message.shadow_point.clone().into(), - decrypt_shadow: message.decrypt_shadow.clone(), - })?, - None => return Err(Error::TooEarlyForRequest), + Some(broadcast_job_session) => { + broadcast_job_session.on_partial_response(sender, PartialDecryptionResponse { + request_id: message.request_id.clone().into(), + shadow_point: message.shadow_point.clone().into(), + decrypt_shadow: message.decrypt_shadow.clone(), + })?; + + if broadcast_job_session.state() != JobSessionState::Finished && + broadcast_job_session.state() != JobSessionState::Failed { + return Ok(()); + } + + broadcast_job_session.result() + }, + None => return Err(Error::InvalidMessage), } - } + }; - if data.consensus_session.state() != ConsensusSessionState::Finished { - return Ok(()); - } - - // send compeltion signal to all nodes, except for rejected nodes - for node in data.consensus_session.consensus_non_rejected_nodes() { - self.core.cluster.send(&node, Message::Decryption(DecryptionMessage::DecryptionSessionCompleted(DecryptionSessionCompleted { - session: self.core.meta.id.clone().into(), - sub_session: self.core.access_key.clone().into(), - session_nonce: self.core.nonce, - })))?; - } - - let result = data.consensus_session.result()?; - Self::set_decryption_result(&self.core, &mut *data, Ok(result)); + Self::set_decryption_result(&self.core, &mut *data, result); Ok(()) } @@ -543,28 +603,31 @@ impl SessionImpl { let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone(); let requester = data.consensus_session.consensus_job().executor().requester().ok_or(Error::InvalidStateForRequest)?.clone(); - let requester_public = requester.public(&core.meta.id).ok_or(Error::InsufficientRequesterData)?; + let requester_public = requester.public(&core.meta.id).map_err(Error::InsufficientRequesterData)?; let consensus_group = data.consensus_session.select_consensus_group()?.clone(); let decryption_job = DecryptionJob::new_on_master(core.meta.self_node_id.clone(), core.access_key.clone(), requester_public.clone(), key_share.clone(), key_version, is_shadow_decryption, is_broadcast_session)?; - let decryption_request_id = decryption_job.request_id().clone().expect("TODO"); + let decryption_request_id = decryption_job.request_id().clone() + .expect("DecryptionJob always have request_id when created on master; it is created using new_on_master above; qed"); let decryption_transport = core.decryption_transport(false); - data.consensus_session.disseminate_jobs(decryption_job, decryption_transport, data.is_broadcast_session.expect("TODO"))?; + let is_broadcast_session = data.is_broadcast_session + .expect("disseminate_jobs is called on master node only; on master node is_broadcast_session is filled during initialization; qed"); + let self_response = data.consensus_session.disseminate_jobs(decryption_job, decryption_transport, is_broadcast_session)?; // ...and prepare decryption job session if we need to broadcast result - if data.is_broadcast_session.expect("TODO") { + if is_broadcast_session { let broadcast_decryption_job = DecryptionJob::new_on_master(core.meta.self_node_id.clone(), core.access_key.clone(), requester_public, key_share.clone(), key_version, is_shadow_decryption, is_broadcast_session)?; Self::create_broadcast_decryption_job(&core, data, consensus_group, broadcast_decryption_job, - decryption_request_id)?; + decryption_request_id, self_response)?; } Ok(()) } /// Create broadcast decryption job. - fn create_broadcast_decryption_job(core: &SessionCore, data: &mut SessionData, mut consensus_group: BTreeSet, mut job: DecryptionJob, request_id: Secret) -> Result<(), Error> { + fn create_broadcast_decryption_job(core: &SessionCore, data: &mut SessionData, mut consensus_group: BTreeSet, mut job: DecryptionJob, request_id: Secret, self_response: Option) -> Result<(), Error> { consensus_group.insert(core.meta.self_node_id.clone()); job.set_request_id(request_id.clone().into()); @@ -575,7 +638,7 @@ impl SessionImpl { self_node_id: core.meta.self_node_id.clone(), threshold: core.meta.threshold, }, job, transport); - job_session.initialize(consensus_group, core.meta.self_node_id != core.meta.master_node_id)?; + job_session.initialize(consensus_group, self_response, core.meta.self_node_id != core.meta.master_node_id)?; data.broadcast_job_session = Some(job_session); Ok(()) @@ -691,6 +754,7 @@ impl JobTransport for DecryptionConsensusTransport { session: self.id.clone().into(), sub_session: self.access_key.clone().into(), session_nonce: self.nonce, + origin: self.origin.clone().map(Into::into), message: ConsensusMessage::InitializeConsensusSession(InitializeConsensusSession { requester: request.into(), version: version.clone().into(), @@ -703,6 +767,7 @@ impl JobTransport for DecryptionConsensusTransport { session: self.id.clone().into(), sub_session: self.access_key.clone().into(), session_nonce: self.nonce, + origin: None, message: ConsensusMessage::ConfirmConsensusInitialization(ConfirmConsensusInitialization { is_confirmed: response, }) @@ -751,7 +816,7 @@ mod tests { use std::sync::Arc; use std::collections::{BTreeMap, VecDeque}; use acl_storage::DummyAclStorage; - use ethkey::{self, KeyPair, Random, Generator, Public, Secret}; + use ethkey::{self, KeyPair, Random, Generator, Public, Secret, public_to_address}; use key_server_cluster::{NodeId, DocumentKeyShare, DocumentKeyShareVersion, SessionId, Requester, Error, EncryptedDocumentKeyShadow, SessionMeta}; use key_server_cluster::cluster::tests::DummyCluster; @@ -918,7 +983,7 @@ mod tests { cluster: Arc::new(DummyCluster::new(self_node_id.clone())), nonce: 0, }, Some(Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()))).unwrap(); - assert_eq!(session.initialize(Default::default(), false, false), Err(Error::InvalidMessage)); + assert_eq!(session.initialize(Default::default(), Default::default(), false, false), Err(Error::InvalidMessage)); } #[test] @@ -951,24 +1016,25 @@ mod tests { cluster: Arc::new(DummyCluster::new(self_node_id.clone())), nonce: 0, }, Some(Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()))).unwrap(); - assert_eq!(session.initialize(Default::default(), false, false), Err(Error::ConsensusUnreachable)); + assert_eq!(session.initialize(Default::default(), Default::default(), false, false), Err(Error::ConsensusUnreachable)); } #[test] fn fails_to_initialize_when_already_initialized() { let (_, _, _, sessions) = prepare_decryption_sessions(); - assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap(), ()); - assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap_err(), Error::InvalidStateForRequest); + assert_eq!(sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(), ()); + assert_eq!(sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap_err(), Error::InvalidStateForRequest); } #[test] fn fails_to_accept_initialization_when_already_initialized() { let (_, _, _, sessions) = prepare_decryption_sessions(); - assert_eq!(sessions[0].initialize(Default::default(), false, false).unwrap(), ()); + assert_eq!(sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(), ()); assert_eq!(sessions[0].on_consensus_message(sessions[1].node(), &message::DecryptionConsensusMessage { session: SessionId::default().into(), sub_session: sessions[0].access_key().clone().into(), session_nonce: 0, + origin: None, message: message::ConsensusMessage::InitializeConsensusSession(message::InitializeConsensusSession { requester: Requester::Signature(ethkey::sign( Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).into(), @@ -984,6 +1050,7 @@ mod tests { session: SessionId::default().into(), sub_session: sessions[0].access_key().clone().into(), session_nonce: 0, + origin: None, message: message::ConsensusMessage::InitializeConsensusSession(message::InitializeConsensusSession { requester: Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).into(), @@ -1008,6 +1075,7 @@ mod tests { session: SessionId::default().into(), sub_session: sessions[0].access_key().clone().into(), session_nonce: 0, + origin: None, message: message::ConsensusMessage::InitializeConsensusSession(message::InitializeConsensusSession { requester: Requester::Signature(ethkey::sign(Random.generate().unwrap().secret(), &SessionId::default()).unwrap()).into(), @@ -1041,7 +1109,7 @@ mod tests { #[test] fn fails_to_accept_partial_decrypt_twice() { let (_, clusters, _, sessions) = prepare_decryption_sessions(); - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); let mut pd_from = None; let mut pd_msg = None; @@ -1069,7 +1137,7 @@ mod tests { #[test] fn node_is_marked_rejected_when_timed_out_during_initialization_confirmation() { let (_, _, _, sessions) = prepare_decryption_sessions(); - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); // 1 node disconnects => we still can recover secret sessions[0].on_node_timeout(sessions[1].node()); @@ -1086,8 +1154,8 @@ mod tests { let (_, clusters, acl_storages, sessions) = prepare_decryption_sessions(); let key_pair = Random.generate().unwrap(); - acl_storages[1].prohibit(key_pair.public().clone(), SessionId::default()); - sessions[0].initialize(Default::default(), false, false).unwrap(); + acl_storages[1].prohibit(public_to_address(key_pair.public()), SessionId::default()); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap(); @@ -1099,7 +1167,7 @@ mod tests { #[test] fn session_does_not_fail_if_requested_node_disconnects() { let (_, clusters, _, sessions) = prepare_decryption_sessions(); - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap(); @@ -1115,7 +1183,7 @@ mod tests { #[test] fn session_does_not_fail_if_node_with_shadow_point_disconnects() { let (_, clusters, _, sessions) = prepare_decryption_sessions(); - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults && sessions[0].data.lock().consensus_session.computation_job().responses().len() == 2).unwrap(); @@ -1132,7 +1200,7 @@ mod tests { #[test] fn session_restarts_if_confirmed_node_disconnects() { let (_, clusters, _, sessions) = prepare_decryption_sessions(); - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap(); @@ -1147,7 +1215,7 @@ mod tests { #[test] fn session_does_not_fail_if_non_master_node_disconnects_from_non_master_node() { let (_, clusters, _, sessions) = prepare_decryption_sessions(); - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange_until(&clusters, &sessions, |_, _, _| sessions[0].state() == ConsensusSessionState::WaitingForPartialResults).unwrap(); @@ -1162,7 +1230,7 @@ mod tests { let (_, clusters, _, sessions) = prepare_decryption_sessions(); // now let's try to do a decryption - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange(&clusters, &sessions).unwrap(); @@ -1184,7 +1252,7 @@ mod tests { let (key_pair, clusters, _, sessions) = prepare_decryption_sessions(); // now let's try to do a decryption - sessions[0].initialize(Default::default(), true, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), true, false).unwrap(); do_messages_exchange(&clusters, &sessions).unwrap(); @@ -1215,12 +1283,12 @@ mod tests { let (key_pair, clusters, acl_storages, sessions) = prepare_decryption_sessions(); // now let's try to do a decryption - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); // we need 4 out of 5 nodes to agree to do a decryption // let's say that 2 of these nodes are disagree - acl_storages[1].prohibit(key_pair.public().clone(), SessionId::default()); - acl_storages[2].prohibit(key_pair.public().clone(), SessionId::default()); + acl_storages[1].prohibit(public_to_address(key_pair.public()), SessionId::default()); + acl_storages[2].prohibit(public_to_address(key_pair.public()), SessionId::default()); assert_eq!(do_messages_exchange(&clusters, &sessions).unwrap_err(), Error::ConsensusUnreachable); @@ -1235,10 +1303,10 @@ mod tests { // we need 4 out of 5 nodes to agree to do a decryption // let's say that 1 of these nodes (master) is disagree - acl_storages[0].prohibit(key_pair.public().clone(), SessionId::default()); + acl_storages[0].prohibit(public_to_address(key_pair.public()), SessionId::default()); // now let's try to do a decryption - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange(&clusters, &sessions).unwrap(); @@ -1278,7 +1346,7 @@ mod tests { ); // now let's try to do a decryption - sessions[1].delegate(sessions[0].core.meta.self_node_id.clone(), Default::default(), false, false).unwrap(); + sessions[1].delegate(sessions[0].core.meta.self_node_id.clone(), Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange(&clusters, &sessions).unwrap(); // now check that: @@ -1304,7 +1372,7 @@ mod tests { } // now let's try to do a decryption - sessions[0].initialize(Default::default(), false, false).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, false).unwrap(); do_messages_exchange(&clusters, &sessions).unwrap(); assert_eq!(sessions[0].decrypted_secret().unwrap().unwrap(), EncryptedDocumentKeyShadow { @@ -1317,13 +1385,52 @@ mod tests { #[test] fn decryption_result_restored_on_all_nodes_if_broadcast_session_is_completed() { let (_, clusters, _, sessions) = prepare_decryption_sessions(); - sessions[0].initialize(Default::default(), false, true).unwrap(); + sessions[0].initialize(Default::default(), Default::default(), false, true).unwrap(); do_messages_exchange(&clusters, &sessions).unwrap(); // decryption result must be the same and available on 4 nodes let result = sessions[0].decrypted_secret(); assert!(result.clone().unwrap().is_ok()); + assert_eq!(result.clone().unwrap().unwrap(), EncryptedDocumentKeyShadow { + decrypted_secret: SECRET_PLAIN.into(), + common_point: None, + decrypt_shadows: None, + }); assert_eq!(3, sessions.iter().skip(1).filter(|s| s.decrypted_secret() == result).count()); assert_eq!(1, sessions.iter().skip(1).filter(|s| s.decrypted_secret().is_none()).count()); } + + #[test] + fn decryption_shadows_restored_on_all_nodes_if_shadow_broadcast_session_is_completed() { + let (key_pair, clusters, _, sessions) = prepare_decryption_sessions(); + sessions[0].initialize(Default::default(), Default::default(), true, true).unwrap(); + do_messages_exchange(&clusters, &sessions).unwrap(); + + // decryption shadows must be the same and available on 4 nodes + let broadcast_shadows = sessions[0].broadcast_shadows(); + assert!(broadcast_shadows.is_some()); + assert_eq!(3, sessions.iter().skip(1).filter(|s| s.broadcast_shadows() == broadcast_shadows).count()); + assert_eq!(1, sessions.iter().skip(1).filter(|s| s.broadcast_shadows().is_none()).count()); + + // 4 nodes must be able to recover original secret + use ethcrypto::DEFAULT_MAC; + use ethcrypto::ecies::decrypt; + let result = sessions[0].decrypted_secret().unwrap().unwrap(); + assert_eq!(3, sessions.iter().skip(1).filter(|s| s.decrypted_secret() == Some(Ok(result.clone()))).count()); + let decrypt_shadows: Vec<_> = result.decrypt_shadows.unwrap().into_iter() + .map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap())) + .collect(); + let decrypted_secret = math::decrypt_with_shadow_coefficients(result.decrypted_secret, result.common_point.unwrap(), decrypt_shadows).unwrap(); + assert_eq!(decrypted_secret, SECRET_PLAIN.into()); + } + + #[test] + fn decryption_session_origin_is_known_to_all_initialized_nodes() { + let (_, clusters, _, sessions) = prepare_decryption_sessions(); + sessions[0].initialize(Some(1.into()), Default::default(), true, true).unwrap(); + do_messages_exchange(&clusters, &sessions).unwrap(); + + // all session must have origin set + assert_eq!(5, sessions.iter().filter(|s| s.origin() == Some(1.into())).count()); + } } diff --git a/secret_store/src/key_server_cluster/client_sessions/encryption_session.rs b/secret_store/src/key_server_cluster/client_sessions/encryption_session.rs index eafac6fd2..ca8bac381 100644 --- a/secret_store/src/key_server_cluster/client_sessions/encryption_session.rs +++ b/secret_store/src/key_server_cluster/client_sessions/encryption_session.rs @@ -19,8 +19,10 @@ use std::fmt::{Debug, Formatter, Error as FmtError}; use std::time; use std::sync::Arc; use parking_lot::{Condvar, Mutex}; +use ethereum_types::Address; use ethkey::Public; -use key_server_cluster::{Error, NodeId, SessionId, Requester, KeyStorage, DocumentKeyShare}; +use key_server_cluster::{Error, NodeId, SessionId, Requester, KeyStorage, + DocumentKeyShare, ServerKeyId}; use key_server_cluster::cluster::Cluster; use key_server_cluster::cluster_sessions::ClusterSession; use key_server_cluster::message::{Message, EncryptionMessage, InitializeEncryptionSession, @@ -107,7 +109,7 @@ pub enum SessionState { impl SessionImpl { /// Create new encryption session. pub fn new(params: SessionParams) -> Result { - check_encrypted_data(¶ms.encrypted_data)?; + check_encrypted_data(params.encrypted_data.as_ref())?; Ok(SessionImpl { id: params.id, @@ -133,9 +135,9 @@ impl SessionImpl { /// Wait for session completion. pub fn wait(&self, timeout: Option) -> Result<(), Error> { Self::wait_session(&self.completed, &self.data, timeout, |data| data.result.clone()) + .expect("wait_session returns Some if called without timeout; qed") } - /// Start new session initialization. This must be called on master node. pub fn initialize(&self, requester: Requester, common_point: Public, encrypted_point: Public) -> Result<(), Error> { let mut data = self.data.lock(); @@ -155,17 +157,10 @@ impl SessionImpl { // TODO [Reliability]: there could be situation when some nodes have failed to store encrypted data // => potential problems during restore. some confirmation step is needed (2pc)? // save encryption data - if let Some(mut encrypted_data) = self.encrypted_data.clone() { - // check that the requester is the author of the encrypted data - let requester_address = requester.address(&self.id).ok_or(Error::InsufficientRequesterData)?; - if encrypted_data.author != requester_address { - return Err(Error::AccessDenied); - } - - encrypted_data.common_point = Some(common_point.clone()); - encrypted_data.encrypted_point = Some(encrypted_point.clone()); - self.key_storage.update(self.id.clone(), encrypted_data) - .map_err(|e| Error::KeyStorage(e.into()))?; + if let Some(encrypted_data) = self.encrypted_data.clone() { + let requester_address = requester.address(&self.id).map_err(Error::InsufficientRequesterData)?; + update_encrypted_data(&self.key_storage, self.id.clone(), + encrypted_data, requester_address, common_point.clone(), encrypted_point.clone())?; } // start initialization @@ -199,18 +194,11 @@ impl SessionImpl { } // check that the requester is the author of the encrypted data - if let Some(mut encrypted_data) = self.encrypted_data.clone() { + if let Some(encrypted_data) = self.encrypted_data.clone() { let requester: Requester = message.requester.clone().into(); - let requestor_address = requester.address(&self.id).ok_or(Error::InsufficientRequesterData)?; - if encrypted_data.author != requestor_address { - return Err(Error::AccessDenied); - } - - // save encryption data - encrypted_data.common_point = Some(message.common_point.clone().into()); - encrypted_data.encrypted_point = Some(message.encrypted_point.clone().into()); - self.key_storage.update(self.id.clone(), encrypted_data) - .map_err(|e| Error::KeyStorage(e.into()))?; + let requester_address = requester.address(&self.id).map_err(Error::InsufficientRequesterData)?; + update_encrypted_data(&self.key_storage, self.id.clone(), + encrypted_data, requester_address, message.common_point.clone().into(), message.encrypted_point.clone().into())?; } // update state @@ -333,13 +321,28 @@ impl Debug for SessionImpl { } } -fn check_encrypted_data(encrypted_data: &Option) -> Result<(), Error> { - if let &Some(ref encrypted_data) = encrypted_data { +/// Check that common_point and encrypted point are not yet set in key share. +pub fn check_encrypted_data(key_share: Option<&DocumentKeyShare>) -> Result<(), Error> { + if let Some(key_share) = key_share { // check that common_point and encrypted_point are still not set yet - if encrypted_data.common_point.is_some() || encrypted_data.encrypted_point.is_some() { + if key_share.common_point.is_some() || key_share.encrypted_point.is_some() { return Err(Error::CompletedSessionId); } } Ok(()) } + +/// Update key share with encrypted document key. +pub fn update_encrypted_data(key_storage: &Arc, key_id: ServerKeyId, mut key_share: DocumentKeyShare, author: Address, common_point: Public, encrypted_point: Public) -> Result<(), Error> { + // author must be the same + if key_share.author != author { + return Err(Error::AccessDenied); + } + + // save encryption data + key_share.common_point = Some(common_point); + key_share.encrypted_point = Some(encrypted_point); + key_storage.update(key_id, key_share) + .map_err(|e| Error::KeyStorage(e.into())) +} diff --git a/secret_store/src/key_server_cluster/client_sessions/generation_session.rs b/secret_store/src/key_server_cluster/client_sessions/generation_session.rs index 30a35cbf6..2c0cbe2b1 100644 --- a/secret_store/src/key_server_cluster/client_sessions/generation_session.rs +++ b/secret_store/src/key_server_cluster/client_sessions/generation_session.rs @@ -82,6 +82,8 @@ struct SessionData { author: Option
, // === Values, filled when session initialization is completed === + /// Session origin (if any). + origin: Option
, /// Is zero secret generation session? is_zero: Option, /// Threshold value for this DKG. Only `threshold + 1` will be able to collectively recreate joint secret, @@ -217,6 +219,7 @@ impl SessionImpl { simulate_faulty_behaviour: false, master: None, author: None, + origin: None, is_zero: None, threshold: None, derived_point: None, @@ -251,8 +254,13 @@ impl SessionImpl { self.data.lock().state.clone() } + /// Get session origin. + pub fn origin(&self) -> Option
{ + self.data.lock().origin.clone() + } + /// Wait for session completion. - pub fn wait(&self, timeout: Option) -> Result { + pub fn wait(&self, timeout: Option) -> Option> { Self::wait_session(&self.completed, &self.data, timeout, |data| data.joint_public_and_secret.clone() .map(|r| r.map(|r| r.0.clone()))) } @@ -263,7 +271,7 @@ impl SessionImpl { } /// Start new session initialization. This must be called on master node. - pub fn initialize(&self, author: Address, is_zero: bool, threshold: usize, nodes: InitializationNodes) -> Result<(), Error> { + pub fn initialize(&self, origin: Option
, author: Address, is_zero: bool, threshold: usize, nodes: InitializationNodes) -> Result<(), Error> { check_cluster_nodes(self.node(), &nodes.set())?; check_threshold(threshold, &nodes.set())?; @@ -277,6 +285,7 @@ impl SessionImpl { // update state data.master = Some(self.node().clone()); data.author = Some(author.clone()); + data.origin = origin.clone(); data.is_zero = Some(is_zero); data.threshold = Some(threshold); match nodes { @@ -304,6 +313,7 @@ impl SessionImpl { self.cluster.send(&next_node, Message::Generation(GenerationMessage::InitializeSession(InitializeSession { session: self.id.clone().into(), session_nonce: self.nonce, + origin: origin.map(Into::into), author: author.into(), nodes: data.nodes.iter().map(|(k, v)| (k.clone().into(), v.id_number.clone().into())).collect(), is_zero: data.is_zero.expect("is_zero is filled in initialization phase; KD phase follows initialization phase; qed"), @@ -380,6 +390,7 @@ impl SessionImpl { data.author = Some(message.author.clone().into()); data.state = SessionState::WaitingForInitializationComplete; data.nodes = message.nodes.iter().map(|(id, number)| (id.clone().into(), NodeData::with_id_number(number.clone().into()))).collect(); + data.origin = message.origin.clone().map(Into::into); data.is_zero = Some(message.is_zero); data.threshold = Some(message.threshold); @@ -411,6 +422,7 @@ impl SessionImpl { return self.cluster.send(&next_receiver, Message::Generation(GenerationMessage::InitializeSession(InitializeSession { session: self.id.clone().into(), session_nonce: self.nonce, + origin: data.origin.clone().map(Into::into), author: data.author.as_ref().expect("author is filled on initialization step; confrm initialization follows initialization; qed").clone().into(), nodes: data.nodes.iter().map(|(k, v)| (k.clone().into(), v.id_number.clone().into())).collect(), is_zero: data.is_zero.expect("is_zero is filled in initialization phase; KD phase follows initialization phase; qed"), @@ -937,7 +949,7 @@ pub mod tests { use std::time::Duration; use tokio_core::reactor::Core; use ethereum_types::Address; - use ethkey::{Random, Generator, Public, KeyPair}; + use ethkey::{Random, Generator, KeyPair}; use key_server_cluster::{NodeId, SessionId, Error, KeyStorage, DummyKeyStorage}; use key_server_cluster::message::{self, Message, GenerationMessage}; use key_server_cluster::cluster::tests::{DummyCluster, make_clusters, run_clusters, loop_until, all_connections_established}; @@ -1065,7 +1077,7 @@ pub mod tests { fn make_simple_cluster(threshold: usize, num_nodes: usize) -> Result<(SessionId, NodeId, NodeId, MessageLoop), Error> { let l = MessageLoop::new(num_nodes); - l.master().initialize(Default::default(), false, threshold, l.nodes.keys().cloned().collect::>().into())?; + l.master().initialize(Default::default(), Default::default(), false, threshold, l.nodes.keys().cloned().collect::>().into())?; let session_id = l.session_id.clone(); let master_id = l.master().node().clone(); @@ -1076,7 +1088,7 @@ pub mod tests { #[test] fn initializes_in_cluster_of_single_node() { let l = MessageLoop::new(1); - assert!(l.master().initialize(Default::default(), false, 0, l.nodes.keys().cloned().collect::>().into()).is_ok()); + assert!(l.master().initialize(Default::default(), Default::default(), false, 0, l.nodes.keys().cloned().collect::>().into()).is_ok()); } #[test] @@ -1091,7 +1103,7 @@ pub mod tests { nonce: Some(0), }); let cluster_nodes: BTreeSet<_> = (0..2).map(|_| math::generate_random_point().unwrap()).collect(); - assert_eq!(session.initialize(Default::default(), false, 0, cluster_nodes.into()).unwrap_err(), Error::InvalidNodesConfiguration); + assert_eq!(session.initialize(Default::default(), Default::default(), false, 0, cluster_nodes.into()).unwrap_err(), Error::InvalidNodesConfiguration); } #[test] @@ -1105,7 +1117,7 @@ pub mod tests { #[test] fn fails_to_initialize_when_already_initialized() { let (_, _, _, l) = make_simple_cluster(0, 2).unwrap(); - assert_eq!(l.master().initialize(Default::default(), false, 0, l.nodes.keys().cloned().collect::>().into()).unwrap_err(), + assert_eq!(l.master().initialize(Default::default(), Default::default(), false, 0, l.nodes.keys().cloned().collect::>().into()).unwrap_err(), Error::InvalidStateForRequest); } @@ -1185,6 +1197,7 @@ pub mod tests { assert_eq!(l.first_slave().on_initialize_session(m, &message::InitializeSession { session: sid.into(), session_nonce: 0, + origin: None, author: Address::default().into(), nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), is_zero: false, @@ -1202,6 +1215,7 @@ pub mod tests { assert_eq!(l.first_slave().on_initialize_session(m, &message::InitializeSession { session: sid.into(), session_nonce: 0, + origin: None, author: Address::default().into(), nodes: nodes.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), is_zero: false, @@ -1345,7 +1359,7 @@ pub mod tests { 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(Default::default(), false, threshold, l.nodes.keys().cloned().collect::>().into()).unwrap(); + l.master().initialize(Default::default(), Default::default(), false, threshold, l.nodes.keys().cloned().collect::>().into()).unwrap(); assert_eq!(l.nodes.len(), num_nodes); // let nodes do initialization + keys dissemination @@ -1377,6 +1391,9 @@ pub mod tests { #[test] fn encryption_session_works_over_network() { + const CONN_TIMEOUT: Duration = Duration::from_millis(300); + const SESSION_TIMEOUT: Duration = Duration::from_millis(1000); + let test_cases = [(1, 3)]; for &(threshold, num_nodes) in &test_cases { let mut core = Core::new().unwrap(); @@ -1386,12 +1403,12 @@ pub mod tests { run_clusters(&clusters); // establish connections - loop_until(&mut core, Duration::from_millis(300), || clusters.iter().all(all_connections_established)); + loop_until(&mut core, CONN_TIMEOUT, || clusters.iter().all(all_connections_established)); // run session to completion let session_id = SessionId::default(); - let session = clusters[0].client().new_generation_session(session_id, Public::default(), threshold).unwrap(); - loop_until(&mut core, Duration::from_millis(1000), || session.joint_public_and_secret().is_some()); + let session = clusters[0].client().new_generation_session(session_id, Default::default(), Default::default(), threshold).unwrap(); + loop_until(&mut core, SESSION_TIMEOUT, || session.joint_public_and_secret().is_some()); } } diff --git a/secret_store/src/key_server_cluster/client_sessions/signing_session_ecdsa.rs b/secret_store/src/key_server_cluster/client_sessions/signing_session_ecdsa.rs index ddd494412..02c20bc20 100644 --- a/secret_store/src/key_server_cluster/client_sessions/signing_session_ecdsa.rs +++ b/secret_store/src/key_server_cluster/client_sessions/signing_session_ecdsa.rs @@ -222,6 +222,7 @@ impl SessionImpl { /// Wait for session completion. pub fn wait(&self) -> Result { Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone()) + .expect("wait_session returns Some if called without timeout; qed") } /// Delegate session to other node. @@ -402,7 +403,7 @@ impl SessionImpl { session_nonce: n, message: m, })); - sig_nonce_generation_session.initialize(Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?; + sig_nonce_generation_session.initialize(Default::default(), Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?; data.sig_nonce_generation_session = Some(sig_nonce_generation_session); // start generation of inversed nonce computation session @@ -414,7 +415,7 @@ impl SessionImpl { session_nonce: n, message: m, })); - inv_nonce_generation_session.initialize(Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?; + inv_nonce_generation_session.initialize(Default::default(), Default::default(), false, key_share.threshold, consensus_group_map.clone().into())?; data.inv_nonce_generation_session = Some(inv_nonce_generation_session); // start generation of zero-secret shares for inversed nonce computation session @@ -426,7 +427,7 @@ impl SessionImpl { session_nonce: n, message: m, })); - inv_zero_generation_session.initialize(Default::default(), true, key_share.threshold * 2, consensus_group_map.clone().into())?; + inv_zero_generation_session.initialize(Default::default(), Default::default(), true, key_share.threshold * 2, consensus_group_map.clone().into())?; data.inv_zero_generation_session = Some(inv_zero_generation_session); data.state = SessionState::NoncesGenerating; @@ -688,7 +689,7 @@ impl SessionImpl { id: message.request_id.clone().into(), inversed_nonce_coeff: message.inversed_nonce_coeff.clone().into(), message_hash: message.message_hash.clone().into(), - }, signing_job, signing_transport) + }, signing_job, signing_transport).map(|_| ()) } /// When partial signature is received. @@ -989,7 +990,7 @@ impl SessionCore { let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone(); let signing_job = EcdsaSigningJob::new_on_master(key_share.clone(), key_version, nonce_public, inv_nonce_share, inversed_nonce_coeff, message_hash)?; - consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false) + consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false).map(|_| ()) } } @@ -1054,7 +1055,7 @@ mod tests { use std::sync::Arc; use std::collections::{BTreeSet, BTreeMap, VecDeque}; use ethereum_types::H256; - use ethkey::{self, Random, Generator, KeyPair, verify_public}; + use ethkey::{self, Random, Generator, KeyPair, verify_public, public_to_address}; use acl_storage::DummyAclStorage; use key_server_cluster::{NodeId, DummyKeyStorage, SessionId, SessionMeta, Error, KeyStorage}; use key_server_cluster::cluster_sessions::ClusterSession; @@ -1165,7 +1166,7 @@ mod tests { fn prepare_signing_sessions(threshold: usize, num_nodes: usize) -> (KeyGenerationMessageLoop, MessageLoop) { // run key generation sessions let mut gl = KeyGenerationMessageLoop::new(num_nodes); - gl.master().initialize(Default::default(), false, threshold, gl.nodes.keys().cloned().collect::>().into()).unwrap(); + gl.master().initialize(Default::default(), Default::default(), false, threshold, gl.nodes.keys().cloned().collect::>().into()).unwrap(); while let Some((from, to, message)) = gl.take_message() { gl.process_message((from, to, message)).unwrap(); } @@ -1214,7 +1215,7 @@ mod tests { // we need at least 3-of-4 nodes to agree to reach consensus // let's say 1 of 4 nodes disagee - sl.acl_storages[1].prohibit(sl.requester.public().clone(), SessionId::default()); + sl.acl_storages[1].prohibit(public_to_address(sl.requester.public()), SessionId::default()); // then consensus reachable, but single node will disagree while let Some((from, to, message)) = sl.take_message() { @@ -1235,7 +1236,7 @@ mod tests { // we need at least 3-of-4 nodes to agree to reach consensus // let's say 1 of 4 nodes disagee - sl.acl_storages[0].prohibit(sl.requester.public().clone(), SessionId::default()); + sl.acl_storages[0].prohibit(public_to_address(sl.requester.public()), SessionId::default()); // then consensus reachable, but single node will disagree while let Some((from, to, message)) = sl.take_message() { diff --git a/secret_store/src/key_server_cluster/client_sessions/signing_session_schnorr.rs b/secret_store/src/key_server_cluster/client_sessions/signing_session_schnorr.rs index 4eaf07237..b4041da53 100644 --- a/secret_store/src/key_server_cluster/client_sessions/signing_session_schnorr.rs +++ b/secret_store/src/key_server_cluster/client_sessions/signing_session_schnorr.rs @@ -209,6 +209,7 @@ impl SessionImpl { /// Wait for session completion. pub fn wait(&self) -> Result<(Secret, Secret), Error> { Self::wait_session(&self.core.completed, &self.data, None, |data| data.result.clone()) + .expect("wait_session returns Some if called without timeout; qed") } /// Delegate session to other node. @@ -277,7 +278,7 @@ impl SessionImpl { }), nonce: None, }); - generation_session.initialize(Default::default(), false, 0, vec![self.core.meta.self_node_id.clone()].into_iter().collect::>().into())?; + generation_session.initialize(Default::default(), Default::default(), false, 0, vec![self.core.meta.self_node_id.clone()].into_iter().collect::>().into())?; debug_assert_eq!(generation_session.state(), GenerationSessionState::WaitingForGenerationConfirmation); let joint_public_and_secret = generation_session @@ -406,7 +407,7 @@ impl SessionImpl { nonce: None, }); - generation_session.initialize(Default::default(), false, key_share.threshold, consensus_group.into())?; + generation_session.initialize(Default::default(), Default::default(), false, key_share.threshold, consensus_group.into())?; data.generation_session = Some(generation_session); data.state = SessionState::SessionKeyGeneration; @@ -508,7 +509,7 @@ impl SessionImpl { id: message.request_id.clone().into(), message_hash: message.message_hash.clone().into(), other_nodes_ids: message.nodes.iter().cloned().map(Into::into).collect(), - }, signing_job, signing_transport) + }, signing_job, signing_transport).map(|_| ()) } /// When partial signature is received. @@ -735,8 +736,9 @@ impl SessionCore { }; let key_version = key_share.version(version).map_err(|e| Error::KeyStorage(e.into()))?.hash.clone(); - let signing_job = SchnorrSigningJob::new_on_master(self.meta.self_node_id.clone(), key_share.clone(), key_version, session_public, session_secret_share, message_hash)?; - consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false) + let signing_job = SchnorrSigningJob::new_on_master(self.meta.self_node_id.clone(), key_share.clone(), key_version, + session_public, session_secret_share, message_hash)?; + consensus_session.disseminate_jobs(signing_job, self.signing_transport(), false).map(|_| ()) } } @@ -802,7 +804,7 @@ mod tests { use std::str::FromStr; use std::collections::{BTreeSet, BTreeMap, VecDeque}; use ethereum_types::{Address, H256}; - use ethkey::{self, Random, Generator, Public, Secret, KeyPair}; + use ethkey::{self, Random, Generator, Public, Secret, KeyPair, public_to_address}; use acl_storage::DummyAclStorage; use key_server_cluster::{NodeId, DummyKeyStorage, DocumentKeyShare, DocumentKeyShareVersion, SessionId, Requester, SessionMeta, Error, KeyStorage}; @@ -928,7 +930,7 @@ mod tests { fn prepare_signing_sessions(threshold: usize, num_nodes: usize) -> (KeyGenerationMessageLoop, MessageLoop) { // run key generation sessions let mut gl = KeyGenerationMessageLoop::new(num_nodes); - gl.master().initialize(Default::default(), false, threshold, gl.nodes.keys().cloned().collect::>().into()).unwrap(); + gl.master().initialize(Default::default(), Default::default(), false, threshold, gl.nodes.keys().cloned().collect::>().into()).unwrap(); while let Some((from, to, message)) = gl.take_message() { gl.process_message((from, to, message)).unwrap(); } @@ -1114,6 +1116,7 @@ mod tests { message: GenerationMessage::InitializeSession(InitializeSession { session: SessionId::default().into(), session_nonce: 0, + origin: None, author: Address::default().into(), nodes: BTreeMap::new(), is_zero: false, @@ -1157,8 +1160,8 @@ mod tests { // we need at least 2-of-3 nodes to agree to reach consensus // let's say 2 of 3 nodes disagee - sl.acl_storages[1].prohibit(sl.requester.public().clone(), SessionId::default()); - sl.acl_storages[2].prohibit(sl.requester.public().clone(), SessionId::default()); + sl.acl_storages[1].prohibit(public_to_address(sl.requester.public()), SessionId::default()); + sl.acl_storages[2].prohibit(public_to_address(sl.requester.public()), SessionId::default()); // then consensus is unreachable assert_eq!(sl.run_until(|_| false), Err(Error::ConsensusUnreachable)); @@ -1171,7 +1174,7 @@ mod tests { // we need at least 2-of-3 nodes to agree to reach consensus // let's say 1 of 3 nodes disagee - sl.acl_storages[1].prohibit(sl.requester.public().clone(), SessionId::default()); + sl.acl_storages[1].prohibit(public_to_address(sl.requester.public()), SessionId::default()); // then consensus reachable, but single node will disagree while let Some((from, to, message)) = sl.take_message() { @@ -1192,7 +1195,7 @@ mod tests { // we need at least 2-of-3 nodes to agree to reach consensus // let's say 1 of 3 nodes disagee - sl.acl_storages[0].prohibit(sl.requester.public().clone(), SessionId::default()); + sl.acl_storages[0].prohibit(public_to_address(sl.requester.public()), SessionId::default()); // then consensus reachable, but single node will disagree while let Some((from, to, message)) = sl.take_message() { diff --git a/secret_store/src/key_server_cluster/cluster.rs b/secret_store/src/key_server_cluster/cluster.rs index 22590f1a7..c90474fa6 100644 --- a/secret_store/src/key_server_cluster/cluster.rs +++ b/secret_store/src/key_server_cluster/cluster.rs @@ -26,8 +26,8 @@ use parking_lot::{RwLock, Mutex}; use tokio_io::IoFuture; use tokio_core::reactor::{Handle, Remote, Interval}; use tokio_core::net::{TcpListener, TcpStream}; -use ethkey::{Public, KeyPair, Signature, Random, Generator, public_to_address}; -use ethereum_types::H256; +use ethkey::{Public, KeyPair, Signature, Random, Generator}; +use ethereum_types::{Address, H256}; use key_server_cluster::{Error, NodeId, SessionId, Requester, AclStorage, KeyStorage, KeyServerSet, NodeKeyPair}; use key_server_cluster::cluster_sessions::{ClusterSession, AdminSession, ClusterSessions, SessionIdWithSubSession, ClusterSessionsContainer, SERVERS_SET_CHANGE_SESSION_ID, create_cluster_view, AdminSessionCreationData, ClusterSessionsListener}; @@ -66,11 +66,11 @@ pub trait ClusterClient: Send + Sync { /// Get cluster state. fn cluster_state(&self) -> ClusterState; /// Start new generation session. - fn new_generation_session(&self, session_id: SessionId, author: Public, threshold: usize) -> Result, Error>; + fn new_generation_session(&self, session_id: SessionId, origin: Option
, author: Address, threshold: usize) -> Result, Error>; /// Start new encryption session. - fn new_encryption_session(&self, session_id: SessionId, requester: Requester, common_point: Public, encrypted_point: Public) -> Result, Error>; + fn new_encryption_session(&self, session_id: SessionId, author: Requester, common_point: Public, encrypted_point: Public) -> Result, Error>; /// Start new decryption session. - fn new_decryption_session(&self, session_id: SessionId, requester: Requester, version: Option, is_shadow_decryption: bool) -> Result, Error>; + fn new_decryption_session(&self, session_id: SessionId, origin: Option
, requester: Requester, version: Option, is_shadow_decryption: bool, is_broadcast_decryption: bool) -> Result, Error>; /// Start new Schnorr signing session. fn new_schnorr_signing_session(&self, session_id: SessionId, requester: Requester, version: Option, message_hash: H256) -> Result, Error>; /// Start new ECDSA session. @@ -82,6 +82,8 @@ pub trait ClusterClient: Send + Sync { /// Listen for new generation sessions. fn add_generation_listener(&self, listener: Arc>); + /// Listen for new decryption sessions. + fn add_decryption_listener(&self, listener: Arc>); /// Ask node to make 'faulty' generation sessions. #[cfg(test)] @@ -477,11 +479,11 @@ impl ClusterCore { data.sessions.negotiation_sessions.remove(&session.id()); match session.wait() { Ok((version, master)) => match session.take_continue_action() { - Some(ContinueAction::Decrypt(session, is_shadow_decryption)) => { + Some(ContinueAction::Decrypt(session, origin, is_shadow_decryption, is_broadcast_decryption)) => { let initialization_error = if data.self_key_pair.public() == &master { - session.initialize(version, is_shadow_decryption, false) + session.initialize(origin, version, is_shadow_decryption, is_broadcast_decryption) } else { - session.delegate(master, version, is_shadow_decryption, false) + session.delegate(master, origin, version, is_shadow_decryption, is_broadcast_decryption) }; if let Err(error) = initialization_error { @@ -516,7 +518,7 @@ impl ClusterCore { None => (), }, Err(error) => match session.take_continue_action() { - Some(ContinueAction::Decrypt(session, _)) => { + Some(ContinueAction::Decrypt(session, _, _, _)) => { data.sessions.decryption_sessions.remove(&session.id()); session.on_session_error(&meta.self_node_id, error); }, @@ -901,13 +903,13 @@ impl ClusterClient for ClusterClientImpl { self.data.connections.cluster_state() } - fn new_generation_session(&self, session_id: SessionId, author: Public, threshold: usize) -> Result, Error> { + fn new_generation_session(&self, session_id: SessionId, origin: Option
, author: Address, 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 = create_cluster_view(&self.data, true)?; let session = self.data.sessions.generation_sessions.insert(cluster, self.data.self_key_pair.public().clone(), session_id, None, false, None)?; - match session.initialize(public_to_address(&author), false, threshold, connected_nodes.into()) { + match session.initialize(origin, author, false, threshold, connected_nodes.into()) { Ok(()) => Ok(session), Err(error) => { self.data.sessions.generation_sessions.remove(&session.id()); @@ -931,7 +933,7 @@ impl ClusterClient for ClusterClientImpl { } } - fn new_decryption_session(&self, session_id: SessionId, requester: Requester, version: Option, is_shadow_decryption: bool) -> Result, Error> { + fn new_decryption_session(&self, session_id: SessionId, origin: Option
, requester: Requester, version: Option, is_shadow_decryption: bool, is_broadcast_decryption: bool) -> Result, Error> { let mut connected_nodes = self.data.connections.connected_nodes(); connected_nodes.insert(self.data.self_key_pair.public().clone()); @@ -942,11 +944,11 @@ impl ClusterClient for ClusterClientImpl { session_id.clone(), None, false, Some(requester))?; let initialization_result = match version { - Some(version) => session.initialize(version, is_shadow_decryption, false), + Some(version) => session.initialize(origin, version, is_shadow_decryption, is_broadcast_decryption), None => { self.create_key_version_negotiation_session(session_id.id.clone()) .map(|version_session| { - version_session.set_continue_action(ContinueAction::Decrypt(session.clone(), is_shadow_decryption)); + version_session.set_continue_action(ContinueAction::Decrypt(session.clone(), origin, is_shadow_decryption, is_broadcast_decryption)); ClusterCore::try_continue_session(&self.data, Some(version_session)); }) }, @@ -1056,6 +1058,10 @@ impl ClusterClient for ClusterClientImpl { self.data.sessions.generation_sessions.add_listener(listener); } + fn add_decryption_listener(&self, listener: Arc>) { + self.data.sessions.decryption_sessions.add_listener(listener); + } + #[cfg(test)] fn connect(&self) { ClusterCore::connect_disconnected_nodes(self.data.clone()); @@ -1085,11 +1091,12 @@ fn make_socket_address(address: &str, port: u16) -> Result { #[cfg(test)] pub mod tests { use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::{Duration, Instant}; use std::collections::{BTreeSet, VecDeque}; use parking_lot::Mutex; use tokio_core::reactor::Core; - use ethereum_types::H256; + use ethereum_types::{Address, H256}; use ethkey::{Random, Generator, Public, Signature, sign}; use key_server_cluster::{NodeId, SessionId, Requester, Error, DummyAclStorage, DummyKeyStorage, MapKeyServerSet, PlainNodeKeyPair, KeyStorage}; @@ -1107,7 +1114,9 @@ pub mod tests { const TIMEOUT: Duration = Duration::from_millis(300); #[derive(Default)] - pub struct DummyClusterClient; + pub struct DummyClusterClient { + pub generation_requests_count: AtomicUsize, + } #[derive(Debug)] pub struct DummyCluster { @@ -1123,15 +1132,20 @@ pub mod tests { impl ClusterClient for DummyClusterClient { fn cluster_state(&self) -> ClusterState { unimplemented!("test-only") } - fn new_generation_session(&self, _session_id: SessionId, _author: Public, _threshold: usize) -> Result, Error> { unimplemented!("test-only") } + fn new_generation_session(&self, _session_id: SessionId, _origin: Option
, _author: Address, _threshold: usize) -> Result, Error> { + self.generation_requests_count.fetch_add(1, Ordering::Relaxed); + Err(Error::Io("test-errror".into())) + } fn new_encryption_session(&self, _session_id: SessionId, _requester: Requester, _common_point: Public, _encrypted_point: Public) -> Result, Error> { unimplemented!("test-only") } - fn new_decryption_session(&self, _session_id: SessionId, _requester: Requester, _version: Option, _is_shadow_decryption: bool) -> Result, Error> { unimplemented!("test-only") } + fn new_decryption_session(&self, _session_id: SessionId, _origin: Option
, _requester: Requester, _version: Option, _is_shadow_decryption: bool, _is_broadcast_session: bool) -> Result, Error> { unimplemented!("test-only") } fn new_schnorr_signing_session(&self, _session_id: SessionId, _requester: Requester, _version: Option, _message_hash: H256) -> Result, Error> { unimplemented!("test-only") } fn new_ecdsa_signing_session(&self, _session_id: SessionId, _requester: Requester, _version: Option, _message_hash: H256) -> Result, Error> { unimplemented!("test-only") } + fn new_key_version_negotiation_session(&self, _session_id: SessionId) -> Result>, Error> { unimplemented!("test-only") } fn new_servers_set_change_session(&self, _session_id: Option, _migration_id: Option, _new_nodes_set: BTreeSet, _old_set_signature: Signature, _new_set_signature: Signature) -> Result, Error> { unimplemented!("test-only") } fn add_generation_listener(&self, _listener: Arc>) {} + fn add_decryption_listener(&self, _listener: Arc>) {} fn make_faulty_generation_sessions(&self) { unimplemented!("test-only") } fn generation_session(&self, _session_id: &SessionId) -> Option> { unimplemented!("test-only") } @@ -1258,7 +1272,7 @@ pub mod tests { let core = Core::new().unwrap(); let clusters = make_clusters(&core, 6013, 3); clusters[0].run().unwrap(); - match clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1) { + match clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1) { Err(Error::NodeDisconnected) => (), Err(e) => panic!("unexpected error {:?}", e), _ => panic!("unexpected success"), @@ -1277,7 +1291,7 @@ pub mod tests { clusters[1].client().make_faulty_generation_sessions(); // start && wait for generation session to fail - let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap(); + let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap(); loop_until(&mut core, TIMEOUT, || session.joint_public_and_secret().is_some() && clusters[0].client().generation_session(&SessionId::default()).is_none()); assert!(session.joint_public_and_secret().unwrap().is_err()); @@ -1306,7 +1320,7 @@ pub mod tests { clusters[0].client().make_faulty_generation_sessions(); // start && wait for generation session to fail - let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap(); + let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap(); loop_until(&mut core, TIMEOUT, || session.joint_public_and_secret().is_some() && clusters[0].client().generation_session(&SessionId::default()).is_none()); assert!(session.joint_public_and_secret().unwrap().is_err()); @@ -1332,7 +1346,7 @@ pub mod tests { loop_until(&mut core, TIMEOUT, || clusters.iter().all(all_connections_established)); // start && wait for generation session to complete - let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap(); + let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap(); loop_until(&mut core, TIMEOUT, || (session.state() == GenerationSessionState::Finished || session.state() == GenerationSessionState::Failed) && clusters[0].client().generation_session(&SessionId::default()).is_none()); @@ -1359,11 +1373,11 @@ pub mod tests { // generation session { // try to start generation session => fail in initialization - assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 100).map(|_| ()), + assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 100).map(|_| ()), Err(Error::InvalidThreshold)); // try to start generation session => fails in initialization - assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 100).map(|_| ()), + assert_eq!(clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 100).map(|_| ()), Err(Error::InvalidThreshold)); assert!(clusters[0].data.sessions.generation_sessions.is_empty()); @@ -1372,11 +1386,11 @@ pub mod tests { // decryption session { // try to start decryption session => fails in initialization - assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Some(Default::default()), false).map(|_| ()), + assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Default::default(), Some(Default::default()), false, false).map(|_| ()), Err(Error::InvalidMessage)); // try to start generation session => fails in initialization - assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Some(Default::default()), false).map(|_| ()), + assert_eq!(clusters[0].client().new_decryption_session(Default::default(), Default::default(), Default::default(), Some(Default::default()), false, false).map(|_| ()), Err(Error::InvalidMessage)); assert!(clusters[0].data.sessions.decryption_sessions.is_empty()); @@ -1393,7 +1407,7 @@ pub mod tests { loop_until(&mut core, TIMEOUT, || clusters.iter().all(all_connections_established)); // start && wait for generation session to complete - let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap(); + let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap(); loop_until(&mut core, TIMEOUT, || (session.state() == GenerationSessionState::Finished || session.state() == GenerationSessionState::Failed) && clusters[0].client().generation_session(&SessionId::default()).is_none()); @@ -1442,7 +1456,7 @@ pub mod tests { loop_until(&mut core, TIMEOUT, || clusters.iter().all(all_connections_established)); // start && wait for generation session to complete - let session = clusters[0].client().new_generation_session(SessionId::default(), Public::default(), 1).unwrap(); + let session = clusters[0].client().new_generation_session(SessionId::default(), Default::default(), Default::default(), 1).unwrap(); loop_until(&mut core, TIMEOUT, || (session.state() == GenerationSessionState::Finished || session.state() == GenerationSessionState::Failed) && clusters[0].client().generation_session(&SessionId::default()).is_none()); diff --git a/secret_store/src/key_server_cluster/cluster_sessions.rs b/secret_store/src/key_server_cluster/cluster_sessions.rs index cdd0c1958..e67458f76 100644 --- a/secret_store/src/key_server_cluster/cluster_sessions.rs +++ b/secret_store/src/key_server_cluster/cluster_sessions.rs @@ -84,10 +84,10 @@ pub trait ClusterSession { fn on_message(&self, sender: &NodeId, message: &Message) -> Result<(), Error>; /// 'Wait for session completion' helper. - fn wait_session Option>>(completion_event: &Condvar, session_data: &Mutex, timeout: Option, result_reader: F) -> Result { + fn wait_session Option>>(completion_event: &Condvar, session_data: &Mutex, timeout: Option, result_reader: F) -> Option> { let mut locked_data = session_data.lock(); match result_reader(&locked_data) { - Some(result) => result, + Some(result) => Some(result), None => { match timeout { None => completion_event.wait(&mut locked_data), @@ -97,7 +97,6 @@ pub trait ClusterSession { } result_reader(&locked_data) - .expect("waited for completion; completion is only signaled when result.is_some(); qed") }, } } @@ -563,12 +562,14 @@ pub fn create_cluster_view(data: &Arc, requires_all_connections: bo #[cfg(test)] mod tests { use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; use ethkey::{Random, Generator}; use key_server_cluster::{Error, DummyAclStorage, DummyKeyStorage, MapKeyServerSet, PlainNodeKeyPair}; use key_server_cluster::cluster::ClusterConfiguration; use key_server_cluster::connection_trigger::SimpleServersSetChangeSessionCreatorConnector; use key_server_cluster::cluster::tests::DummyCluster; - use super::{ClusterSessions, AdminSessionCreationData}; + use key_server_cluster::generation_session::{SessionImpl as GenerationSession}; + use super::{ClusterSessions, AdminSessionCreationData, ClusterSessionsListener}; pub fn make_cluster_sessions() -> ClusterSessions { let key_pair = Random.generate().unwrap(); @@ -610,4 +611,35 @@ mod tests { Ok(_) => unreachable!("OK"), } } + + #[test] + fn session_listener_works() { + #[derive(Default)] + struct GenerationSessionListener { + inserted: AtomicUsize, + removed: AtomicUsize, + } + + impl ClusterSessionsListener for GenerationSessionListener { + fn on_session_inserted(&self, _session: Arc) { + self.inserted.fetch_add(1, Ordering::Relaxed); + } + + fn on_session_removed(&self, _session: Arc) { + self.removed.fetch_add(1, Ordering::Relaxed); + } + } + + let listener = Arc::new(GenerationSessionListener::default()); + let sessions = make_cluster_sessions(); + sessions.generation_sessions.add_listener(listener.clone()); + + sessions.generation_sessions.insert(Arc::new(DummyCluster::new(Default::default())), Default::default(), Default::default(), None, false, None).unwrap(); + assert_eq!(listener.inserted.load(Ordering::Relaxed), 1); + assert_eq!(listener.removed.load(Ordering::Relaxed), 0); + + sessions.generation_sessions.remove(&Default::default()); + assert_eq!(listener.inserted.load(Ordering::Relaxed), 1); + assert_eq!(listener.removed.load(Ordering::Relaxed), 1); + } } diff --git a/secret_store/src/key_server_cluster/jobs/consensus_session.rs b/secret_store/src/key_server_cluster/jobs/consensus_session.rs index 42de71b54..cc4ebc6d9 100644 --- a/secret_store/src/key_server_cluster/jobs/consensus_session.rs +++ b/secret_store/src/key_server_cluster/jobs/consensus_session.rs @@ -17,7 +17,7 @@ use std::collections::BTreeSet; use key_server_cluster::{Error, NodeId, SessionMeta, Requester}; use key_server_cluster::message::ConsensusMessage; -use key_server_cluster::jobs::job_session::{JobSession, JobSessionState, JobTransport, JobExecutor}; +use key_server_cluster::jobs::job_session::{JobSession, JobSessionState, JobTransport, JobExecutor, JobPartialRequestAction}; /// Consensus session state. #[derive(Debug, Clone, Copy, PartialEq)] @@ -114,7 +114,6 @@ impl &JobSession { self.computation_job.as_ref() .expect("computation_job must only be called on master nodes") @@ -140,15 +139,15 @@ impl) -> Result<(), Error> { debug_assert!(self.meta.self_node_id == self.meta.master_node_id); - let initialization_result = self.consensus_job.initialize(nodes, false); + let initialization_result = self.consensus_job.initialize(nodes, None, false); self.state = ConsensusSessionState::EstablishingConsensus; - self.process_result(initialization_result) + self.process_result(initialization_result.map(|_| ())) } /// Process consensus request message. pub fn on_consensus_partial_request(&mut self, sender: &NodeId, request: ConsensusExecutor::PartialJobRequest) -> Result<(), Error> { let consensus_result = self.consensus_job.on_partial_request(sender, request); - self.process_result(consensus_result) + self.process_result(consensus_result.map(|_| ())) } /// Process consensus message response. @@ -179,19 +178,22 @@ impl Result<(), Error> { + pub fn disseminate_jobs(&mut self, executor: ComputationExecutor, transport: ComputationTransport, broadcast_self_response: bool) -> Result, Error> { let consensus_group = self.select_consensus_group()?.clone(); self.consensus_group.clear(); let mut computation_job = JobSession::new(self.meta.clone(), executor, transport); - let computation_result = computation_job.initialize(consensus_group, broadcast_self_response); + let computation_result = computation_job.initialize(consensus_group, None, broadcast_self_response); self.computation_job = Some(computation_job); self.state = ConsensusSessionState::WaitingForPartialResults; - self.process_result(computation_result) + match computation_result { + Ok(computation_result) => self.process_result(Ok(())).map(|_| computation_result), + Err(error) => Err(self.process_result(Err(error)).unwrap_err()), + } } /// Process job request on slave node. - pub fn on_job_request(&mut self, node: &NodeId, request: ComputationExecutor::PartialJobRequest, executor: ComputationExecutor, transport: ComputationTransport) -> Result<(), Error> { + pub fn on_job_request(&mut self, node: &NodeId, request: ComputationExecutor::PartialJobRequest, executor: ComputationExecutor, transport: ComputationTransport) -> Result, Error> { if &self.meta.master_node_id != node { return Err(Error::InvalidMessage); } @@ -350,7 +352,7 @@ impl - self.consensus_job.on_partial_request(sender, message.requester.clone().into()), + self.consensus_job.on_partial_request(sender, message.requester.clone().into()).map(|_| ()), &ConsensusMessage::ConfirmConsensusInitialization(ref message) => self.consensus_job.on_partial_response(sender, message.is_confirmed), }; @@ -361,7 +363,7 @@ impl None, Some(decrypt_shadow) => Some(encrypt(&self.requester, &DEFAULT_MAC, &**decrypt_shadow)?), }, diff --git a/secret_store/src/key_server_cluster/jobs/job_session.rs b/secret_store/src/key_server_cluster/jobs/job_session.rs index 0299fdb14..82f387d7b 100644 --- a/secret_store/src/key_server_cluster/jobs/job_session.rs +++ b/secret_store/src/key_server_cluster/jobs/job_session.rs @@ -197,7 +197,7 @@ impl JobSession where Executor: JobExe } /// Initialize. - pub fn initialize(&mut self, nodes: BTreeSet, broadcast_self_response: bool) -> Result<(), Error> { + pub fn initialize(&mut self, nodes: BTreeSet, self_response: Option, broadcast_self_response: bool) -> Result, Error> { debug_assert!(self.meta.self_node_id == self.meta.master_node_id); if nodes.len() < self.meta.threshold + 1 { @@ -215,15 +215,13 @@ impl JobSession where Executor: JobExe responses: BTreeMap::new(), }; let waits_for_self = active_data.requests.contains(&self.meta.self_node_id); - let self_response = if waits_for_self { - let partial_request = self.executor.prepare_partial_request(&self.meta.self_node_id, &active_data.requests)?; - Some(self.executor.process_partial_request(partial_request)?) - } else { - None - }; let self_response = match self_response { - Some(JobPartialRequestAction::Respond(self_response)) => Some(self_response), - Some(JobPartialRequestAction::Reject(self_response)) => Some(self_response), + Some(self_response) => Some(self_response), + None if waits_for_self => { + let partial_request = self.executor.prepare_partial_request(&self.meta.self_node_id, &active_data.requests)?; + let self_response = self.executor.process_partial_request(partial_request)?; + Some(self_response.take_response()) + }, None => None, }; @@ -249,11 +247,11 @@ impl JobSession where Executor: JobExe } } - Ok(()) + Ok(self_response) } /// When partial request is received by slave node. - pub fn on_partial_request(&mut self, node: &NodeId, request: Executor::PartialJobRequest) -> Result<(), Error> { + pub fn on_partial_request(&mut self, node: &NodeId, request: Executor::PartialJobRequest) -> Result, Error> { if node != &self.meta.master_node_id { return Err(Error::InvalidMessage); } @@ -264,17 +262,19 @@ impl JobSession where Executor: JobExe return Err(Error::InvalidStateForRequest); } - let partial_response = match self.executor.process_partial_request(request)? { - JobPartialRequestAction::Respond(partial_response) => { + let partial_request_action = self.executor.process_partial_request(request)?; + let partial_response = match partial_request_action { + JobPartialRequestAction::Respond(ref partial_response) => { self.data.state = JobSessionState::Finished; - partial_response + partial_response.clone() }, - JobPartialRequestAction::Reject(partial_response) => { + JobPartialRequestAction::Reject(ref partial_response) => { self.data.state = JobSessionState::Failed; - partial_response + partial_response.clone() }, }; - self.transport.send_partial_response(node, partial_response) + self.transport.send_partial_response(node, partial_response)?; + Ok(partial_request_action) } /// When partial request is received by master node. @@ -291,7 +291,7 @@ impl JobSession where Executor: JobExe if !active_data.requests.remove(node) { return Err(Error::InvalidNodeForRequest); } - + match self.executor.check_partial_response(node, &response)? { JobPartialResponseAction::Ignore => Ok(()), JobPartialResponseAction::Reject => { @@ -358,6 +358,15 @@ impl JobSession where Executor: JobExe } } +impl JobPartialRequestAction { + /// Take actual response. + pub fn take_response(self) -> PartialJobResponse { + match self { + JobPartialRequestAction::Respond(response) => response, + JobPartialRequestAction::Reject(response) => response, + } + } +} #[cfg(test)] pub mod tests { @@ -415,14 +424,14 @@ pub mod tests { #[test] fn job_initialize_fails_if_not_inactive() { let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap(); - assert_eq!(job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap_err(), Error::InvalidStateForRequest); + job.initialize(vec![Public::from(1)].into_iter().collect(), None, false).unwrap(); + assert_eq!(job.initialize(vec![Public::from(1)].into_iter().collect(), None, false).unwrap_err(), Error::InvalidStateForRequest); } #[test] fn job_initialization_leads_to_finish_if_single_node_is_required() { let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Finished); assert!(job.is_result_ready()); assert_eq!(job.result(), Ok(4)); @@ -431,7 +440,7 @@ pub mod tests { #[test] fn job_initialization_does_not_leads_to_finish_if_single_other_node_is_required() { let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(2)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); } @@ -474,7 +483,7 @@ pub mod tests { #[test] fn job_response_fails_if_comes_to_failed_state() { let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(2)].into_iter().collect(), None, false).unwrap(); job.on_session_timeout().unwrap_err(); assert_eq!(job.on_partial_response(&NodeId::from(2), 2).unwrap_err(), Error::InvalidStateForRequest); } @@ -482,14 +491,14 @@ pub mod tests { #[test] fn job_response_fails_if_comes_from_unknown_node() { let mut job = JobSession::new(make_master_session_meta(0), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(2)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.on_partial_response(&NodeId::from(3), 2).unwrap_err(), Error::InvalidNodeForRequest); } #[test] fn job_response_leads_to_failure_if_too_few_nodes_left() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); assert_eq!(job.on_partial_response(&NodeId::from(2), 3).unwrap_err(), Error::ConsensusUnreachable); assert_eq!(job.state(), JobSessionState::Failed); @@ -498,7 +507,7 @@ pub mod tests { #[test] fn job_response_succeeds() { let mut job = JobSession::new(make_master_session_meta(2), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); assert!(!job.is_result_ready()); job.on_partial_response(&NodeId::from(2), 2).unwrap(); @@ -509,7 +518,7 @@ pub mod tests { #[test] fn job_response_leads_to_finish() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); job.on_partial_response(&NodeId::from(2), 2).unwrap(); assert_eq!(job.state(), JobSessionState::Finished); @@ -534,7 +543,7 @@ pub mod tests { #[test] fn job_node_error_ignored_when_disconnects_from_rejected() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); job.on_partial_response(&NodeId::from(2), 3).unwrap(); job.on_node_error(&NodeId::from(2)).unwrap(); @@ -544,7 +553,7 @@ pub mod tests { #[test] fn job_node_error_ignored_when_disconnects_from_unknown() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); job.on_node_error(&NodeId::from(3)).unwrap(); assert_eq!(job.state(), JobSessionState::Active); @@ -553,7 +562,7 @@ pub mod tests { #[test] fn job_node_error_ignored_when_disconnects_from_requested_and_enough_nodes_left() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2), Public::from(3)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); job.on_node_error(&NodeId::from(3)).unwrap(); assert_eq!(job.state(), JobSessionState::Active); @@ -562,7 +571,7 @@ pub mod tests { #[test] fn job_node_error_leads_to_fail_when_disconnects_from_requested_and_not_enough_nodes_left() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); assert_eq!(job.on_node_error(&NodeId::from(2)).unwrap_err(), Error::ConsensusUnreachable); assert_eq!(job.state(), JobSessionState::Failed); @@ -571,7 +580,7 @@ pub mod tests { #[test] fn job_broadcasts_self_response() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), true).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, true).unwrap(); assert_eq!(job.state(), JobSessionState::Active); assert_eq!(job.transport().response(), (NodeId::from(2), 4)); } @@ -579,7 +588,7 @@ pub mod tests { #[test] fn job_does_not_broadcasts_self_response() { let mut job = JobSession::new(make_master_session_meta(1), SquaredSumJobExecutor, DummyJobTransport::default()); - job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), false).unwrap(); + job.initialize(vec![Public::from(1), Public::from(2)].into_iter().collect(), None, false).unwrap(); assert_eq!(job.state(), JobSessionState::Active); assert!(job.transport().is_empty_response()); } diff --git a/secret_store/src/key_server_cluster/jobs/key_access_job.rs b/secret_store/src/key_server_cluster/jobs/key_access_job.rs index 2cb686e6c..a47385b5a 100644 --- a/secret_store/src/key_server_cluster/jobs/key_access_job.rs +++ b/secret_store/src/key_server_cluster/jobs/key_access_job.rs @@ -78,7 +78,7 @@ impl JobExecutor for KeyAccessJob { } self.requester = Some(partial_request.clone()); - self.acl_storage.check(&partial_request.public(&self.id).ok_or(Error::InsufficientRequesterData)?, &self.id) + self.acl_storage.check(partial_request.address(&self.id).map_err(Error::InsufficientRequesterData)?, &self.id) .map_err(|_| Error::AccessDenied) .map(|is_confirmed| if is_confirmed { JobPartialRequestAction::Respond(true) } else { JobPartialRequestAction::Reject(false) }) } diff --git a/secret_store/src/key_server_cluster/message.rs b/secret_store/src/key_server_cluster/message.rs index 9a93efb03..e08d6761a 100644 --- a/secret_store/src/key_server_cluster/message.rs +++ b/secret_store/src/key_server_cluster/message.rs @@ -272,6 +272,8 @@ pub struct InitializeSession { pub session: MessageSessionId, /// Session-level nonce. pub session_nonce: u64, + /// Session origin address (if any). + pub origin: Option, /// Session author. pub author: SerializableAddress, /// All session participants along with their identification numbers. @@ -713,6 +715,8 @@ pub struct DecryptionConsensusMessage { pub sub_session: SerializableSecret, /// Session-level nonce. pub session_nonce: u64, + /// Session origin (in consensus initialization message). + pub origin: Option, /// Consensus message. pub message: ConsensusMessage, } @@ -788,6 +792,8 @@ pub struct DecryptionSessionDelegation { pub sub_session: SerializableSecret, /// Session-level nonce. pub session_nonce: u64, + /// Session origin. + pub origin: Option, /// Requester. pub requester: SerializableRequester, /// Key version. diff --git a/secret_store/src/key_server_cluster/mod.rs b/secret_store/src/key_server_cluster/mod.rs index 17334e838..804c85e31 100644 --- a/secret_store/src/key_server_cluster/mod.rs +++ b/secret_store/src/key_server_cluster/mod.rs @@ -115,7 +115,7 @@ pub enum Error { /// Can't start exclusive session, because there are other active sessions. HasActiveSessions, /// Insufficient requester data. - InsufficientRequesterData, + InsufficientRequesterData(String), } impl From for Error { @@ -164,7 +164,7 @@ impl fmt::Display for Error { Error::AccessDenied => write!(f, "Access denied"), Error::ExclusiveSessionActive => write!(f, "Exclusive session active"), Error::HasActiveSessions => write!(f, "Unable to start exclusive session"), - Error::InsufficientRequesterData => write!(f, "Insufficient requester data"), + Error::InsufficientRequesterData(ref e) => write!(f, "Insufficient requester data: {}", e), } } } diff --git a/secret_store/src/key_storage.rs b/secret_store/src/key_storage.rs index 4429500db..1d4b968c0 100644 --- a/secret_store/src/key_storage.rs +++ b/secret_store/src/key_storage.rs @@ -475,6 +475,10 @@ pub mod tests { let config = ServiceConfiguration { listener_address: None, service_contract_address: None, + service_contract_srv_gen_address: None, + service_contract_srv_retr_address: None, + service_contract_doc_store_address: None, + service_contract_doc_sretr_address: None, acl_check_enabled: true, data_path: tempdir.path().display().to_string(), cluster_config: ClusterConfiguration { diff --git a/secret_store/src/lib.rs b/secret_store/src/lib.rs index 9d89ba0c6..e796ff4bc 100644 --- a/secret_store/src/lib.rs +++ b/secret_store/src/lib.rs @@ -86,26 +86,75 @@ pub fn start(client: Arc, sync: Arc, self_key_pair: Arc = key_server; - // prepare listeners + // prepare HTTP listener let http_listener = match config.listener_address { - Some(listener_address) => Some(listener::http_listener::KeyServerHttpListener::start(listener_address, key_server.clone())?), + Some(listener_address) => Some(listener::http_listener::KeyServerHttpListener::start(listener_address, Arc::downgrade(&key_server))?), None => None, }; - let contract_listener = config.service_contract_address.map(|service_contract_address| { - let service_contract = Arc::new(listener::service_contract::OnChainServiceContract::new(trusted_client, service_contract_address, self_key_pair.clone())); - let contract_listener = listener::service_contract_listener::ServiceContractListener::new(listener::service_contract_listener::ServiceContractListenerParams { - contract: service_contract, - key_server: key_server.clone(), - self_key_pair: self_key_pair, - key_server_set: key_server_set, - cluster: cluster, - key_storage: key_storage, - }); - client.add_notify(contract_listener.clone()); - contract_listener - }); + + // prepare service contract listeners + let create_service_contract = |address, name, api_mask| + Arc::new(listener::service_contract::OnChainServiceContract::new( + api_mask, + trusted_client.clone(), + name, + address, + self_key_pair.clone())); + + let mut contracts: Vec> = Vec::new(); + config.service_contract_address.map(|address| + create_service_contract(address, + listener::service_contract::SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), + listener::ApiMask::all())) + .map(|l| contracts.push(l)); + config.service_contract_srv_gen_address.map(|address| + create_service_contract(address, + listener::service_contract::SRV_KEY_GEN_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), + listener::ApiMask { server_key_generation_requests: true, ..Default::default() })) + .map(|l| contracts.push(l)); + config.service_contract_srv_retr_address.map(|address| + create_service_contract(address, + listener::service_contract::SRV_KEY_RETR_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), + listener::ApiMask { server_key_retrieval_requests: true, ..Default::default() })) + .map(|l| contracts.push(l)); + config.service_contract_doc_store_address.map(|address| + create_service_contract(address, + listener::service_contract::DOC_KEY_STORE_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), + listener::ApiMask { document_key_store_requests: true, ..Default::default() })) + .map(|l| contracts.push(l)); + config.service_contract_doc_sretr_address.map(|address| + create_service_contract(address, + listener::service_contract::DOC_KEY_SRETR_SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), + listener::ApiMask { document_key_shadow_retrieval_requests: true, ..Default::default() })) + .map(|l| contracts.push(l)); + + let contract: Option> = match contracts.len() { + 0 => None, + 1 => Some(contracts.pop().expect("contract.len() is 1; qed")), + _ => Some(Arc::new(listener::service_contract_aggregate::OnChainServiceContractAggregate::new(contracts))), + }; + + let contract_listener = match contract { + Some(contract) => Some({ + let listener = listener::service_contract_listener::ServiceContractListener::new( + listener::service_contract_listener::ServiceContractListenerParams { + contract: contract, + self_key_pair: self_key_pair.clone(), + key_server_set: key_server_set, + acl_storage: acl_storage, + cluster: cluster, + key_storage: key_storage, + } + )?; + client.add_notify(listener.clone()); + listener + }), + None => None, + }; + Ok(Box::new(listener::Listener::new(key_server, http_listener, contract_listener))) } diff --git a/secret_store/src/listener/http_listener.rs b/secret_store/src/listener/http_listener.rs index 4d34f984b..1680201e1 100644 --- a/secret_store/src/listener/http_listener.rs +++ b/secret_store/src/listener/http_listener.rs @@ -16,7 +16,7 @@ use std::collections::BTreeSet; use std::io::Read; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use hyper::header; use hyper::uri::RequestUri; use hyper::method::Method as HttpMethod; @@ -77,12 +77,12 @@ struct KeyServerHttpHandler { /// Shared http handler struct KeyServerSharedHttpHandler { - key_server: Arc, + key_server: Weak, } impl KeyServerHttpListener { /// Start KeyServer http listener - pub fn start(listener_address: NodeAddress, key_server: Arc) -> Result { + pub fn start(listener_address: NodeAddress, key_server: Weak) -> Result { let shared_handler = Arc::new(KeyServerSharedHttpHandler { key_server: key_server, }); @@ -128,56 +128,72 @@ impl HttpHandler for KeyServerHttpHandler { match &req_uri { &RequestUri::AbsolutePath(ref path) => match parse_request(&req_method, &path, &req_body) { Request::GenerateServerKey(document, signature, threshold) => { - return_server_public_key(req, res, self.handler.key_server.generate_key(&document, &signature, threshold) + return_server_public_key(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.generate_key(&document, &signature.into(), threshold)) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "GenerateServerKey request {} has failed with: {}", req_uri, err); err })); }, Request::StoreDocumentKey(document, signature, common_point, encrypted_document_key) => { - return_empty(req, res, self.handler.key_server.store_document_key(&document, &signature, common_point, encrypted_document_key) + return_empty(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.store_document_key(&document, &signature.into(), common_point, encrypted_document_key)) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "StoreDocumentKey request {} has failed with: {}", req_uri, err); err })); }, Request::GenerateDocumentKey(document, signature, threshold) => { - return_document_key(req, res, self.handler.key_server.generate_document_key(&document, &signature, threshold) + return_document_key(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.generate_document_key(&document, &signature.into(), threshold)) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "GenerateDocumentKey request {} has failed with: {}", req_uri, err); err })); }, Request::GetDocumentKey(document, signature) => { - return_document_key(req, res, self.handler.key_server.restore_document_key(&document, &signature) + return_document_key(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.restore_document_key(&document, &signature.into())) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req_uri, err); err })); }, Request::GetDocumentKeyShadow(document, signature) => { - return_document_key_shadow(req, res, self.handler.key_server.restore_document_key_shadow(&document, &signature) + return_document_key_shadow(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.restore_document_key_shadow(&document, &signature.into())) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "GetDocumentKeyShadow request {} has failed with: {}", req_uri, err); err })); }, Request::SchnorrSignMessage(document, signature, message_hash) => { - return_message_signature(req, res, self.handler.key_server.sign_message_schnorr(&document, &signature, message_hash) + return_message_signature(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.sign_message_schnorr(&document, &signature.into(), message_hash)) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "SchnorrSignMessage request {} has failed with: {}", req_uri, err); err })); }, Request::EcdsaSignMessage(document, signature, message_hash) => { - return_message_signature(req, res, self.handler.key_server.sign_message_ecdsa(&document, &signature, message_hash) + return_message_signature(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.sign_message_ecdsa(&document, &signature.into(), message_hash)) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "EcdsaSignMessage request {} has failed with: {}", req_uri, err); err })); }, Request::ChangeServersSet(old_set_signature, new_set_signature, new_servers_set) => { - return_empty(req, res, self.handler.key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set) + return_empty(req, res, self.handler.key_server.upgrade() + .map(|key_server| key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set)) + .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .map_err(|err| { warn!(target: "secretstore", "ChangeServersSet request {} has failed with: {}", req_uri, err); err @@ -241,7 +257,7 @@ fn return_bytes(req: HttpRequest, mut res: HttpResponse, result: R fn return_error(mut res: HttpResponse, err: Error) { match err { - Error::BadSignature => *res.status_mut() = HttpStatusCode::BadRequest, + Error::InsufficientRequesterData(_) => *res.status_mut() = HttpStatusCode::BadRequest, Error::AccessDenied => *res.status_mut() = HttpStatusCode::Forbidden, Error::DocumentNotFound => *res.status_mut() = HttpStatusCode::NotFound, Error::Hyper(_) => *res.status_mut() = HttpStatusCode::BadRequest, @@ -342,15 +358,16 @@ mod tests { use std::sync::Arc; use hyper::method::Method as HttpMethod; use ethkey::Public; + use traits::KeyServer; use key_server::tests::DummyKeyServer; use types::all::NodeAddress; use super::{parse_request, Request, KeyServerHttpListener}; #[test] fn http_listener_successfully_drops() { - let key_server = Arc::new(DummyKeyServer::default()); + let key_server: Arc = Arc::new(DummyKeyServer::default()); let address = NodeAddress { address: "127.0.0.1".into(), port: 9000 }; - let listener = KeyServerHttpListener::start(address, key_server).unwrap(); + let listener = KeyServerHttpListener::start(address, Arc::downgrade(&key_server)).unwrap(); drop(listener); } diff --git a/secret_store/src/listener/mod.rs b/secret_store/src/listener/mod.rs index df96c583d..2c4fbaf4c 100644 --- a/secret_store/src/listener/mod.rs +++ b/secret_store/src/listener/mod.rs @@ -16,6 +16,7 @@ pub mod http_listener; pub mod service_contract; +pub mod service_contract_aggregate; pub mod service_contract_listener; mod tasks_queue; @@ -23,15 +24,42 @@ use std::collections::BTreeSet; use std::sync::Arc; use traits::{ServerKeyGenerator, DocumentKeyServer, MessageSigner, AdminSessionsServer, KeyServer}; use types::all::{Error, Public, MessageHash, EncryptedMessageSignature, RequestSignature, ServerKeyId, - EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId}; + EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId, Requester}; +/// Available API mask. +#[derive(Debug, Default)] +pub struct ApiMask { + /// Accept server key generation requests. + pub server_key_generation_requests: bool, + /// Accept server key retrieval requests. + pub server_key_retrieval_requests: bool, + /// Accept document key store requests. + pub document_key_store_requests: bool, + /// Accept document key shadow retrieval requests. + pub document_key_shadow_retrieval_requests: bool, +} + +/// Combined HTTP + service contract listener. pub struct Listener { key_server: Arc, _http: Option, _contract: Option>, } +impl ApiMask { + /// Create mask that accepts all requests. + pub fn all() -> Self { + ApiMask { + server_key_generation_requests: true, + server_key_retrieval_requests: true, + document_key_store_requests: true, + document_key_shadow_retrieval_requests: true, + } + } +} + impl Listener { + /// Create new listener. pub fn new(key_server: Arc, http: Option, contract: Option>) -> Self { Self { key_server: key_server, @@ -44,36 +72,36 @@ impl Listener { impl KeyServer for Listener {} impl ServerKeyGenerator for Listener { - fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { - self.key_server.generate_key(key_id, signature, threshold) + fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result { + self.key_server.generate_key(key_id, author, threshold) } } impl DocumentKeyServer for Listener { - fn store_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> { - self.key_server.store_document_key(key_id, signature, common_point, encrypted_document_key) + fn store_document_key(&self, key_id: &ServerKeyId, author: &Requester, common_point: Public, encrypted_document_key: Public) -> Result<(), Error> { + self.key_server.store_document_key(key_id, author, common_point, encrypted_document_key) } - fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result { - self.key_server.generate_document_key(key_id, signature, threshold) + fn generate_document_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result { + self.key_server.generate_document_key(key_id, author, threshold) } - fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { - self.key_server.restore_document_key(key_id, signature) + fn restore_document_key(&self, key_id: &ServerKeyId, requester: &Requester) -> Result { + self.key_server.restore_document_key(key_id, requester) } - fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result { - self.key_server.restore_document_key_shadow(key_id, signature) + fn restore_document_key_shadow(&self, key_id: &ServerKeyId, requester: &Requester) -> Result { + self.key_server.restore_document_key_shadow(key_id, requester) } } impl MessageSigner for Listener { - fn sign_message_schnorr(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result { - self.key_server.sign_message_schnorr(key_id, signature, message) + fn sign_message_schnorr(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result { + self.key_server.sign_message_schnorr(key_id, requester, message) } - fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result { - self.key_server.sign_message_ecdsa(key_id, signature, message) + fn sign_message_ecdsa(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result { + self.key_server.sign_message_ecdsa(key_id, requester, message) } } @@ -81,4 +109,4 @@ impl AdminSessionsServer for Listener { fn change_servers_set(&self, old_set_signature: RequestSignature, new_set_signature: RequestSignature, new_servers_set: BTreeSet) -> Result<(), Error> { self.key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set) } -} \ No newline at end of file +} diff --git a/secret_store/src/listener/service_contract.rs b/secret_store/src/listener/service_contract.rs index f2d13f192..7bb28ae05 100644 --- a/secret_store/src/listener/service_contract.rs +++ b/secret_store/src/listener/service_contract.rs @@ -16,28 +16,51 @@ use std::sync::Arc; use parking_lot::RwLock; +use ethabi::RawLog; use ethcore::filter::Filter; use ethcore::client::{Client, BlockChainClient, BlockId, RegistryInfo, CallContract}; -use ethkey::{Public, Signature, public_to_address}; +use ethkey::{Public, public_to_address}; use hash::keccak; +use bytes::Bytes; use ethereum_types::{H256, U256, Address}; +use listener::ApiMask; use listener::service_contract_listener::ServiceTask; use trusted_client::TrustedClient; use {ServerKeyId, NodeKeyPair, ContractAddress}; use_contract!(service, "Service", "res/service.json"); -/// Name of the SecretStore contract in the registry. -const SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service"; +/// Name of the general SecretStore contract in the registry. +pub const SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service"; +/// Name of the server key generation SecretStore contract in the registry. +pub const SRV_KEY_GEN_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_srv_gen"; +/// Name of the server key retrieval SecretStore contract in the registry. +pub const SRV_KEY_RETR_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_srv_retr"; +/// Name of the document key store SecretStore contract in the registry. +pub const DOC_KEY_STORE_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_doc_store"; +/// Name of the document key retrieval SecretStore contract in the registry. +pub const DOC_KEY_SRETR_SERVICE_CONTRACT_REGISTRY_NAME: &'static str = "secretstore_service_doc_sretr"; -/// Key server has been added to the set. -const SERVER_KEY_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyRequested(bytes32,uint256)"; +/// Server key generation has been requested. +const SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyGenerationRequested(bytes32,address,uint8)"; +/// Server key retrieval has been requested. +const SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME: &'static [u8] = &*b"ServerKeyRetrievalRequested(bytes32)"; +/// Document key store has been requested. +const DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME: &'static [u8] = &*b"DocumentKeyStoreRequested(bytes32,address,bytes,bytes)"; +/// Document key common part retrieval has been requested. +const DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME: &'static [u8] = &*b"DocumentKeyCommonRetrievalRequested(bytes32,address)"; +/// Document key personal part retrieval has been requested. +const DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME: &'static [u8] = &*b"DocumentKeyPersonalRetrievalRequested(bytes32,bytes)"; /// Number of confirmations required before request can be processed. const REQUEST_CONFIRMATIONS_REQUIRED: u64 = 3; lazy_static! { - static ref SERVER_KEY_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_REQUESTED_EVENT_NAME); + pub static ref SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME); + pub static ref SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME_HASH: H256 = keccak(SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME); + pub static ref DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME_HASH: H256 = keccak(DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME); + pub static ref DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH: H256 = keccak(DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME); + pub static ref DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH: H256 = keccak(DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME); } /// Service contract trait. @@ -45,61 +68,82 @@ pub trait ServiceContract: Send + Sync { /// Update contract when new blocks are enacted. Returns true if contract is installed && up-to-date (i.e. chain is synced). fn update(&self) -> bool; /// Read recent contract logs. Returns topics of every entry. - fn read_logs(&self) -> Box>>; + fn read_logs(&self) -> Box>; /// Publish generated key. fn read_pending_requests(&self) -> Box>; - /// Publish server key. - fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String>; + /// Publish generated server key. + fn publish_generated_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String>; + /// Publish server key generation error. + fn publish_server_key_generation_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>; + /// Publish retrieved server key. + fn publish_retrieved_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String>; + /// Publish server key retrieval error. + fn publish_server_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>; + /// Publish stored document key. + fn publish_stored_document_key(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>; + /// Publish document key store error. + fn publish_document_key_store_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String>; + /// Publish retrieved document key common. + fn publish_retrieved_document_key_common(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String>; + /// Publish retrieved document key personal. + fn publish_retrieved_document_key_personal(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String>; + /// Publish document key store error. + fn publish_document_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String>; } /// On-chain service contract. pub struct OnChainServiceContract { + /// Requests mask. + mask: ApiMask, /// Blockchain client. client: TrustedClient, /// This node key pair. self_key_pair: Arc, - /// Contract addresss. + /// Contract registry name (if any). + name: String, + /// Contract address. address: ContractAddress, /// Contract. + contract: service::Service, + /// Contract. data: RwLock, } /// On-chain service contract data. struct ServiceData { - /// Contract. - pub contract: service::Service, - /// Contract address. + /// Actual contract address. pub contract_address: Address, /// Last block we have read logs from. pub last_log_block: Option, } /// Pending requests iterator. -struct PendingRequestsIterator { - /// Blockchain client. - client: Arc, - /// Contract. - contract: service::Service, - /// Contract address. - contract_address: Address, - /// This node key pair. - self_key_pair: Arc, - /// Block, this iterator is created for. - block: H256, +struct PendingRequestsIterator Option<(bool, ServiceTask)>> { + /// Pending request read function. + read_request: F, /// Current request index. index: U256, /// Requests length. length: U256, } +/// Server key generation related functions. +struct ServerKeyGenerationService; +/// Server key retrieval related functions. +struct ServerKeyRetrievalService; +/// Document key store related functions. +struct DocumentKeyStoreService; +/// Document key shadow retrievalrelated functions. +struct DocumentKeyShadowRetrievalService; + impl OnChainServiceContract { /// Create new on-chain service contract. - pub fn new(client: TrustedClient, address: ContractAddress, self_key_pair: Arc) -> Self { + pub fn new(mask: ApiMask, client: TrustedClient, name: String, address: ContractAddress, self_key_pair: Arc) -> Self { let contract_addr = match address { - ContractAddress::Registry => client.get().and_then(|c| c.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), BlockId::Latest) + ContractAddress::Registry => client.get().and_then(|c| c.registry_address(name.clone(), BlockId::Latest) .map(|address| { - trace!(target: "secretstore", "{}: installing service contract from address {}", - self_key_pair.public(), address); + trace!(target: "secretstore", "{}: installing {} service contract from address {}", + self_key_pair.public(), name, address); address })) .unwrap_or_default(), @@ -111,16 +155,81 @@ impl OnChainServiceContract { }; OnChainServiceContract { + mask: mask, client: client, self_key_pair: self_key_pair, + name: name, address: address, + contract: service::Service::default(), data: RwLock::new(ServiceData { - contract: service::Service::default(), contract_address: contract_addr, last_log_block: None, }), } } + + /// Send transaction to the service contract. + fn send_contract_transaction(&self, origin: &Address, server_key_id: &ServerKeyId, is_response_required: C, prepare_tx: P) -> Result<(), String> + where C: FnOnce(&Client, &Address, &service::Service, &ServerKeyId, &Address) -> bool, + P: FnOnce(&Client, &Address, &service::Service) -> Result { + // only publish if contract address is set && client is online + let client = match self.client.get() { + Some(client) => client, + None => return Err("trusted client is required to publish key".into()), + }; + + // only publish key if contract waits for publication + // failing is ok here - it could be that enough confirmations have been recevied + // or key has been requested using HTTP API + let self_address = public_to_address(self.self_key_pair.public()); + if !is_response_required(&*client, origin, &self.contract, server_key_id, &self_address) { + return Ok(()); + } + + // prepare transaction data + let transaction_data = prepare_tx(&*client, origin, &self.contract)?; + + // send transaction + client.transact_contract( + origin.clone(), + transaction_data + ).map_err(|e| format!("{}", e))?; + + Ok(()) + } + + /// Create task-specific pending requests iterator. + fn create_pending_requests_iterator< + C: 'static + Fn(&Client, &Address, &service::Service, &BlockId) -> Result, + R: 'static + Fn(&NodeKeyPair, &Client, &Address, &service::Service, &BlockId, U256) -> Result<(bool, ServiceTask), String> + >(&self, client: Arc, contract_address: &Address, block: &BlockId, get_count: C, read_item: R) -> Box> { + let contract = service::Service::default(); + get_count(&*client, contract_address, &contract, block) + .map(|count| { + let client = client.clone(); + let self_key_pair = self.self_key_pair.clone(); + let contract_address = contract_address.clone(); + let block = block.clone(); + Box::new(PendingRequestsIterator { + read_request: move |index| read_item(&*self_key_pair, &*client, &contract_address, &contract, &block, index) + .map_err(|error| { + warn!(target: "secretstore", "{}: reading pending request failed: {}", + self_key_pair.public(), error); + error + }) + .ok(), + index: 0.into(), + length: count, + }) as Box> + }) + .map_err(|error| { + warn!(target: "secretstore", "{}: creating pending requests iterator failed: {}", + self.self_key_pair.public(), error); + error + }) + .ok() + .unwrap_or_else(|| Box::new(::std::iter::empty())) + } } impl ServiceContract for OnChainServiceContract { @@ -130,10 +239,10 @@ impl ServiceContract for OnChainServiceContract { if let &ContractAddress::Registry = &self.address { if let Some(client) = self.client.get() { // update contract address from registry - let service_contract_addr = client.registry_address(SERVICE_CONTRACT_REGISTRY_NAME.to_owned(), BlockId::Latest).unwrap_or_default(); + let service_contract_addr = client.registry_address(self.name.clone(), BlockId::Latest).unwrap_or_default(); if self.data.read().contract_address != service_contract_addr { - trace!(target: "secretstore", "{}: installing service contract from address {}", - self.self_key_pair.public(), service_contract_addr); + trace!(target: "secretstore", "{}: installing {} service contract from address {}", + self.self_key_pair.public(), self.name, service_contract_addr); self.data.write().contract_address = service_contract_addr; } } @@ -143,7 +252,7 @@ impl ServiceContract for OnChainServiceContract { && self.client.get().is_some() } - fn read_logs(&self) -> Box>> { + fn read_logs(&self) -> Box> { let client = match self.client.get() { Some(client) => client, None => { @@ -181,16 +290,33 @@ impl ServiceContract for OnChainServiceContract { from_block: BlockId::Hash(first_block), to_block: BlockId::Hash(last_block), address: Some(vec![address]), - topics: vec![ - Some(vec![*SERVER_KEY_REQUESTED_EVENT_NAME_HASH]), - None, - None, - None, - ], + topics: vec![Some(mask_topics(&self.mask))], limit: None, }); - Box::new(request_logs.into_iter().map(|log| log.entry.topics)) + Box::new(request_logs.into_iter() + .filter_map(|log| { + let raw_log: RawLog = (log.entry.topics.into_iter().map(|t| t.0.into()).collect(), log.entry.data).into(); + if raw_log.topics[0] == *SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME_HASH { + ServerKeyGenerationService::parse_log(&address, &self.contract, raw_log) + } else if raw_log.topics[0] == *SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME_HASH { + ServerKeyRetrievalService::parse_log(&address, &self.contract, raw_log) + } else if raw_log.topics[0] == *DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME_HASH { + DocumentKeyStoreService::parse_log(&address, &self.contract, raw_log) + } else if raw_log.topics[0] == *DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH { + DocumentKeyShadowRetrievalService::parse_common_request_log(&address, &self.contract, raw_log) + } else if raw_log.topics[0] == *DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH { + DocumentKeyShadowRetrievalService::parse_personal_request_log(&address, &self.contract, raw_log) + } else { + Err("unknown type of log entry".into()) + } + .map_err(|error| { + warn!(target: "secretstore", "{}: error parsing log entry from service contract: {}", + self.self_key_pair.public(), error); + error + }) + .ok() + }).collect::>().into_iter()) } fn read_pending_requests(&self) -> Box> { @@ -205,81 +331,102 @@ impl ServiceContract for OnChainServiceContract { match data.contract_address == Default::default() { true => Box::new(::std::iter::empty()), false => get_confirmed_block_hash(&*client, REQUEST_CONFIRMATIONS_REQUIRED + 1) - .and_then(|b| { - let contract_address = data.contract_address; - let do_call = |data| client.call_contract(BlockId::Hash(b), contract_address, data); - data.contract.functions().server_key_generation_requests_count().call(&do_call) - .map_err(|error| { - warn!(target: "secretstore", "{}: call to server_key_generation_requests_count failed: {}", - self.self_key_pair.public(), error); - error - }) - .map(|l| (b, l)) - .ok() + .map(|b| { + let block = BlockId::Hash(b); + let iter = match self.mask.server_key_generation_requests { + true => Box::new(self.create_pending_requests_iterator(client.clone(), &data.contract_address, &block, + &ServerKeyGenerationService::read_pending_requests_count, + &ServerKeyGenerationService::read_pending_request)) as Box>, + false => Box::new(::std::iter::empty()), + }; + let iter = match self.mask.server_key_retrieval_requests { + true => Box::new(iter.chain(self.create_pending_requests_iterator(client.clone(), &data.contract_address, &block, + &ServerKeyRetrievalService::read_pending_requests_count, + &ServerKeyRetrievalService::read_pending_request))), + false => iter, + }; + let iter = match self.mask.document_key_store_requests { + true => Box::new(iter.chain(self.create_pending_requests_iterator(client.clone(), &data.contract_address, &block, + &DocumentKeyStoreService::read_pending_requests_count, + &DocumentKeyStoreService::read_pending_request))), + false => iter, + }; + let iter = match self.mask.document_key_shadow_retrieval_requests { + true => Box::new(iter.chain(self.create_pending_requests_iterator(client, &data.contract_address, &block, + &DocumentKeyShadowRetrievalService::read_pending_requests_count, + &DocumentKeyShadowRetrievalService::read_pending_request))), + false => iter + }; + + iter }) - .map(|(b, l)| Box::new(PendingRequestsIterator { - client: client, - contract: service::Service::default(), - contract_address: data.contract_address, - self_key_pair: self.self_key_pair.clone(), - block: b, - index: 0.into(), - length: l, - }) as Box>) .unwrap_or_else(|| Box::new(::std::iter::empty())) } } - fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> { - // only publish if contract address is set && client is online - let data = self.data.read(); - if data.contract_address == Default::default() { - // it is not an error, because key could be generated even without contract - return Ok(()); - } + fn publish_generated_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String> { + self.send_contract_transaction(origin, server_key_id, ServerKeyGenerationService::is_response_required, |_, _, service| + Ok(ServerKeyGenerationService::prepare_pubish_tx_data(service, server_key_id, &server_key)) + ) + } - let client = match self.client.get() { - Some(client) => client, - None => return Err("trusted client is required to publish key".into()), - }; + fn publish_server_key_generation_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.send_contract_transaction(origin, server_key_id, ServerKeyGenerationService::is_response_required, |_, _, service| + Ok(ServerKeyGenerationService::prepare_error_tx_data(service, server_key_id)) + ) + } - // only publish key if contract waits for publication - // failing is ok here - it could be that enough confirmations have been recevied - // or key has been requested using HTTP API - let contract_address = data.contract_address; - let do_call = |data| client.call_contract(BlockId::Latest, contract_address, data); - let self_address = public_to_address(self.self_key_pair.public()); - if data.contract.functions() - .get_server_key_confirmation_status() - .call(*server_key_id, self_address, &do_call) - .unwrap_or(false) { - return Ok(()); - } + fn publish_retrieved_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String> { + let threshold = serialize_threshold(threshold)?; + self.send_contract_transaction(origin, server_key_id, ServerKeyRetrievalService::is_response_required, |_, _, service| + Ok(ServerKeyRetrievalService::prepare_pubish_tx_data(service, server_key_id, server_key, threshold)) + ) + } - // prepare transaction data - let server_key_hash = keccak(server_key); - let signed_server_key = self.self_key_pair.sign(&server_key_hash).map_err(|e| format!("{}", e))?; - let signed_server_key: Signature = signed_server_key.into_electrum().into(); - let transaction_data = data.contract.functions() - .server_key_generated() - .input(*server_key_id, - server_key.to_vec(), - signed_server_key.v(), - signed_server_key.r(), - signed_server_key.s(), - ); + fn publish_server_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.send_contract_transaction(origin, server_key_id, ServerKeyRetrievalService::is_response_required, |_, _, service| + Ok(ServerKeyRetrievalService::prepare_error_tx_data(service, server_key_id)) + ) + } - // send transaction - client.transact_contract( - data.contract_address, - transaction_data - ).map_err(|e| format!("{}", e))?; + fn publish_stored_document_key(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.send_contract_transaction(origin, server_key_id, DocumentKeyStoreService::is_response_required, |_, _, service| + Ok(DocumentKeyStoreService::prepare_pubish_tx_data(service, server_key_id)) + ) + } - Ok(()) + fn publish_document_key_store_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.send_contract_transaction(origin, server_key_id, DocumentKeyStoreService::is_response_required, |_, _, service| + Ok(DocumentKeyStoreService::prepare_error_tx_data(service, server_key_id)) + ) + } + + fn publish_retrieved_document_key_common(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String> { + let threshold = serialize_threshold(threshold)?; + self.send_contract_transaction(origin, server_key_id, |client, contract_address, contract, server_key_id, key_server| + DocumentKeyShadowRetrievalService::is_response_required(client, contract_address, contract, server_key_id, requester, key_server), + |_, _, service| + Ok(DocumentKeyShadowRetrievalService::prepare_pubish_common_tx_data(service, server_key_id, requester, common_point, threshold)) + ) + } + + fn publish_retrieved_document_key_personal(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String> { + self.send_contract_transaction(origin, server_key_id, |_, _, _, _, _| true, + move |client, address, service| + DocumentKeyShadowRetrievalService::prepare_pubish_personal_tx_data(client, address, service, server_key_id, requester, participants, decrypted_secret, shadow) + ) + } + + fn publish_document_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> { + self.send_contract_transaction(origin, server_key_id, |client, contract_address, contract, server_key_id, key_server| + DocumentKeyShadowRetrievalService::is_response_required(client, contract_address, contract, server_key_id, requester, key_server), + |_, _, service| + Ok(DocumentKeyShadowRetrievalService::prepare_error_tx_data(service, server_key_id, requester)) + ) } } -impl Iterator for PendingRequestsIterator { +impl Iterator for PendingRequestsIterator where F: Fn(U256) -> Option<(bool, ServiceTask)> { type Item = (bool, ServiceTask); fn next(&mut self) -> Option<(bool, ServiceTask)> { @@ -290,27 +437,29 @@ impl Iterator for PendingRequestsIterator { let index = self.index.clone(); self.index = self.index + 1.into(); - let self_address = public_to_address(self.self_key_pair.public()); - let contract_address = self.contract_address; - let do_call = |data| self.client.call_contract(BlockId::Hash(self.block.clone()), contract_address, data); - self.contract.functions().get_server_key_id().call(index, &do_call) - .and_then(|server_key_id| - self.contract.functions().get_server_key_threshold().call(server_key_id, &do_call) - .map(|threshold| (server_key_id, threshold))) - .and_then(|(server_key_id, threshold)| - self.contract.functions().get_server_key_confirmation_status().call(server_key_id, self_address, &do_call) - .map(|is_confirmed| (server_key_id, threshold, is_confirmed))) - .map(|(server_key_id, threshold, is_confirmed)| - Some((is_confirmed, ServiceTask::GenerateServerKey(server_key_id, threshold.into())))) - .map_err(|error| { - warn!(target: "secretstore", "{}: reading service contract request failed: {}", - self.self_key_pair.public(), error); - () - }) - .unwrap_or(None) + (self.read_request)(index) } } +/// Returns vector of logs topics to listen to. +pub fn mask_topics(mask: &ApiMask) -> Vec { + let mut topics = Vec::new(); + if mask.server_key_generation_requests { + topics.push(*SERVER_KEY_GENERATION_REQUESTED_EVENT_NAME_HASH); + } + if mask.server_key_retrieval_requests { + topics.push(*SERVER_KEY_RETRIEVAL_REQUESTED_EVENT_NAME_HASH); + } + if mask.document_key_store_requests { + topics.push(*DOCUMENT_KEY_STORE_REQUESTED_EVENT_NAME_HASH); + } + if mask.document_key_shadow_retrieval_requests { + topics.push(*DOCUMENT_KEY_COMMON_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH); + topics.push(*DOCUMENT_KEY_PERSONAL_PART_RETRIEVAL_REQUESTED_EVENT_NAME_HASH); + } + topics +} + /// Get hash of the last block with at least n confirmations. fn get_confirmed_block_hash(client: &Client, confirmations: u64) -> Option { client.block_number(BlockId::Latest) @@ -318,21 +467,365 @@ fn get_confirmed_block_hash(client: &Client, confirmations: u64) -> Option .and_then(|b| client.block_hash(BlockId::Number(b))) } +impl ServerKeyGenerationService { + /// Parse request log entry. + pub fn parse_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result { + let event = contract.events().server_key_generation_requested(); + match event.parse_log(raw_log) { + Ok(l) => Ok(ServiceTask::GenerateServerKey(origin.clone(), l.server_key_id, l.author, parse_threshold(l.threshold)?)), + Err(e) => Err(format!("{}", e)), + } + } + + /// Check if response from key server is required. + pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, key_server: &Address) -> bool { + // we're checking confirmation in Latest block, because we're interested in latest contract state here + let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data); + contract.functions() + .is_server_key_generation_response_required() + .call(*server_key_id, key_server.clone(), &do_call) + .unwrap_or(true) + } + + /// Prepare publish key transaction data. + pub fn prepare_pubish_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, server_key_public: &Public) -> Bytes { + contract.functions() + .server_key_generated() + .input(*server_key_id, server_key_public.to_vec()) + } + + /// Prepare error transaction data. + pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes { + contract.functions() + .server_key_generation_error() + .input(*server_key_id) + } + + /// Read pending requests count. + fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result { + let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data); + let contract = service::Service::default(); + contract.functions() + .server_key_generation_requests_count() + .call(&do_call) + .map_err(|error| format!("{}", error)) + } + + /// Read pending request. + fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> { + let self_address = public_to_address(self_key_pair.public()); + let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d); + contract.functions() + .get_server_key_generation_request() + .call(index, &do_call) + .map_err(|error| format!("{}", error)) + .and_then(|(server_key_id, author, threshold)| parse_threshold(threshold) + .map(|threshold| (server_key_id, author, threshold))) + .and_then(|(server_key_id, author, threshold)| contract.functions() + .is_server_key_generation_response_required() + .call(server_key_id.clone(), self_address, &do_call) + .map(|not_confirmed| ( + not_confirmed, + ServiceTask::GenerateServerKey( + contract_address.clone(), + server_key_id, + author, + threshold, + ))) + .map_err(|error| format!("{}", error))) + } +} + +impl ServerKeyRetrievalService { + /// Parse request log entry. + pub fn parse_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result { + let event = contract.events().server_key_retrieval_requested(); + match event.parse_log(raw_log) { + Ok(l) => Ok(ServiceTask::RetrieveServerKey(origin.clone(), l.server_key_id)), + Err(e) => Err(format!("{}", e)), + } + } + + /// Check if response from key server is required. + pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, key_server: &Address) -> bool { + // we're checking confirmation in Latest block, because we're interested in latest contract state here + let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data); + contract.functions() + .is_server_key_retrieval_response_required() + .call(*server_key_id, key_server.clone(), &do_call) + .unwrap_or(true) + } + + /// Prepare publish key transaction data. + pub fn prepare_pubish_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, server_key_public: Public, threshold: U256) -> Bytes { + contract.functions() + .server_key_retrieved() + .input(*server_key_id, server_key_public.to_vec(), threshold) + } + + /// Prepare error transaction data. + pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes { + contract.functions() + .server_key_retrieval_error() + .input(*server_key_id) + } + + /// Read pending requests count. + fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result { + let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data); + let contract = service::Service::default(); + contract.functions() + .server_key_retrieval_requests_count() + .call(&do_call) + .map_err(|error| format!("{}", error)) + } + + /// Read pending request. + fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> { + let self_address = public_to_address(self_key_pair.public()); + let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d); + contract.functions() + .get_server_key_retrieval_request() + .call(index, &do_call) + .map_err(|error| format!("{}", error)) + .and_then(|server_key_id| contract.functions() + .is_server_key_retrieval_response_required() + .call(server_key_id.clone(), self_address, &do_call) + .map(|not_confirmed| ( + not_confirmed, + ServiceTask::RetrieveServerKey( + contract_address.clone(), + server_key_id, + ))) + .map_err(|error| format!("{}", error))) + } +} + +impl DocumentKeyStoreService { + /// Parse request log entry. + pub fn parse_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result { + let event = contract.events().document_key_store_requested(); + match event.parse_log(raw_log) { + Ok(l) => Ok(ServiceTask::StoreDocumentKey(origin.clone(), l.server_key_id, l.author, (*l.common_point).into(), (*l.encrypted_point).into())), + Err(e) => Err(format!("{}", e)), + } + } + + /// Check if response from key server is required. + pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, key_server: &Address) -> bool { + // we're checking confirmation in Latest block, because we're interested in latest contract state here + let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data); + contract.functions() + .is_document_key_store_response_required() + .call(*server_key_id, key_server.clone(), &do_call) + .unwrap_or(true) + } + + /// Prepare publish key transaction data. + pub fn prepare_pubish_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes { + contract.functions() + .document_key_stored() + .input(*server_key_id) + } + + /// Prepare error transaction data. + pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId) -> Bytes { + contract.functions() + .document_key_store_error() + .input(*server_key_id) + } + + /// Read pending requests count. + fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result { + let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data); + let contract = service::Service::default(); + contract.functions() + .document_key_store_requests_count() + .call(&do_call) + .map_err(|error| format!("{}", error)) + } + + /// Read pending request. + fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> { + let self_address = public_to_address(self_key_pair.public()); + let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d); + contract.functions() + .get_document_key_store_request() + .call(index, &do_call) + .map_err(|error| format!("{}", error)) + .and_then(|(server_key_id, author, common_point, encrypted_point)| contract.functions() + .is_document_key_store_response_required() + .call(server_key_id.clone(), self_address, &do_call) + .map(|not_confirmed| ( + not_confirmed, + ServiceTask::StoreDocumentKey( + contract_address.clone(), + server_key_id, + author, + Public::from_slice(&common_point), + Public::from_slice(&encrypted_point), + ))) + .map_err(|error| format!("{}", error))) + } +} + +impl DocumentKeyShadowRetrievalService { + /// Parse common request log entry. + pub fn parse_common_request_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result { + let event = contract.events().document_key_common_retrieval_requested(); + match event.parse_log(raw_log) { + Ok(l) => Ok(ServiceTask::RetrieveShadowDocumentKeyCommon(origin.clone(), l.server_key_id, l.requester)), + Err(e) => Err(format!("{}", e)), + } + } + + /// Parse personal request log entry. + pub fn parse_personal_request_log(origin: &Address, contract: &service::Service, raw_log: RawLog) -> Result { + let event = contract.events().document_key_personal_retrieval_requested(); + match event.parse_log(raw_log) { + Ok(l) => Ok(ServiceTask::RetrieveShadowDocumentKeyPersonal(origin.clone(), l.server_key_id, (*l.requester_public).into())), + Err(e) => Err(format!("{}", e)), + } + } + + /// Check if response from key server is required. + pub fn is_response_required(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address, key_server: &Address) -> bool { + // we're checking confirmation in Latest block, because we're interested in latest contract state here + let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data); + contract.functions() + .is_document_key_shadow_retrieval_response_required() + .call(*server_key_id, *requester, key_server.clone(), &do_call) + .unwrap_or(true) + } + + /// Prepare publish common key transaction data. + pub fn prepare_pubish_common_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: U256) -> Bytes { + contract.functions() + .document_key_common_retrieved() + .input(*server_key_id, *requester, common_point.to_vec(), threshold) + } + + /// Prepare publish personal key transaction data. + pub fn prepare_pubish_personal_tx_data(client: &Client, contract_address: &Address, contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result { + let mut participants_mask = U256::default(); + for participant in participants { + let participant_index = Self::map_key_server_address(client, contract_address, contract, participant.clone()) + .map_err(|e| format!("Error searching for {} participant: {}", participant, e))?; + participants_mask = participants_mask | (U256::one() << participant_index.into()); + } + Ok(contract.functions() + .document_key_personal_retrieved() + .input(*server_key_id, *requester, participants_mask, decrypted_secret.to_vec(), shadow)) + } + + /// Prepare error transaction data. + pub fn prepare_error_tx_data(contract: &service::Service, server_key_id: &ServerKeyId, requester: &Address) -> Bytes { + contract.functions() + .document_key_shadow_retrieval_error() + .input(*server_key_id, *requester) + } + + /// Read pending requests count. + fn read_pending_requests_count(client: &Client, contract_address: &Address, _contract: &service::Service, block: &BlockId) -> Result { + let do_call = |data| client.call_contract(block.clone(), contract_address.clone(), data); + let contract = service::Service::default(); + contract.functions() + .document_key_shadow_retrieval_requests_count() + .call(&do_call) + .map_err(|error| format!("{}", error)) + } + + /// Read pending request. + fn read_pending_request(self_key_pair: &NodeKeyPair, client: &Client, contract_address: &Address, contract: &service::Service, block: &BlockId, index: U256) -> Result<(bool, ServiceTask), String> { + let self_address = public_to_address(self_key_pair.public()); + let do_call = |d| client.call_contract(block.clone(), contract_address.clone(), d); + contract.functions() + .get_document_key_shadow_retrieval_request() + .call(index, &do_call) + .map_err(|error| format!("{}", error)) + .and_then(|(server_key_id, requester, is_common_retrieval_completed)| { + let requester = Public::from_slice(&requester); + contract.functions() + .is_document_key_shadow_retrieval_response_required() + .call(server_key_id.clone(), public_to_address(&requester), self_address, &do_call) + .map(|not_confirmed| ( + not_confirmed, + match is_common_retrieval_completed { + true => ServiceTask::RetrieveShadowDocumentKeyCommon( + contract_address.clone(), + server_key_id, + public_to_address(&requester), + ), + false => ServiceTask::RetrieveShadowDocumentKeyPersonal( + contract_address.clone(), + server_key_id, + requester, + ) + }, + )) + .map_err(|error| format!("{}", error)) + }) + } + + /// Map from key server address to key server index. + fn map_key_server_address(client: &Client, contract_address: &Address, contract: &service::Service, key_server: Address) -> Result { + // we're checking confirmation in Latest block, because tx ,ust be appended to the latest state + let do_call = |data| client.call_contract(BlockId::Latest, *contract_address, data); + contract.functions() + .require_key_server() + .call(key_server, &do_call) + .map_err(|e| format!("{}", e)) + .and_then(|index| if index > ::std::u8::MAX.into() { + Err(format!("key server index is too big: {}", index)) + } else { + let index: u32 = index.into(); + Ok(index as u8) + }) + } +} + +/// Parse threshold (we only supposrt 256 KS at max). +fn parse_threshold(threshold: U256) -> Result { + let threshold_num = threshold.low_u64(); + if threshold != threshold_num.into() || threshold_num >= ::std::u8::MAX as u64 { + return Err(format!("invalid threshold to use in service contract: {}", threshold)); + } + + Ok(threshold_num as usize) +} + +/// Serialize threshold (we only support 256 KS at max). +fn serialize_threshold(threshold: usize) -> Result { + if threshold > ::std::u8::MAX as usize { + return Err(format!("invalid threshold to use in service contract: {}", threshold)); + } + Ok(threshold.into()) +} + #[cfg(test)] pub mod tests { use parking_lot::Mutex; + use bytes::Bytes; use ethkey::Public; - use ethereum_types::H256; + use ethereum_types::Address; use listener::service_contract_listener::ServiceTask; - use ServerKeyId; + use {ServerKeyId}; use super::ServiceContract; #[derive(Default)] pub struct DummyServiceContract { pub is_actual: bool, - pub logs: Vec>, + pub logs: Vec, pub pending_requests: Vec<(bool, ServiceTask)>, - pub published_keys: Mutex>, + pub generated_server_keys: Mutex>, + pub server_keys_generation_failures: Mutex>, + pub retrieved_server_keys: Mutex>, + pub server_keys_retrieval_failures: Mutex>, + pub stored_document_keys: Mutex>, + pub document_keys_store_failures: Mutex>, + pub common_shadow_retrieved_document_keys: Mutex>, + pub personal_shadow_retrieved_document_keys: Mutex, Public, Bytes)>>, + pub document_keys_shadow_retrieval_failures: Mutex>, } impl ServiceContract for DummyServiceContract { @@ -340,7 +833,7 @@ pub mod tests { true } - fn read_logs(&self) -> Box>> { + fn read_logs(&self) -> Box> { Box::new(self.logs.clone().into_iter()) } @@ -348,8 +841,48 @@ pub mod tests { Box::new(self.pending_requests.clone().into_iter()) } - fn publish_server_key(&self, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> { - self.published_keys.lock().push((server_key_id.clone(), server_key.clone())); + fn publish_generated_server_key(&self, _origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String> { + self.generated_server_keys.lock().push((server_key_id.clone(), server_key.clone())); + Ok(()) + } + + fn publish_server_key_generation_error(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.server_keys_generation_failures.lock().push(server_key_id.clone()); + Ok(()) + } + + fn publish_retrieved_server_key(&self, _origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String> { + self.retrieved_server_keys.lock().push((server_key_id.clone(), server_key.clone(), threshold)); + Ok(()) + } + + fn publish_server_key_retrieval_error(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.server_keys_retrieval_failures.lock().push(server_key_id.clone()); + Ok(()) + } + + fn publish_stored_document_key(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.stored_document_keys.lock().push(server_key_id.clone()); + Ok(()) + } + + fn publish_document_key_store_error(&self, _origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.document_keys_store_failures.lock().push(server_key_id.clone()); + Ok(()) + } + + fn publish_retrieved_document_key_common(&self, _origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String> { + self.common_shadow_retrieved_document_keys.lock().push((server_key_id.clone(), requester.clone(), common_point.clone(), threshold)); + Ok(()) + } + + fn publish_retrieved_document_key_personal(&self, _origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String> { + self.personal_shadow_retrieved_document_keys.lock().push((server_key_id.clone(), requester.clone(), participants.iter().cloned().collect(), decrypted_secret, shadow)); + Ok(()) + } + + fn publish_document_key_retrieval_error(&self, _origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> { + self.document_keys_shadow_retrieval_failures.lock().push((server_key_id.clone(), requester.clone())); Ok(()) } } diff --git a/secret_store/src/listener/service_contract_aggregate.rs b/secret_store/src/listener/service_contract_aggregate.rs new file mode 100644 index 000000000..9ec467fea --- /dev/null +++ b/secret_store/src/listener/service_contract_aggregate.rs @@ -0,0 +1,100 @@ +// 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 bytes::Bytes; +use ethereum_types::Address; +use ethkey::Public; +use listener::service_contract::ServiceContract; +use listener::service_contract_listener::ServiceTask; +use {ServerKeyId}; + +/// Aggregated on-chain service contract. +pub struct OnChainServiceContractAggregate { + /// All hosted service contracts. + contracts: Vec>, +} + +impl OnChainServiceContractAggregate { + /// Create new aggregated service contract listener. + pub fn new(contracts: Vec>) -> Self { + debug_assert!(contracts.len() > 1); + OnChainServiceContractAggregate { + contracts: contracts, + } + } +} + +impl ServiceContract for OnChainServiceContractAggregate { + fn update(&self) -> bool { + let mut result = false; + for contract in &self.contracts { + result = contract.update() || result; + } + result + } + + fn read_logs(&self) -> Box> { + self.contracts.iter() + .fold(Box::new(::std::iter::empty()) as Box>, |i, c| + Box::new(i.chain(c.read_logs()))) + } + + fn read_pending_requests(&self) -> Box> { + self.contracts.iter() + .fold(Box::new(::std::iter::empty()) as Box>, |i, c| + Box::new(i.chain(c.read_pending_requests()))) + } + + // in current implementation all publish methods are independent of actual contract adddress + // (tx is sent to origin) => we do not care which contract to use for publish data in methods below + + fn publish_generated_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public) -> Result<(), String> { + self.contracts[0].publish_generated_server_key(origin, server_key_id, server_key) + } + + fn publish_server_key_generation_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.contracts[0].publish_server_key_generation_error(origin, server_key_id) + } + + fn publish_retrieved_server_key(&self, origin: &Address, server_key_id: &ServerKeyId, server_key: Public, threshold: usize) -> Result<(), String> { + self.contracts[0].publish_retrieved_server_key(origin, server_key_id, server_key, threshold) + } + + fn publish_server_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.contracts[0].publish_server_key_retrieval_error(origin, server_key_id) + } + + fn publish_stored_document_key(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.contracts[0].publish_stored_document_key(origin, server_key_id) + } + + fn publish_document_key_store_error(&self, origin: &Address, server_key_id: &ServerKeyId) -> Result<(), String> { + self.contracts[0].publish_document_key_store_error(origin, server_key_id) + } + + fn publish_retrieved_document_key_common(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, common_point: Public, threshold: usize) -> Result<(), String> { + self.contracts[0].publish_retrieved_document_key_common(origin, server_key_id, requester, common_point, threshold) + } + + fn publish_retrieved_document_key_personal(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address, participants: &[Address], decrypted_secret: Public, shadow: Bytes) -> Result<(), String> { + self.contracts[0].publish_retrieved_document_key_personal(origin, server_key_id, requester, participants, decrypted_secret, shadow) + } + + fn publish_document_key_retrieval_error(&self, origin: &Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> { + self.contracts[0].publish_document_key_retrieval_error(origin, server_key_id, requester) + } +} diff --git a/secret_store/src/listener/service_contract_listener.rs b/secret_store/src/listener/service_contract_listener.rs index 0d04a7dae..8776dc218 100644 --- a/secret_store/src/listener/service_contract_listener.rs +++ b/secret_store/src/listener/service_contract_listener.rs @@ -20,16 +20,20 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use parking_lot::Mutex; use ethcore::client::ChainNotify; -use ethkey::{Random, Generator, Public, sign}; +use ethkey::{Public, public_to_address}; use bytes::Bytes; -use ethereum_types::{H256, U256}; +use ethereum_types::{H256, U256, Address}; use key_server_set::KeyServerSet; use key_server_cluster::{ClusterClient, ClusterSessionsListener, ClusterSession}; +use key_server_cluster::math; use key_server_cluster::generation_session::SessionImpl as GenerationSession; +use key_server_cluster::encryption_session::{check_encrypted_data, update_encrypted_data}; +use key_server_cluster::decryption_session::SessionImpl as DecryptionSession; use key_storage::KeyStorage; +use acl_storage::AclStorage; use listener::service_contract::ServiceContract; use listener::tasks_queue::TasksQueue; -use {ServerKeyId, NodeKeyPair, KeyServer}; +use {ServerKeyId, NodeKeyPair, Error}; /// Retry interval (in blocks). Every RETRY_INTERVAL_BLOCKS blocks each KeyServer reads pending requests from /// service contract && tries to re-execute. The reason to have this mechanism is primarily because keys @@ -56,12 +60,12 @@ pub struct ServiceContractListener { pub struct ServiceContractListenerParams { /// Service contract. pub contract: Arc, - /// Key server reference. - pub key_server: Arc, /// This node key pair. pub self_key_pair: Arc, /// Key servers set. pub key_server_set: Arc, + /// ACL storage reference. + pub acl_storage: Arc, /// Cluster reference. pub cluster: Arc, /// Key storage reference. @@ -78,8 +82,10 @@ struct ServiceContractListenerData { pub tasks_queue: Arc>, /// Service contract. pub contract: Arc, - /// Key server reference. - pub key_server: Arc, + /// ACL storage reference. + pub acl_storage: Arc, + /// Cluster client reference. + pub cluster: Arc, /// This node key pair. pub self_key_pair: Arc, /// Key servers set. @@ -92,8 +98,10 @@ struct ServiceContractListenerData { /// Retry-related data. #[derive(Default)] struct ServiceContractRetryData { - /// Server keys, which we have generated (or tried to generate) since last retry moment. - pub generated_keys: HashSet, + /// Server keys, which we have 'touched' since last retry. + pub affected_server_keys: HashSet, + /// Document keys + requesters, which we have 'touched' since last retry. + pub affected_document_keys: HashSet<(ServerKeyId, Address)>, } /// Service task. @@ -101,23 +109,30 @@ struct ServiceContractRetryData { pub enum ServiceTask { /// Retry all 'stalled' tasks. Retry, - /// Generate server key (server_key_id, threshold). - GenerateServerKey(H256, H256), - /// Confirm server key (server_key_id). - RestoreServerKey(H256), + /// Generate server key (origin, server_key_id, author, threshold). + GenerateServerKey(Address, ServerKeyId, Address, usize), + /// Retrieve server key (origin, server_key_id). + RetrieveServerKey(Address, ServerKeyId), + /// Store document key (origin, server_key_id, author, common_point, encrypted_point). + StoreDocumentKey(Address, ServerKeyId, Address, Public, Public), + /// Retrieve common data of document key (origin, server_key_id, requester). + RetrieveShadowDocumentKeyCommon(Address, ServerKeyId, Address), + /// Retrieve personal data of document key (origin, server_key_id, requester). + RetrieveShadowDocumentKeyPersonal(Address, ServerKeyId, Public), /// Shutdown listener. Shutdown, } impl ServiceContractListener { /// Create new service contract listener. - pub fn new(params: ServiceContractListenerParams) -> Arc { + pub fn new(params: ServiceContractListenerParams) -> Result, Error> { let data = Arc::new(ServiceContractListenerData { last_retry: AtomicUsize::new(0), retry_data: Default::default(), tasks_queue: Arc::new(TasksQueue::new()), contract: params.contract, - key_server: params.key_server, + acl_storage: params.acl_storage, + cluster: params.cluster, self_key_pair: params.self_key_pair, key_server_set: params.key_server_set, key_storage: params.key_storage, @@ -129,39 +144,55 @@ impl ServiceContractListener { None } else { let service_thread_data = data.clone(); - Some(thread::spawn(move || Self::run_service_thread(service_thread_data))) + Some(thread::Builder::new().name("ServiceContractListener".into()).spawn(move || + Self::run_service_thread(service_thread_data)).map_err(|e| Error::Internal(format!("{}", e)))?) }; let contract = Arc::new(ServiceContractListener { data: data, service_handle: service_handle, }); - params.cluster.add_generation_listener(contract.clone()); - contract + contract.data.cluster.add_generation_listener(contract.clone()); + contract.data.cluster.add_decryption_listener(contract.clone()); + Ok(contract) } /// Process incoming events of service contract. fn process_service_contract_events(&self) { self.data.tasks_queue.push_many(self.data.contract.read_logs() - .filter_map(|topics| match topics.len() { - // when key is already generated && we have this key - 3 if self.data.key_storage.get(&topics[1]).map(|k| k.is_some()).unwrap_or_default() => { - Some(ServiceTask::RestoreServerKey( - topics[1], - )) - } - // when key is not yet generated && this node should be master of this key generation session - 3 if is_processed_by_this_key_server(&*self.data.key_server_set, &*self.data.self_key_pair, &topics[1]) => { - Some(ServiceTask::GenerateServerKey( - topics[1], - topics[2], - )) - }, - 3 => None, - l @ _ => { - warn!(target: "secretstore", "Ignoring ServerKeyRequested event with wrong number of params {}", l); - None - }, - })); + .filter_map(|task| Self::filter_task(&self.data, task))); + } + + /// Filter service task. Only returns Some if task must be executed by this server. + fn filter_task(data: &Arc, task: ServiceTask) -> Option { + match task { + // when this node should be master of this server key generation session + ServiceTask::GenerateServerKey(origin, server_key_id, author, threshold) if is_processed_by_this_key_server( + &*data.key_server_set, &*data.self_key_pair, &server_key_id) => + Some(ServiceTask::GenerateServerKey(origin, server_key_id, author, threshold)), + // when server key is not yet generated and generation must be initiated by other node + ServiceTask::GenerateServerKey(_, _, _, _) => None, + + // when server key retrieval is requested + ServiceTask::RetrieveServerKey(origin, server_key_id) => + Some(ServiceTask::RetrieveServerKey(origin, server_key_id)), + + // when document key store is requested + ServiceTask::StoreDocumentKey(origin, server_key_id, author, common_point, encrypted_point) => + Some(ServiceTask::StoreDocumentKey(origin, server_key_id, author, common_point, encrypted_point)), + + // when common document key data retrieval is requested + ServiceTask::RetrieveShadowDocumentKeyCommon(origin, server_key_id, requester) => + Some(ServiceTask::RetrieveShadowDocumentKeyCommon(origin, server_key_id, requester)), + + // when this node should be master of this document key decryption session + ServiceTask::RetrieveShadowDocumentKeyPersonal(origin, server_key_id, requester) if is_processed_by_this_key_server( + &*data.key_server_set, &*data.self_key_pair, &server_key_id) => + Some(ServiceTask::RetrieveShadowDocumentKeyPersonal(origin, server_key_id, requester)), + // when server key is not yet generated and generation must be initiated by other node + ServiceTask::RetrieveShadowDocumentKeyPersonal(_, _, _) => None, + + ServiceTask::Retry | ServiceTask::Shutdown => unreachable!("must be filtered outside"), + } } /// Service thread procedure. @@ -172,18 +203,45 @@ impl ServiceContractListener { match task { ServiceTask::Shutdown => break, - task @ _ => { - // the only possible reaction to an error is a trace && it is already happened + task => { + // the only possible reaction to an error is a tx+trace && it is already happened let _ = Self::process_service_task(&data, task); }, }; } + + trace!(target: "secretstore_net", "{}: ServiceContractListener thread stopped", data.self_key_pair.public()); } /// Process single service task. fn process_service_task(data: &Arc, task: ServiceTask) -> Result<(), String> { - match task { - ServiceTask::Retry => + match &task { + &ServiceTask::GenerateServerKey(origin, server_key_id, author, threshold) => { + data.retry_data.lock().affected_server_keys.insert(server_key_id.clone()); + log_service_task_result(&task, data.self_key_pair.public(), + Self::generate_server_key(&data, origin, &server_key_id, author, threshold)) + }, + &ServiceTask::RetrieveServerKey(origin, server_key_id) => { + data.retry_data.lock().affected_server_keys.insert(server_key_id.clone()); + log_service_task_result(&task, data.self_key_pair.public(), + Self::retrieve_server_key(&data, origin, &server_key_id)) + }, + &ServiceTask::StoreDocumentKey(origin, server_key_id, author, common_point, encrypted_point) => { + data.retry_data.lock().affected_document_keys.insert((server_key_id.clone(), author.clone())); + log_service_task_result(&task, data.self_key_pair.public(), + Self::store_document_key(&data, origin, &server_key_id, &author, &common_point, &encrypted_point)) + }, + &ServiceTask::RetrieveShadowDocumentKeyCommon(origin, server_key_id, requester) => { + data.retry_data.lock().affected_document_keys.insert((server_key_id.clone(), requester.clone())); + log_service_task_result(&task, data.self_key_pair.public(), + Self::retrieve_document_key_common(&data, origin, &server_key_id, &requester)) + }, + &ServiceTask::RetrieveShadowDocumentKeyPersonal(origin, server_key_id, requester) => { + data.retry_data.lock().affected_server_keys.insert(server_key_id.clone()); + log_service_task_result(&task, data.self_key_pair.public(), + Self::retrieve_document_key_personal(&data, origin, &server_key_id, requester)) + }, + &ServiceTask::Retry => { Self::retry_pending_requests(&data) .map(|processed_requests| { if processed_requests != 0 { @@ -196,38 +254,9 @@ impl ServiceContractListener { warn!(target: "secretstore", "{}: retrying pending requests has failed with: {}", data.self_key_pair.public(), error); error - }), - ServiceTask::RestoreServerKey(server_key_id) => { - data.retry_data.lock().generated_keys.insert(server_key_id.clone()); - Self::restore_server_key(&data, &server_key_id) - .and_then(|server_key| Self::publish_server_key(&data, &server_key_id, &server_key)) - .map(|_| { - trace!(target: "secretstore", "{}: processed RestoreServerKey({}) request", - data.self_key_pair.public(), server_key_id); - () - }) - .map_err(|error| { - warn!(target: "secretstore", "{}: failed to process RestoreServerKey({}) request with: {}", - data.self_key_pair.public(), server_key_id, error); - error }) }, - ServiceTask::GenerateServerKey(server_key_id, threshold) => { - data.retry_data.lock().generated_keys.insert(server_key_id.clone()); - Self::generate_server_key(&data, &server_key_id, &threshold) - .and_then(|server_key| Self::publish_server_key(&data, &server_key_id, &server_key)) - .map(|_| { - trace!(target: "secretstore", "{}: processed GenerateServerKey({}, {}) request", - data.self_key_pair.public(), server_key_id, threshold); - () - }) - .map_err(|error| { - warn!(target: "secretstore", "{}: failed to process GenerateServerKey({}, {}) request with: {}", - data.self_key_pair.public(), server_key_id, threshold, error); - error - }) - }, - ServiceTask::Shutdown => unreachable!("it must be checked outside"), + &ServiceTask::Shutdown => unreachable!("must be filtered outside"), } } @@ -236,32 +265,28 @@ impl ServiceContractListener { let mut failed_requests = 0; let mut processed_requests = 0; let retry_data = ::std::mem::replace(&mut *data.retry_data.lock(), Default::default()); - for (is_confirmed, task) in data.contract.read_pending_requests() { + let pending_tasks = data.contract.read_pending_requests() + .filter_map(|(is_confirmed, task)| Self::filter_task(data, task) + .map(|t| (is_confirmed, t))); + for (is_confirmed, task) in pending_tasks { // only process requests, which we haven't confirmed yet if is_confirmed { continue; } - let request_result = match task { - ServiceTask::GenerateServerKey(server_key_id, threshold) => { - // only process request, which haven't been processed recently - // there could be a lag when we've just generated server key && retrying on the same block - // (or before our tx is mined) - state is not updated yet - if retry_data.generated_keys.contains(&server_key_id) { - continue; - } - - // process request - let is_own_request = is_processed_by_this_key_server(&*data.key_server_set, &*data.self_key_pair, &server_key_id); - Self::process_service_task(data, match is_own_request { - true => ServiceTask::GenerateServerKey(server_key_id, threshold.into()), - false => ServiceTask::RestoreServerKey(server_key_id), - }) - }, - _ => Err("not supported".into()), - }; + match task { + ServiceTask::GenerateServerKey(_, ref key, _, _) | ServiceTask::RetrieveServerKey(_, ref key) + if retry_data.affected_server_keys.contains(key) => continue, + ServiceTask::StoreDocumentKey(_, ref key, ref author, _, _) | + ServiceTask::RetrieveShadowDocumentKeyCommon(_, ref key, ref author) + if retry_data.affected_document_keys.contains(&(key.clone(), author.clone())) => continue, + ServiceTask::RetrieveShadowDocumentKeyPersonal(_, ref key, ref requester) + if retry_data.affected_document_keys.contains(&(key.clone(), public_to_address(requester))) => continue, + _ => (), + } // process request result + let request_result = Self::process_service_task(data, task); match request_result { Ok(_) => processed_requests += 1, Err(_) => { @@ -276,33 +301,119 @@ impl ServiceContractListener { Ok(processed_requests) } - /// Generate server key. - fn generate_server_key(data: &Arc, server_key_id: &ServerKeyId, threshold: &H256) -> Result { - let threshold_num = threshold.low_u64(); - if threshold != &threshold_num.into() || threshold_num >= ::std::usize::MAX as u64 { - return Err(format!("invalid threshold {:?}", threshold)); + /// Generate server key (start generation session). + fn generate_server_key(data: &Arc, origin: Address, server_key_id: &ServerKeyId, author: Address, threshold: usize) -> Result<(), String> { + Self::process_server_key_generation_result(data, origin, server_key_id, data.cluster.new_generation_session( + server_key_id.clone(), Some(origin), author, threshold).map(|_| None).map_err(Into::into)) + } + + /// Process server key generation result. + fn process_server_key_generation_result(data: &Arc, origin: Address, server_key_id: &ServerKeyId, result: Result, Error>) -> Result<(), String> { + match result { + Ok(None) => Ok(()), + Ok(Some(server_key)) => { + data.contract.publish_generated_server_key(&origin, server_key_id, server_key) + }, + Err(ref error) if is_internal_error(error) => Err(format!("{}", error)), + Err(ref error) => { + // ignore error as we're already processing an error + let _ = data.contract.publish_server_key_generation_error(&origin, server_key_id) + .map_err(|error| warn!(target: "secretstore", "{}: failed to publish GenerateServerKey({}) error: {}", + data.self_key_pair.public(), server_key_id, error)); + Err(format!("{}", error)) + } } - - // key server expects signed server_key_id in server_key_generation procedure - // only signer could store document key for this server key later - // => this API (server key generation) is not suitable for usage in encryption via contract endpoint - let author_key = Random.generate().map_err(|e| format!("{}", e))?; - let server_key_id_signature = sign(author_key.secret(), server_key_id).map_err(|e| format!("{}", e))?; - data.key_server.generate_key(server_key_id, &server_key_id_signature, threshold_num as usize) - .map_err(Into::into) } - /// Restore server key. - fn restore_server_key(data: &Arc, server_key_id: &ServerKeyId) -> Result { - data.key_storage.get(server_key_id) - .map_err(|e| format!("{}", e)) - .and_then(|ks| ks.ok_or("missing key".to_owned())) - .map(|ks| ks.public) + /// Retrieve server key. + fn retrieve_server_key(data: &Arc, origin: Address, server_key_id: &ServerKeyId) -> Result<(), String> { + match data.key_storage.get(server_key_id) { + Ok(Some(server_key_share)) => { + data.contract.publish_retrieved_server_key(&origin, server_key_id, server_key_share.public, server_key_share.threshold) + }, + Ok(None) => { + data.contract.publish_server_key_retrieval_error(&origin, server_key_id) + } + Err(ref error) if is_internal_error(error) => Err(format!("{}", error)), + Err(ref error) => { + // ignore error as we're already processing an error + let _ = data.contract.publish_server_key_retrieval_error(&origin, server_key_id) + .map_err(|error| warn!(target: "secretstore", "{}: failed to publish RetrieveServerKey({}) error: {}", + data.self_key_pair.public(), server_key_id, error)); + Err(format!("{}", error)) + } + } } - /// Publish server key. - fn publish_server_key(data: &Arc, server_key_id: &ServerKeyId, server_key: &Public) -> Result<(), String> { - data.contract.publish_server_key(server_key_id, server_key) + /// Store document key. + fn store_document_key(data: &Arc, origin: Address, server_key_id: &ServerKeyId, author: &Address, common_point: &Public, encrypted_point: &Public) -> Result<(), String> { + let store_result = data.key_storage.get(server_key_id) + .and_then(|key_share| key_share.ok_or(Error::DocumentNotFound)) + .and_then(|key_share| check_encrypted_data(Some(&key_share)).map(|_| key_share).map_err(Into::into)) + .and_then(|key_share| update_encrypted_data(&data.key_storage, server_key_id.clone(), key_share, + author.clone(), common_point.clone(), encrypted_point.clone()).map_err(Into::into)); + match store_result { + Ok(()) => { + data.contract.publish_stored_document_key(&origin, server_key_id) + }, + Err(ref error) if is_internal_error(&error) => Err(format!("{}", error)), + Err(ref error) => { + // ignore error as we're already processing an error + let _ = data.contract.publish_document_key_store_error(&origin, server_key_id) + .map_err(|error| warn!(target: "secretstore", "{}: failed to publish StoreDocumentKey({}) error: {}", + data.self_key_pair.public(), server_key_id, error)); + Err(format!("{}", error)) + }, + } + } + + /// Retrieve common part of document key. + fn retrieve_document_key_common(data: &Arc, origin: Address, server_key_id: &ServerKeyId, requester: &Address) -> Result<(), String> { + let retrieval_result = data.acl_storage.check(requester.clone(), server_key_id) + .and_then(|is_allowed| if !is_allowed { Err(Error::AccessDenied) } else { Ok(()) }) + .and_then(|_| data.key_storage.get(server_key_id).and_then(|key_share| key_share.ok_or(Error::DocumentNotFound))) + .and_then(|key_share| key_share.common_point + .ok_or(Error::DocumentNotFound) + .and_then(|common_point| math::make_common_shadow_point(key_share.threshold, common_point) + .map_err(|e| Error::Internal(e.into()))) + .map(|common_point| (common_point, key_share.threshold))); + match retrieval_result { + Ok((common_point, threshold)) => { + data.contract.publish_retrieved_document_key_common(&origin, server_key_id, requester, common_point, threshold) + }, + Err(ref error) if is_internal_error(&error) => Err(format!("{}", error)), + Err(ref error) => { + // ignore error as we're already processing an error + let _ = data.contract.publish_document_key_retrieval_error(&origin, server_key_id, requester) + .map_err(|error| warn!(target: "secretstore", "{}: failed to publish RetrieveDocumentKey({}) error: {}", + data.self_key_pair.public(), server_key_id, error)); + Err(format!("{}", error)) + }, + } + } + + /// Retrieve personal part of document key (start decryption session). + fn retrieve_document_key_personal(data: &Arc, origin: Address, server_key_id: &ServerKeyId, requester: Public) -> Result<(), String> { + Self::process_document_key_retrieval_result(data, origin, server_key_id, &public_to_address(&requester), data.cluster.new_decryption_session( + server_key_id.clone(), Some(origin), requester.clone().into(), None, true, true).map(|_| None).map_err(Into::into)) + } + + /// Process document key retrieval result. + fn process_document_key_retrieval_result(data: &Arc, origin: Address, server_key_id: &ServerKeyId, requester: &Address, result: Result, Public, Bytes)>, Error>) -> Result<(), String> { + match result { + Ok(None) => Ok(()), + Ok(Some((participants, decrypted_secret, shadow))) => { + data.contract.publish_retrieved_document_key_personal(&origin, server_key_id, &requester, &participants, decrypted_secret, shadow) + }, + Err(ref error) if is_internal_error(error) => Err(format!("{}", error)), + Err(ref error) => { + // ignore error as we're already processing an error + let _ = data.contract.publish_document_key_retrieval_error(&origin, server_key_id, &requester) + .map_err(|error| warn!(target: "secretstore", "{}: failed to publish RetrieveDocumentKey({}) error: {}", + data.self_key_pair.public(), server_key_id, error)); + Err(format!("{}", error)) + } + } } } @@ -340,25 +451,84 @@ impl ChainNotify for ServiceContractListener { impl ClusterSessionsListener for ServiceContractListener { fn on_session_removed(&self, session: Arc) { - // only publish when the session is started by another node - // when it is started by this node, it is published from process_service_task - if !is_processed_by_this_key_server(&*self.data.key_server_set, &*self.data.self_key_pair, &session.id()) { - // by this time sesion must already be completed - either successfully, or not - assert!(session.is_finished()); + // by this time sesion must already be completed - either successfully, or not + assert!(session.is_finished()); - // ignore result - the only thing that we can do is to log the error - match session.wait(Some(Default::default())) - .map_err(|e| format!("{}", e)) - .and_then(|server_key| Self::publish_server_key(&self.data, &session.id(), &server_key)) { - Ok(_) => trace!(target: "secretstore", "{}: completed foreign GenerateServerKey({}) request", - self.data.self_key_pair.public(), session.id()), - Err(error) => warn!(target: "secretstore", "{}: failed to process GenerateServerKey({}) request with: {}", - self.data.self_key_pair.public(), session.id(), error), + // ignore result - the only thing that we can do is to log the error + let server_key_id = session.id(); + if let Some(origin) = session.origin() { + if let Some(generation_result) = session.wait(Some(Default::default())) { + let generation_result = generation_result.map(Some).map_err(Into::into); + let _ = Self::process_server_key_generation_result(&self.data, origin, &server_key_id, generation_result); } } } } +impl ClusterSessionsListener for ServiceContractListener { + fn on_session_removed(&self, session: Arc) { + // by this time sesion must already be completed - either successfully, or not + assert!(session.is_finished()); + + // ignore result - the only thing that we can do is to log the error + let session_id = session.id(); + let server_key_id = session_id.id; + if let (Some(requester), Some(origin)) = (session.requester().and_then(|r| r.address(&server_key_id).ok()), session.origin()) { + if let Some(retrieval_result) = session.wait(Some(Default::default())) { + let retrieval_result = retrieval_result.map(|key_shadow| + session.broadcast_shadows() + .and_then(|broadcast_shadows| + broadcast_shadows.get(self.data.self_key_pair.public()) + .map(|self_shadow| ( + broadcast_shadows.keys().map(public_to_address).collect(), + key_shadow.decrypted_secret, + self_shadow.clone() + ))) + ).map_err(Into::into); + let _ = Self::process_document_key_retrieval_result(&self.data, origin, &server_key_id, &requester, retrieval_result); + } + } + } +} + +impl ::std::fmt::Display for ServiceTask { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match *self { + ServiceTask::Retry => write!(f, "Retry"), + ServiceTask::GenerateServerKey(_, ref server_key_id, ref author, ref threshold) => + write!(f, "GenerateServerKey({}, {}, {})", server_key_id, author, threshold), + ServiceTask::RetrieveServerKey(_, ref server_key_id) => + write!(f, "RetrieveServerKey({})", server_key_id), + ServiceTask::StoreDocumentKey(_, ref server_key_id, ref author, _, _) => + write!(f, "StoreDocumentKey({}, {})", server_key_id, author), + ServiceTask::RetrieveShadowDocumentKeyCommon(_, ref server_key_id, ref requester) => + write!(f, "RetrieveShadowDocumentKeyCommon({}, {})", server_key_id, requester), + ServiceTask::RetrieveShadowDocumentKeyPersonal(_, ref server_key_id, ref requester) => + write!(f, "RetrieveShadowDocumentKeyPersonal({}, {})", server_key_id, public_to_address(requester)), + ServiceTask::Shutdown => write!(f, "Shutdown"), + } + } +} + +/// Is internal error? Internal error means that it is SS who's responsible for it, like: connectivity, db failure, ... +/// External error is caused by SS misuse, like: trying to generate duplicated key, access denied, ... +/// When internal error occurs, we just ignore request for now and will retry later. +/// When external error occurs, we reject request. +fn is_internal_error(_error: &Error) -> bool { + // TODO [Reliability]: implement me after proper is passed through network + false +} + +/// Log service task result. +fn log_service_task_result(task: &ServiceTask, self_id: &Public, result: Result<(), String>) -> Result<(), String> { + match result { + Ok(_) => trace!(target: "secretstore", "{}: processed {} request", self_id, task), + Err(ref error) => warn!(target: "secretstore", "{}: failed to process {} request with: {}", self_id, task, error), + } + + result +} + /// Returns true when session, related to `server_key_id` must be started on this KeyServer. fn is_processed_by_this_key_server(key_server_set: &KeyServerSet, self_key_pair: &NodeKeyPair, server_key_id: &H256) -> bool { let servers = key_server_set.snapshot().current_set; @@ -390,17 +560,31 @@ mod tests { use listener::service_contract::ServiceContract; use listener::service_contract::tests::DummyServiceContract; use key_server_cluster::DummyClusterClient; - use key_server::tests::DummyKeyServer; + use acl_storage::{AclStorage, DummyAclStorage}; use key_storage::{KeyStorage, DocumentKeyShare}; use key_storage::tests::DummyKeyStorage; use key_server_set::tests::MapKeyServerSet; - use PlainNodeKeyPair; + use {PlainNodeKeyPair, ServerKeyId}; use super::{ServiceTask, ServiceContractListener, ServiceContractListenerParams, is_processed_by_this_key_server}; - fn make_service_contract_listener(contract: Option>, key_server: Option>, key_storage: Option>) -> Arc { + fn create_non_empty_key_storage(has_doc_key: bool) -> Arc { + let key_storage = Arc::new(DummyKeyStorage::default()); + let mut key_share = DocumentKeyShare::default(); + key_share.public = KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001" + .parse().unwrap()).unwrap().public().clone(); + if has_doc_key { + key_share.common_point = Some(Default::default()); + key_share.encrypted_point = Some(Default::default()); + } + key_storage.insert(Default::default(), key_share.clone()).unwrap(); + key_storage + } + + fn make_service_contract_listener(contract: Option>, cluster: Option>, key_storage: Option>, acl_storage: Option>) -> Arc { let contract = contract.unwrap_or_else(|| Arc::new(DummyServiceContract::default())); - let key_server = key_server.unwrap_or_else(|| Arc::new(DummyKeyServer::default())); + let cluster = cluster.unwrap_or_else(|| Arc::new(DummyClusterClient::default())); let key_storage = key_storage.unwrap_or_else(|| Arc::new(DummyKeyStorage::default())); + let acl_storage = acl_storage.unwrap_or_else(|| Arc::new(DummyAclStorage::default())); let servers_set = Arc::new(MapKeyServerSet::new(vec![ ("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".parse().unwrap(), "127.0.0.1:8080".parse().unwrap()), @@ -412,12 +596,12 @@ mod tests { let self_key_pair = Arc::new(PlainNodeKeyPair::new(KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap()).unwrap())); ServiceContractListener::new(ServiceContractListenerParams { contract: contract, - key_server: key_server, self_key_pair: self_key_pair, key_server_set: servers_set, - cluster: Arc::new(DummyClusterClient::default()), + acl_storage: acl_storage, + cluster: cluster, key_storage: key_storage, - }) + }).unwrap() } #[test] @@ -576,51 +760,32 @@ mod tests { #[test] fn no_tasks_scheduled_when_no_contract_events() { - let listener = make_service_contract_listener(None, None, None); + let listener = make_service_contract_listener(None, None, None, None); assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); listener.process_service_contract_events(); assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); } + // server key generation tests + #[test] - fn server_key_generation_is_scheduled_when_requested_key_is_unknown() { + fn server_key_generation_is_scheduled_when_requested() { let mut contract = DummyServiceContract::default(); - contract.logs.push(vec![Default::default(), Default::default(), Default::default()]); - let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None); + contract.logs.push(ServiceTask::GenerateServerKey(Default::default(), Default::default(), Default::default(), 0)); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); listener.process_service_contract_events(); assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); - assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::GenerateServerKey(Default::default(), Default::default()))); + assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::GenerateServerKey( + Default::default(), Default::default(), Default::default(), 0))); } #[test] - fn no_new_tasks_scheduled_when_requested_key_is_unknown_and_request_belongs_to_other_key_server() { + fn no_new_tasks_scheduled_when_server_key_generation_requested_and_request_belongs_to_other_key_server() { let server_key_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap(); let mut contract = DummyServiceContract::default(); - contract.logs.push(vec![Default::default(), server_key_id, Default::default()]); - let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None); - assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); - listener.process_service_contract_events(); - assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); - } - - #[test] - fn server_key_restore_is_scheduled_when_requested_key_is_known() { - let mut contract = DummyServiceContract::default(); - contract.logs.push(vec![Default::default(), Default::default(), Default::default()]); - let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None); - listener.data.key_storage.insert(Default::default(), Default::default()).unwrap(); - assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); - listener.process_service_contract_events(); - assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); - assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RestoreServerKey(Default::default()))); - } - - #[test] - fn no_new_tasks_scheduled_when_wrong_number_of_topics_in_log() { - let mut contract = DummyServiceContract::default(); - contract.logs.push(vec![Default::default(), Default::default()]); - let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None); + contract.logs.push(ServiceTask::GenerateServerKey(Default::default(), server_key_id, Default::default(), 0)); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); listener.process_service_contract_events(); assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); @@ -628,32 +793,221 @@ mod tests { #[test] fn generation_session_is_created_when_processing_generate_server_key_task() { - let key_server = Arc::new(DummyKeyServer::default()); - let listener = make_service_contract_listener(None, Some(key_server.clone()), None); - ServiceContractListener::process_service_task(&listener.data, ServiceTask::GenerateServerKey(Default::default(), Default::default())).unwrap_err(); - assert_eq!(key_server.generation_requests_count.load(Ordering::Relaxed), 1); + let cluster = Arc::new(DummyClusterClient::default()); + let listener = make_service_contract_listener(None, Some(cluster.clone()), None, None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::GenerateServerKey( + Default::default(), Default::default(), Default::default(), Default::default())).unwrap_err(); + assert_eq!(cluster.generation_requests_count.load(Ordering::Relaxed), 1); } #[test] - fn key_is_read_and_published_when_processing_restore_server_key_task() { - let contract = Arc::new(DummyServiceContract::default()); - let key_storage = Arc::new(DummyKeyStorage::default()); - let mut key_share = DocumentKeyShare::default(); - key_share.public = KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap()).unwrap().public().clone(); - key_storage.insert(Default::default(), key_share.clone()).unwrap(); - let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage)); - ServiceContractListener::process_service_task(&listener.data, ServiceTask::RestoreServerKey(Default::default())).unwrap(); - assert_eq!(*contract.published_keys.lock(), vec![(Default::default(), key_share.public)]); - } - - #[test] - fn generation_is_not_retried_if_tried_in_the_same_cycle() { + fn server_key_generation_is_not_retried_if_tried_in_the_same_cycle() { let mut contract = DummyServiceContract::default(); - contract.pending_requests.push((false, ServiceTask::GenerateServerKey(Default::default(), Default::default()))); - let key_server = Arc::new(DummyKeyServer::default()); - let listener = make_service_contract_listener(Some(Arc::new(contract)), Some(key_server.clone()), None); - listener.data.retry_data.lock().generated_keys.insert(Default::default()); + contract.pending_requests.push((false, ServiceTask::GenerateServerKey(Default::default(), + Default::default(), Default::default(), Default::default()))); + let cluster = Arc::new(DummyClusterClient::default()); + let listener = make_service_contract_listener(Some(Arc::new(contract)), Some(cluster.clone()), None, None); + listener.data.retry_data.lock().affected_server_keys.insert(Default::default()); ServiceContractListener::retry_pending_requests(&listener.data).unwrap(); - assert_eq!(key_server.generation_requests_count.load(Ordering::Relaxed), 0); + assert_eq!(cluster.generation_requests_count.load(Ordering::Relaxed), 0); + } + + // server key retrieval tests + + #[test] + fn server_key_retrieval_is_scheduled_when_requested() { + let mut contract = DummyServiceContract::default(); + contract.logs.push(ServiceTask::RetrieveServerKey(Default::default(), Default::default())); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); + listener.process_service_contract_events(); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); + assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveServerKey( + Default::default(), Default::default()))); + } + + #[test] + fn server_key_retrieval_is_scheduled_when_requested_and_request_belongs_to_other_key_server() { + let server_key_id: ServerKeyId = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap(); + let mut contract = DummyServiceContract::default(); + contract.logs.push(ServiceTask::RetrieveServerKey(Default::default(), server_key_id.clone())); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); + listener.process_service_contract_events(); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); + assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveServerKey( + Default::default(), server_key_id))); + } + + #[test] + fn server_key_is_retrieved_when_processing_retrieve_server_key_task() { + let contract = Arc::new(DummyServiceContract::default()); + let key_storage = create_non_empty_key_storage(false); + let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage), None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveServerKey( + Default::default(), Default::default())).unwrap(); + assert_eq!(*contract.retrieved_server_keys.lock(), vec![(Default::default(), + KeyPair::from_secret("0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap()).unwrap().public().clone(), 0)]); + } + + #[test] + fn server_key_retrieval_failure_is_reported_when_processing_retrieve_server_key_task_and_key_is_unknown() { + let contract = Arc::new(DummyServiceContract::default()); + let listener = make_service_contract_listener(Some(contract.clone()), None, None, None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveServerKey( + Default::default(), Default::default())).unwrap(); + assert_eq!(*contract.server_keys_retrieval_failures.lock(), vec![Default::default()]); + } + + #[test] + fn server_key_retrieval_is_not_retried_if_tried_in_the_same_cycle() { + let mut contract = DummyServiceContract::default(); + contract.pending_requests.push((false, ServiceTask::RetrieveServerKey(Default::default(), Default::default()))); + let cluster = Arc::new(DummyClusterClient::default()); + let listener = make_service_contract_listener(Some(Arc::new(contract)), Some(cluster.clone()), None, None); + listener.data.retry_data.lock().affected_server_keys.insert(Default::default()); + ServiceContractListener::retry_pending_requests(&listener.data).unwrap(); + assert_eq!(cluster.generation_requests_count.load(Ordering::Relaxed), 0); + } + + // document key store tests + + #[test] + fn document_key_store_is_scheduled_when_requested() { + let mut contract = DummyServiceContract::default(); + contract.logs.push(ServiceTask::StoreDocumentKey(Default::default(), Default::default(), + Default::default(), Default::default(), Default::default())); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); + listener.process_service_contract_events(); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); + assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::StoreDocumentKey( + Default::default(), Default::default(), Default::default(), Default::default(), Default::default()))); + } + + #[test] + fn document_key_store_is_scheduled_when_requested_and_request_belongs_to_other_key_server() { + let server_key_id: ServerKeyId = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap(); + let mut contract = DummyServiceContract::default(); + contract.logs.push(ServiceTask::StoreDocumentKey(Default::default(), server_key_id.clone(), + Default::default(), Default::default(), Default::default())); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); + listener.process_service_contract_events(); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); + assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::StoreDocumentKey( + Default::default(), server_key_id, Default::default(), Default::default(), Default::default()))); + } + + #[test] + fn document_key_is_stored_when_processing_store_document_key_task() { + let contract = Arc::new(DummyServiceContract::default()); + let key_storage = create_non_empty_key_storage(false); + let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey( + Default::default(), Default::default(), Default::default(), Default::default(), Default::default())).unwrap(); + assert_eq!(*contract.stored_document_keys.lock(), vec![Default::default()]); + + let key_share = key_storage.get(&Default::default()).unwrap().unwrap(); + assert_eq!(key_share.common_point, Some(Default::default())); + assert_eq!(key_share.encrypted_point, Some(Default::default())); + } + + #[test] + fn document_key_store_failure_reported_when_no_server_key() { + let contract = Arc::new(DummyServiceContract::default()); + let listener = make_service_contract_listener(Some(contract.clone()), None, None, None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey( + Default::default(), Default::default(), Default::default(), Default::default(), Default::default())).unwrap_err(); + assert_eq!(*contract.document_keys_store_failures.lock(), vec![Default::default()]); + } + + #[test] + fn document_key_store_failure_reported_when_document_key_already_set() { + let contract = Arc::new(DummyServiceContract::default()); + let key_storage = create_non_empty_key_storage(true); + let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage), None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey( + Default::default(), Default::default(), Default::default(), Default::default(), Default::default())).unwrap_err(); + assert_eq!(*contract.document_keys_store_failures.lock(), vec![Default::default()]); + } + + #[test] + fn document_key_store_failure_reported_when_author_differs() { + let contract = Arc::new(DummyServiceContract::default()); + let key_storage = create_non_empty_key_storage(false); + let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage), None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::StoreDocumentKey( + Default::default(), Default::default(), 1.into(), Default::default(), Default::default())).unwrap_err(); + assert_eq!(*contract.document_keys_store_failures.lock(), vec![Default::default()]); + } + + // document key shadow common retrieval tests + + #[test] + fn document_key_shadow_common_retrieval_is_scheduled_when_requested() { + let mut contract = DummyServiceContract::default(); + contract.logs.push(ServiceTask::RetrieveShadowDocumentKeyCommon(Default::default(), Default::default(), Default::default())); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); + listener.process_service_contract_events(); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); + assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveShadowDocumentKeyCommon( + Default::default(), Default::default(), Default::default()))); + } + + #[test] + fn document_key_shadow_common_retrieval_is_scheduled_when_requested_and_request_belongs_to_other_key_server() { + let server_key_id: ServerKeyId = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse().unwrap(); + let mut contract = DummyServiceContract::default(); + contract.logs.push(ServiceTask::RetrieveShadowDocumentKeyCommon(Default::default(), server_key_id.clone(), Default::default())); + let listener = make_service_contract_listener(Some(Arc::new(contract)), None, None, None); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 1); + listener.process_service_contract_events(); + assert_eq!(listener.data.tasks_queue.snapshot().len(), 2); + assert_eq!(listener.data.tasks_queue.snapshot().pop_back(), Some(ServiceTask::RetrieveShadowDocumentKeyCommon( + Default::default(), server_key_id, Default::default()))); + } + + #[test] + fn document_key_shadow_common_is_retrieved_when_processing_document_key_shadow_common_retrieval_task() { + let contract = Arc::new(DummyServiceContract::default()); + let key_storage = create_non_empty_key_storage(true); + let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon( + Default::default(), Default::default(), Default::default())).unwrap(); + assert_eq!(*contract.common_shadow_retrieved_document_keys.lock(), vec![(Default::default(), Default::default(), + Default::default(), 0)]); + } + + #[test] + fn document_key_shadow_common_retrieval_failure_reported_when_access_denied() { + let acl_storage = DummyAclStorage::default(); + acl_storage.prohibit(Default::default(), Default::default()); + let contract = Arc::new(DummyServiceContract::default()); + let key_storage = create_non_empty_key_storage(true); + let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), Some(Arc::new(acl_storage))); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon( + Default::default(), Default::default(), Default::default())).unwrap_err(); + assert_eq!(*contract.document_keys_shadow_retrieval_failures.lock(), vec![(Default::default(), Default::default())]); + } + + #[test] + fn document_key_shadow_common_retrieval_failure_reported_when_no_server_key() { + let contract = Arc::new(DummyServiceContract::default()); + let listener = make_service_contract_listener(Some(contract.clone()), None, None, None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon( + Default::default(), Default::default(), Default::default())).unwrap_err(); + assert_eq!(*contract.document_keys_shadow_retrieval_failures.lock(), vec![(Default::default(), Default::default())]); + } + + #[test] + fn document_key_shadow_common_retrieval_failure_reported_when_no_document_key() { + let contract = Arc::new(DummyServiceContract::default()); + let key_storage = create_non_empty_key_storage(false); + let listener = make_service_contract_listener(Some(contract.clone()), None, Some(key_storage.clone()), None); + ServiceContractListener::process_service_task(&listener.data, ServiceTask::RetrieveShadowDocumentKeyCommon( + Default::default(), Default::default(), Default::default())).unwrap_err(); + assert_eq!(*contract.document_keys_shadow_retrieval_failures.lock(), vec![(Default::default(), Default::default())]); } } diff --git a/secret_store/src/traits.rs b/secret_store/src/traits.rs index d486cfb10..ea09a3d90 100644 --- a/secret_store/src/traits.rs +++ b/secret_store/src/traits.rs @@ -17,8 +17,8 @@ use std::collections::BTreeSet; use ethkey::{KeyPair, Signature, Error as EthKeyError}; use ethereum_types::{H256, Address}; -use types::all::{Error, Public, ServerKeyId, MessageHash, EncryptedMessageSignature, RequestSignature, EncryptedDocumentKey, - EncryptedDocumentKeyShadow, NodeId}; +use types::all::{Error, Public, ServerKeyId, MessageHash, EncryptedMessageSignature, RequestSignature, Requester, + EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId}; /// Node key pair. pub trait NodeKeyPair: Send + Sync { @@ -36,34 +36,34 @@ pub trait NodeKeyPair: Send + Sync { pub trait ServerKeyGenerator { /// Generate new SK. /// `key_id` is the caller-provided identifier of generated SK. - /// `signature` is `key_id`, signed with caller public key. + /// `author` is the author of key entry. /// `threshold + 1` is the minimal number of nodes, required to restore private key. /// Result is a public portion of SK. - fn generate_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result; + fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result; } /// Document key (DK) server. pub trait DocumentKeyServer: ServerKeyGenerator { /// Store externally generated DK. /// `key_id` is identifier of previously generated SK. - /// `signature` is key_id, signed with caller public key. Caller must be the same as in the `generate_key` call. + /// `author` is the same author, that has created the server key. /// `common_point` is a result of `k * T` expression, where `T` is generation point and `k` is random scalar in EC field. /// `encrypted_document_key` is a result of `M + k * y` expression, where `M` is unencrypted document key (point on EC), /// `k` is the same scalar used in `common_point` calculation and `y` is previously generated public part of SK. - fn store_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, common_point: Public, encrypted_document_key: Public) -> Result<(), Error>; + fn store_document_key(&self, key_id: &ServerKeyId, author: &Requester, common_point: Public, encrypted_document_key: Public) -> Result<(), Error>; /// Generate and store both SK and DK. This is a shortcut for consequent calls of `generate_key` and `store_document_key`. /// The only difference is that DK is generated by DocumentKeyServer (which might be considered unsafe). /// `key_id` is the caller-provided identifier of generated SK. - /// `signature` is `key_id`, signed with caller public key. + /// `author` is the author of server && document key entry. /// `threshold + 1` is the minimal number of nodes, required to restore private key. /// Result is a DK, encrypted with caller public key. - fn generate_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature, threshold: usize) -> Result; + fn generate_document_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result; /// Restore previously stored DK. /// DK is decrypted on the key server (which might be considered unsafe), and then encrypted with caller public key. /// `key_id` is identifier of previously generated SK. - /// `signature` is key_id, signed with caller public key. Caller must be on ACL for this function to succeed. + /// `requester` is the one who requests access to document key. Caller must be on ACL for this function to succeed. /// Result is a DK, encrypted with caller public key. - fn restore_document_key(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result; + fn restore_document_key(&self, key_id: &ServerKeyId, requester: &Requester) -> Result; /// Restore previously stored DK. /// To decrypt DK on client: /// 1) use requestor secret key to decrypt secret coefficients from result.decrypt_shadows @@ -71,24 +71,24 @@ pub trait DocumentKeyServer: ServerKeyGenerator { /// 3) calculate decrypt_shadow_point: decrypt_shadows_sum * result.common_point /// 4) calculate decrypted_secret: result.decrypted_secret + decrypt_shadow_point /// Result is a DK shadow. - fn restore_document_key_shadow(&self, key_id: &ServerKeyId, signature: &RequestSignature) -> Result; + fn restore_document_key_shadow(&self, key_id: &ServerKeyId, requester: &Requester) -> Result; } /// Message signer. pub trait MessageSigner: ServerKeyGenerator { /// Generate Schnorr signature for message with previously generated SK. /// `key_id` is the caller-provided identifier of generated SK. - /// `signature` is `key_id`, signed with caller public key. + /// `requester` is the one who requests access to server key private. /// `message` is the message to be signed. /// Result is a signed message, encrypted with caller public key. - fn sign_message_schnorr(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result; + fn sign_message_schnorr(&self, key_id: &ServerKeyId, requester: &Requester, message: MessageHash) -> Result; /// Generate ECDSA signature for message with previously generated SK. /// WARNING: only possible when SK was generated using t <= 2 * N. /// `key_id` is the caller-provided identifier of generated SK. /// `signature` is `key_id`, signed with caller public key. /// `message` is the message to be signed. /// Result is a signed message, encrypted with caller public key. - fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &RequestSignature, message: MessageHash) -> Result; + fn sign_message_ecdsa(&self, key_id: &ServerKeyId, signature: &Requester, message: MessageHash) -> Result; } /// Administrative sessions server. diff --git a/secret_store/src/types/all.rs b/secret_store/src/types/all.rs index 9c6f4d172..356f1f1f0 100644 --- a/secret_store/src/types/all.rs +++ b/secret_store/src/types/all.rs @@ -38,8 +38,8 @@ pub use ethkey::Public; /// Secret store error #[derive(Debug, PartialEq)] pub enum Error { - /// Bad signature is passed - BadSignature, + /// Insufficient requester data + InsufficientRequesterData(String), /// Access to resource is denied AccessDenied, /// Requested document not found @@ -77,8 +77,16 @@ pub enum ContractAddress { pub struct ServiceConfiguration { /// HTTP listener address. If None, HTTP API is disabled. pub listener_address: Option, - /// Service contract address. If None, service contract API is disabled. + /// Service contract address. pub service_contract_address: Option, + /// Server key generation service contract address. + pub service_contract_srv_gen_address: Option, + /// Server key retrieval service contract address. + pub service_contract_srv_retr_address: Option, + /// Document key store service contract address. + pub service_contract_doc_store_address: Option, + /// Document key shadow retrieval service contract address. + pub service_contract_doc_sretr_address: Option, /// Is ACL check enabled. If false, everyone has access to all keys. Useful for tests only. pub acl_check_enabled: bool, /// Data directory path for secret store @@ -131,7 +139,7 @@ pub enum Requester { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - Error::BadSignature => write!(f, "Bad signature"), + Error::InsufficientRequesterData(ref e) => write!(f, "Insufficient requester data: {}", e), Error::AccessDenied => write!(f, "Access dened"), Error::DocumentNotFound => write!(f, "Document not found"), Error::Hyper(ref msg) => write!(f, "Hyper error: {}", msg), @@ -163,6 +171,8 @@ impl From for Error { impl From for Error { fn from(err: key_server_cluster::Error) -> Self { match err { + key_server_cluster::Error::InsufficientRequesterData(err) + => Error::InsufficientRequesterData(err), key_server_cluster::Error::ConsensusUnreachable | key_server_cluster::Error::AccessDenied => Error::AccessDenied, key_server_cluster::Error::MissingKeyShare => Error::DocumentNotFound, @@ -184,16 +194,18 @@ impl Default for Requester { } impl Requester { - pub fn public(&self, server_key_id: &ServerKeyId) -> Option { + pub fn public(&self, server_key_id: &ServerKeyId) -> Result { match *self { - Requester::Signature(ref signature) => ethkey::recover(signature, server_key_id).ok(), - Requester::Public(ref public) => Some(public.clone()), - Requester::Address(_) => None, + Requester::Signature(ref signature) => ethkey::recover(signature, server_key_id) + .map_err(|e| format!("bad signature: {}", e)), + Requester::Public(ref public) => Ok(public.clone()), + Requester::Address(_) => Err("cannot recover public from address".into()), } } - pub fn address(&self, server_key_id: &ServerKeyId) -> Option { - self.public(server_key_id).map(|p| ethkey::public_to_address(&p)) + pub fn address(&self, server_key_id: &ServerKeyId) -> Result { + self.public(server_key_id) + .map(|p| ethkey::public_to_address(&p)) } } @@ -202,3 +214,15 @@ impl From for Requester { Requester::Signature(signature) } } + +impl From for Requester { + fn from(public: ethereum_types::Public) -> Requester { + Requester::Public(public) + } +} + +impl From for Requester { + fn from(address: ethereum_types::Address) -> Requester { + Requester::Address(address) + } +}