diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 237aa3b30..445e75d1b 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -454,7 +454,7 @@ usage! { "--jsonrpc-interface=[IP]", "Specify the hostname portion of the JSONRPC API server, IP should be an interface's IP address, or all (all interfaces) or local.", - ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,secretstore,shh,shh_pubsub", or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")), + ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,shh,shh_pubsub", or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")), "--jsonrpc-apis=[APIS]", "Specify the APIs available through the JSONRPC interface. APIS is a comma-delimited list of API name. Possible name are all, safe, web3, eth, net, personal, parity, parity_set, traces, rpc, parity_accounts. You can also disable a specific API by putting '-' in the front: all,-personal.", @@ -487,7 +487,7 @@ usage! { "--ws-interface=[IP]", "Specify the hostname portion of the WebSockets server, IP should be an interface's IP address, or all (all interfaces) or local.", - ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,secretstore,shh,shh_pubsub", or |c: &Config| otry!(c.websockets).apis.as_ref().map(|vec| vec.join(",")), + ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,traces,rpc,shh,shh_pubsub", or |c: &Config| otry!(c.websockets).apis.as_ref().map(|vec| vec.join(",")), "--ws-apis=[APIS]", "Specify the APIs available through the WebSockets interface. APIS is a comma-delimited list of API name. Possible name are web3, eth, pubsub, net, personal, parity, parity_set, traces, rpc, parity_accounts..", @@ -508,7 +508,7 @@ usage! { "--ipc-path=[PATH]", "Specify custom path for JSON-RPC over IPC service.", - ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,traces,rpc,secretstore,shh,shh_pubsub", or |c: &Config| otry!(c.ipc).apis.as_ref().map(|vec| vec.join(",")), + ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,traces,rpc,shh,shh_pubsub", or |c: &Config| otry!(c.ipc).apis.as_ref().map(|vec| vec.join(",")), "--ipc-apis=[APIS]", "Specify custom API set available via JSON-RPC over IPC.", diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index a5bc1e154..f2d97f892 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -66,7 +66,7 @@ pub enum Api { Traces, /// Rpc (Safe) Rpc, - /// SecretStore (Safe) + /// SecretStore (UNSAFE: arbitrary hash signing) SecretStore, /// Whisper (Safe) // TODO: _if_ someone guesses someone else's key or filter IDs they can remove @@ -602,7 +602,6 @@ impl ApiSet { Api::EthPubSub, Api::Parity, Api::Rpc, - Api::SecretStore, Api::Whisper, Api::WhisperPubSub, ].into_iter().cloned().collect(); @@ -627,6 +626,7 @@ impl ApiSet { public_list.insert(Api::ParityAccounts); public_list.insert(Api::ParitySet); public_list.insert(Api::Signer); + public_list.insert(Api::SecretStore); public_list }, ApiSet::All => { @@ -636,6 +636,7 @@ impl ApiSet { public_list.insert(Api::ParitySet); public_list.insert(Api::Signer); public_list.insert(Api::Personal); + public_list.insert(Api::SecretStore); public_list }, ApiSet::PubSub => [ @@ -686,7 +687,7 @@ mod test { fn test_api_set_unsafe_context() { let expected = vec![ // make sure this list contains only SAFE methods - Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub, + Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, ].into_iter().collect(); assert_eq!(ApiSet::UnsafeContext.list_apis(), expected); } @@ -695,7 +696,7 @@ mod test { fn test_api_set_ipc_context() { let expected = vec![ // safe - Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub, + Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, // semi-safe Api::ParityAccounts ].into_iter().collect(); @@ -737,7 +738,7 @@ mod test { #[test] fn test_safe_parsing() { assert_eq!("safe".parse::().unwrap(), ApiSet::List(vec![ - Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::SecretStore, Api::Whisper, Api::WhisperPubSub, + Api::Web3, Api::Net, Api::Eth, Api::EthPubSub, Api::Parity, Api::ParityPubSub, Api::Traces, Api::Rpc, Api::Whisper, Api::WhisperPubSub, ].into_iter().collect())); } } diff --git a/rpc/src/v1/impls/secretstore.rs b/rpc/src/v1/impls/secretstore.rs index c34da62d4..14cef5587 100644 --- a/rpc/src/v1/impls/secretstore.rs +++ b/rpc/src/v1/impls/secretstore.rs @@ -28,7 +28,7 @@ use v1::helpers::errors; use v1::helpers::accounts::unwrap_provider; use v1::helpers::secretstore::{encrypt_document, decrypt_document, decrypt_document_with_shadow, ordered_servers_keccak}; use v1::traits::SecretStore; -use v1::types::{H160, H512, Bytes}; +use v1::types::{H160, H256, H512, Bytes}; /// Parity implementation. pub struct SecretStoreClient { @@ -84,12 +84,15 @@ impl SecretStore for SecretStoreClient { .map(Into::into) } - fn sign_servers_set(&self, address: H160, password: String, servers_set: BTreeSet) -> Result { - let servers_set_keccak_value = ordered_servers_keccak(servers_set); + fn servers_set_hash(&self, servers_set: BTreeSet) -> Result { + Ok(ordered_servers_keccak(servers_set)) + } + + fn sign_raw_hash(&self, address: H160, password: String, raw_hash: H256) -> Result { let store = self.account_provider()?; store - .sign(address.into(), Some(password), servers_set_keccak_value.into()) + .sign(address.into(), Some(password), raw_hash.into()) .map(|s| Bytes::new((*s).to_vec())) - .map_err(|e| errors::account("Could not sign servers set.", e)) + .map_err(|e| errors::account("Could not sign raw hash.", e)) } } diff --git a/rpc/src/v1/tests/mocked/secretstore.rs b/rpc/src/v1/tests/mocked/secretstore.rs index b0b01cb65..e209f6a96 100644 --- a/rpc/src/v1/tests/mocked/secretstore.rs +++ b/rpc/src/v1/tests/mocked/secretstore.rs @@ -25,6 +25,7 @@ use v1::metadata::Metadata; use v1::SecretStoreClient; use v1::traits::secretstore::SecretStore; use v1::helpers::secretstore::ordered_servers_keccak; +use v1::types::H256; struct Dependencies { pub accounts: Arc, @@ -100,7 +101,29 @@ fn rpc_secretstore_shadow_decrypt() { } #[test] -fn rpc_secretstore_sign_servers_set() { +fn rpc_secretstore_servers_set_hash() { + let deps = Dependencies::new(); + let io = deps.default_client(); + + // execute hashing request + let hashing_request = r#"{"jsonrpc": "2.0", "method": "secretstore_serversSetHash", "params":[ + ["0x843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91", + "0x07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3"] + ], "id": 1}"#; + let hashing_response = io.handle_request_sync(&hashing_request).unwrap(); + let hashing_response = hashing_response.replace(r#"{"jsonrpc":"2.0","result":"0x"#, ""); + let hashing_response = hashing_response.replace(r#"","id":1}"#, ""); + let hash: H256 = hashing_response.parse().unwrap(); + + let servers_set_keccak = ordered_servers_keccak(vec![ + "843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91".parse().unwrap(), + "07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3".parse().unwrap() + ].into_iter().collect()); + assert_eq!(hash, servers_set_keccak); +} + +#[test] +fn rpc_secretstore_sign_raw_hash() { let deps = Dependencies::new(); let io = deps.default_client(); @@ -110,19 +133,14 @@ fn rpc_secretstore_sign_servers_set() { deps.accounts.insert_account(key_pair.secret().clone(), "password").unwrap(); // execute signing request - let signing_request = r#"{"jsonrpc": "2.0", "method": "secretstore_signServersSet", "params":[ - "0x00dfE63B22312ab4329aD0d28CaD8Af987A01932", "password", - ["0x843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91", - "0x07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3"] + let signing_request = r#"{"jsonrpc": "2.0", "method": "secretstore_signRawHash", "params":[ + "0x00dfE63B22312ab4329aD0d28CaD8Af987A01932", "password", "0x0000000000000000000000000000000000000000000000000000000000000001" ], "id": 1}"#; let signing_response = io.handle_request_sync(&signing_request).unwrap(); let signing_response = signing_response.replace(r#"{"jsonrpc":"2.0","result":"0x"#, ""); let signing_response = signing_response.replace(r#"","id":1}"#, ""); let signature: Signature = signing_response.parse().unwrap(); - let servers_set_keccak = ordered_servers_keccak(vec![ - "843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91".parse().unwrap(), - "07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3".parse().unwrap() - ].into_iter().collect()); - assert!(verify_public(key_pair.public(), &signature, &servers_set_keccak.into()).unwrap()); + let hash = "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap(); + assert!(verify_public(key_pair.public(), &signature, &hash).unwrap()); } diff --git a/rpc/src/v1/traits/secretstore.rs b/rpc/src/v1/traits/secretstore.rs index f63bdce92..e3ce24185 100644 --- a/rpc/src/v1/traits/secretstore.rs +++ b/rpc/src/v1/traits/secretstore.rs @@ -19,7 +19,7 @@ use std::collections::BTreeSet; use jsonrpc_core::Result; -use v1::types::{H160, H512, Bytes}; +use v1::types::{H160, H256, H512, Bytes}; build_rpc_trait! { /// Parity-specific rpc interface. @@ -39,9 +39,16 @@ build_rpc_trait! { #[rpc(name = "secretstore_shadowDecrypt")] fn shadow_decrypt(&self, H160, String, H512, H512, Vec, Bytes) -> Result; - /// Sign servers set for use in ServersSetChange session. - /// Arguments: `account`, `password`, `servers_set`. - #[rpc(name = "secretstore_signServersSet")] - fn sign_servers_set(&self, H160, String, BTreeSet) -> Result; + /// Calculates the hash (keccak256) of servers set for using in ServersSetChange session. + /// Returned hash must be signed later by using `secretstore_signRawHash` method. + /// Arguments: `servers_set`. + #[rpc(name = "secretstore_serversSetHash")] + fn servers_set_hash(&self, BTreeSet) -> Result; + + /// Generate recoverable ECDSA signature of raw hash. + /// Passed hash is treated as an input to the `sign` function (no prefixes added, no hash function is applied). + /// Arguments: `account`, `password`, `raw_hash`. + #[rpc(name = "secretstore_signRawHash")] + fn sign_raw_hash(&self, H160, String, H256) -> Result; } }