Secretstore RPCs + integration (#5439)

* ECDKG protocol prototype

* added test for enc/dec math

* get rid of decryption_session

* added licenses

* fix after merge

* get rid of unused serde dependency

* doc

* decryption session [without commutative enc]

* failed_dec_session

* fixed tests

* added commen

* added more decryption session tests

* helper to localize an issue

* more computations to localize error

* decryption_session::SessionParams

* added tests for EC math to localize problem

* secretstore network transport

* encryption_session_works_over_network

* network errors processing

* connecting to KeyServer

* licenses

* get rid of debug println-s

* fixed secretstore args

* encryption results are stored in KS database

* decryption protocol works over network

* enc/dec Session traits

* fixing warnings

* fix after merge

* on-chain ACL checker proto

* fixed compilation

* fixed compilation

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

* temporary commented test

* 1-of-N works in math

* scheme 1-of-N works

* updated AclStorage with real contract ABI

* remove unnecessary unsafety

* fixed grumbles

* wakeup on access denied

* encrypt secretstore messages

* 'shadow' decryption

* fix grumbles

* lost files

* secretstore cli-options

* decryption seccion when ACL check failed on master

* disallow regenerating key for existing document

* removed obsolete TODO

* fix after merge

* switched to tokio_io

* fix after merge

* fix after merge

* fix after merge

* fix after merge

* fix after merge

* fixed test

* fix after merge

* encryption session errors are now fatal

* session timeouts

* autorestart decryption session

* remove sessions on completion

* exclude disconnected nodes from decryption session

* test for enc/dec session over network with 1 node

* remove debug printlns

* fixed 1-of-1 scheme

* drop for KeyServerHttpListener

* Use standard encryption and decryption (as in RPC)

* added some tests

* moved DEFAULT_MAC to ethcrypto

* rpc_secretstore_encrypt_and_decrypt

* serialization with "0x" prefix (RPC compatibility)

* secretstore RPC API

* fix after merge

* fixed typo

* secretstore_shadowDecrypt RPC

* enable secretstore RPCs by default

* fixed test

* SecStore RPCs available without SecStore feature

* fixed grumbles

* lost files

* added password argument to Parity RPCs

* update docs

* lost file
This commit is contained in:
Svyatoslav Nikolsky 2017-05-05 16:57:29 +03:00 committed by Gav Wood
parent 0d8920347a
commit 8b9adb4d74
24 changed files with 497 additions and 57 deletions

1
Cargo.lock generated
View File

@ -1712,6 +1712,7 @@ dependencies = [
"parity-reactor 0.1.0",
"parity-updater 1.7.0",
"pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rlp 0.1.0",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -34,6 +34,9 @@ pub const KEY_LENGTH: usize = 32;
pub const KEY_ITERATIONS: usize = 10240;
pub const KEY_LENGTH_AES: usize = KEY_LENGTH / 2;
/// Default MAC to use (in RPC).
pub const DEFAULT_MAC: [u8; 2] = [0, 0];
#[derive(PartialEq, Debug)]
pub enum ScryptError {
// log(N) < r / 16

View File

@ -50,7 +50,7 @@ disable = false
port = 8545
interface = "local"
cors = "null"
apis = ["web3", "eth", "net", "parity", "traces", "rpc"]
apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"]
hosts = ["none"]
[websockets]
@ -58,13 +58,13 @@ disable = false
port = 8546
interface = "local"
origins = ["none"]
apis = ["web3", "eth", "net", "parity", "traces", "rpc"]
apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"]
hosts = ["none"]
[ipc]
disable = false
path = "$HOME/.parity/jsonrpc.ipc"
apis = ["web3", "eth", "net", "parity", "parity_accounts", "personal", "traces", "rpc"]
apis = ["web3", "eth", "net", "parity", "parity_accounts", "personal", "traces", "rpc", "secretstore"]
[dapps]
disable = false

View File

@ -165,7 +165,7 @@ usage! {
or |c: &Config| otry!(c.rpc).interface.clone(),
flag_jsonrpc_cors: Option<String> = None,
or |c: &Config| otry!(c.rpc).cors.clone().map(Some),
flag_jsonrpc_apis: String = "web3,eth,net,parity,traces,rpc",
flag_jsonrpc_apis: String = "web3,eth,net,parity,traces,rpc,secretstore",
or |c: &Config| otry!(c.rpc).apis.as_ref().map(|vec| vec.join(",")),
flag_jsonrpc_hosts: String = "none",
or |c: &Config| otry!(c.rpc).hosts.as_ref().map(|vec| vec.join(",")),
@ -179,7 +179,7 @@ usage! {
or |c: &Config| otry!(c.websockets).port.clone(),
flag_ws_interface: String = "local",
or |c: &Config| otry!(c.websockets).interface.clone(),
flag_ws_apis: String = "web3,eth,net,parity,traces,rpc",
flag_ws_apis: String = "web3,eth,net,parity,traces,rpc,secretstore",
or |c: &Config| otry!(c.websockets).apis.as_ref().map(|vec| vec.join(",")),
flag_ws_origins: String = "none",
or |c: &Config| otry!(c.websockets).origins.as_ref().map(|vec| vec.join(",")),
@ -191,7 +191,7 @@ usage! {
or |c: &Config| otry!(c.ipc).disable.clone(),
flag_ipc_path: String = "$BASE/jsonrpc.ipc",
or |c: &Config| otry!(c.ipc).path.clone(),
flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc",
flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc,secretstore",
or |c: &Config| otry!(c.ipc).apis.as_ref().map(|vec| vec.join(",")),
// DAPPS
@ -723,7 +723,7 @@ mod tests {
flag_jsonrpc_port: 8545u16,
flag_jsonrpc_interface: "local".into(),
flag_jsonrpc_cors: Some("null".into()),
flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc".into(),
flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc,secretstore".into(),
flag_jsonrpc_hosts: "none".into(),
flag_jsonrpc_threads: None,
@ -731,14 +731,14 @@ mod tests {
flag_no_ws: false,
flag_ws_port: 8546u16,
flag_ws_interface: "local".into(),
flag_ws_apis: "web3,eth,net,parity,traces,rpc".into(),
flag_ws_apis: "web3,eth,net,parity,traces,rpc,secretstore".into(),
flag_ws_origins: "none".into(),
flag_ws_hosts: "none".into(),
// IPC
flag_no_ipc: false,
flag_ipc_path: "$HOME/.parity/jsonrpc.ipc".into(),
flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc".into(),
flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc,secretstore".into(),
// DAPPS
flag_dapps_path: "$HOME/.parity/dapps".into(),

View File

@ -59,6 +59,8 @@ pub enum Api {
Traces,
/// Rpc (Safe)
Rpc,
/// SecretStore (Safe)
SecretStore,
}
impl FromStr for Api {
@ -78,6 +80,7 @@ impl FromStr for Api {
"parity_set" => Ok(ParitySet),
"traces" => Ok(Traces),
"rpc" => Ok(Rpc),
"secretstore" => Ok(SecretStore),
api => Err(format!("Unknown api: {}", api))
}
}
@ -156,6 +159,7 @@ fn to_modules(apis: &[Api]) -> BTreeMap<String, String> {
Api::ParitySet => ("parity_set", "1.0"),
Api::Traces => ("traces", "1.0"),
Api::Rpc => ("rpc", "1.0"),
Api::SecretStore => ("secretstore", "1.0"),
};
modules.insert(name.into(), version.into());
}
@ -295,7 +299,10 @@ impl Dependencies for FullDependencies {
Api::Rpc => {
let modules = to_modules(&apis);
handler.extend_with(RpcClient::new(modules).to_delegate());
}
},
Api::SecretStore => {
handler.extend_with(SecretStoreClient::new(&self.secret_store).to_delegate());
},
}
}
}
@ -424,7 +431,11 @@ impl Dependencies for LightDependencies {
Api::Rpc => {
let modules = to_modules(&apis);
handler.extend_with(RpcClient::new(modules).to_delegate());
}
},
Api::SecretStore => {
let secret_store = Some(self.secret_store.clone());
handler.extend_with(SecretStoreClient::new(&secret_store).to_delegate());
},
}
}
}
@ -438,7 +449,7 @@ impl ApiSet {
pub fn list_apis(&self) -> HashSet<Api> {
let mut public_list = vec![
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Rpc,
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Rpc, Api::SecretStore,
].into_iter().collect();
match *self {
ApiSet::List(ref apis) => apis.clone(),
@ -496,6 +507,7 @@ mod test {
assert_eq!(Api::ParitySet, "parity_set".parse().unwrap());
assert_eq!(Api::Traces, "traces".parse().unwrap());
assert_eq!(Api::Rpc, "rpc".parse().unwrap());
assert_eq!(Api::SecretStore, "secretstore".parse().unwrap());
assert!("rp".parse::<Api>().is_err());
}
@ -513,7 +525,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::Parity, Api::Traces, Api::Rpc
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore
].into_iter().collect();
assert_eq!(ApiSet::UnsafeContext.list_apis(), expected);
}
@ -522,7 +534,7 @@ mod test {
fn test_api_set_ipc_context() {
let expected = vec![
// safe
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc,
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore,
// semi-safe
Api::ParityAccounts
].into_iter().collect();
@ -533,7 +545,7 @@ mod test {
fn test_api_set_safe_context() {
let expected = vec![
// safe
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc,
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore,
// semi-safe
Api::ParityAccounts,
// Unsafe
@ -545,7 +557,7 @@ mod test {
#[test]
fn test_all_apis() {
assert_eq!("all".parse::<ApiSet>().unwrap(), ApiSet::List(vec![
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc,
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore,
Api::ParityAccounts,
Api::ParitySet, Api::Signer,
Api::Personal
@ -555,7 +567,7 @@ mod test {
#[test]
fn test_all_without_personal_apis() {
assert_eq!("personal,all,-personal".parse::<ApiSet>().unwrap(), ApiSet::List(vec![
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc,
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore,
Api::ParityAccounts,
Api::ParitySet, Api::Signer,
].into_iter().collect()));
@ -564,7 +576,7 @@ mod test {
#[test]
fn test_safe_parsing() {
assert_eq!("safe".parse::<ApiSet>().unwrap(), ApiSet::List(vec![
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc,
Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, Api::SecretStore,
].into_iter().collect()));
}
}

View File

@ -21,6 +21,7 @@ transient-hashmap = "0.4"
cid = "0.2.1"
multihash = "0.5"
rust-crypto = "0.2.36"
rand = "0.3"
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }

View File

@ -30,6 +30,7 @@ extern crate transient_hashmap;
extern crate cid;
extern crate multihash;
extern crate crypto as rust_crypto;
extern crate rand;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server as http;

View File

@ -37,6 +37,7 @@ use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient;
use ethcore::transaction::{Action, SignedTransaction, PendingTransaction, Transaction};
use ethcore::account_provider::AccountProvider;
use crypto::DEFAULT_MAC;
use jsonrpc_core::Error;
use v1::helpers::{errors, TransactionRequest, FilledTransactionRequest, ConfirmationPayload};
@ -400,9 +401,6 @@ impl Dispatcher for LightDispatcher {
}
}
/// default MAC to use.
pub const DEFAULT_MAC: [u8; 2] = [0, 0];
/// Single-use account token.
pub type AccountToken = String;

View File

@ -25,6 +25,7 @@ pub mod light_fetch;
pub mod informant;
pub mod oneshot;
pub mod ipfs;
pub mod secretstore;
mod network_settings;
mod poll_manager;

View File

@ -0,0 +1,127 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::iter::repeat;
use rand::{Rng, OsRng};
use ethkey::{Public, Secret, math};
use crypto;
use util::Bytes;
use jsonrpc_core::Error;
use v1::helpers::errors;
/// Initialization vector length.
const INIT_VEC_LEN: usize = 16;
/// Encrypt document with distributely generated key.
pub fn encrypt_document(key: Bytes, document: Bytes) -> Result<Bytes, Error> {
// make document key
let key = into_document_key(key)?;
// use symmetric encryption to encrypt document
let iv = initialization_vector();
let mut encrypted_document = Vec::with_capacity(document.len() + iv.len());
encrypted_document.extend(repeat(0).take(document.len()));
crypto::aes::encrypt(&key, &iv, &document, &mut encrypted_document);
encrypted_document.extend_from_slice(&iv);
Ok(encrypted_document)
}
/// Decrypt document with distributely generated key.
pub fn decrypt_document(key: Bytes, mut encrypted_document: Bytes) -> Result<Bytes, Error> {
// initialization vector takes INIT_VEC_LEN bytes
let encrypted_document_len = encrypted_document.len();
if encrypted_document_len < INIT_VEC_LEN {
return Err(errors::invalid_params("encrypted_document", "invalid encrypted data"));
}
// make document key
let key = into_document_key(key)?;
// use symmetric decryption to decrypt document
let iv = encrypted_document.split_off(encrypted_document_len - INIT_VEC_LEN);
let mut document = Vec::with_capacity(encrypted_document_len - INIT_VEC_LEN);
document.extend(repeat(0).take(encrypted_document_len - INIT_VEC_LEN));
crypto::aes::decrypt(&key, &iv, &encrypted_document, &mut document);
Ok(document)
}
pub fn decrypt_document_with_shadow(decrypted_secret: Public, common_point: Public, shadows: Vec<Secret>, encrypted_document: Bytes) -> Result<Bytes, Error> {
let key = decrypt_with_shadow_coefficients(decrypted_secret, common_point, shadows)?;
decrypt_document(key.to_vec(), encrypted_document)
}
fn into_document_key(key: Bytes) -> Result<Bytes, Error> {
// key is a previously distributely generated Public
if key.len() != 64 {
return Err(errors::invalid_params("key", "invalid public key length"));
}
// use x coordinate of distributely generated point as encryption key
Ok(key[..INIT_VEC_LEN].into())
}
fn initialization_vector() -> [u8; INIT_VEC_LEN] {
let mut result = [0u8; INIT_VEC_LEN];
let mut rng = OsRng::new().unwrap();
rng.fill_bytes(&mut result);
result
}
fn decrypt_with_shadow_coefficients(mut decrypted_shadow: Public, mut common_shadow_point: Public, shadow_coefficients: Vec<Secret>) -> Result<Public, Error> {
let mut shadow_coefficients_sum = shadow_coefficients[0].clone();
for shadow_coefficient in shadow_coefficients.iter().skip(1) {
shadow_coefficients_sum.add(shadow_coefficient)
.map_err(errors::encryption_error)?;
}
math::public_mul_secret(&mut common_shadow_point, &shadow_coefficients_sum)
.map_err(errors::encryption_error)?;
math::public_add(&mut decrypted_shadow, &common_shadow_point)
.map_err(errors::encryption_error)?;
Ok(decrypted_shadow)
}
#[cfg(test)]
mod tests {
use util::Bytes;
use rustc_serialize::hex::FromHex;
use super::{encrypt_document, decrypt_document, decrypt_document_with_shadow};
#[test]
fn encrypt_and_decrypt_document() {
let document_key: Bytes = "cac6c205eb06c8308d65156ff6c862c62b000b8ead121a4455a8ddeff7248128d895692136f240d5d1614dc7cc4147b1bd584bd617e30560bb872064d09ea325".from_hex().unwrap();
let document: Bytes = b"Hello, world!!!"[..].into();
let encrypted_document = encrypt_document(document_key.clone(), document.clone()).unwrap();
assert!(document != encrypted_document);
let decrypted_document = decrypt_document(document_key.clone(), encrypted_document).unwrap();
assert_eq!(decrypted_document, document);
}
#[test]
fn encrypt_and_shadow_decrypt_document() {
let document: Bytes = "deadbeef".from_hex().unwrap();
let encrypted_document = "2ddec1f96229efa2916988d8b2a82a47ef36f71c".from_hex().unwrap();
let decrypted_secret = "843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91".parse().unwrap();
let common_point = "07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3".parse().unwrap();
let shadows = vec!["46f542416216f66a7d7881f5a283d2a1ab7a87b381cbc5f29d0b093c7c89ee31".parse().unwrap()];
let decrypted_document = decrypt_document_with_shadow(decrypted_secret, common_point, shadows, encrypted_document).unwrap();
assert_eq!(decrypted_document, document);
}
}

View File

@ -27,13 +27,14 @@ use ethkey::{Brain, Generator};
use ethstore::random_phrase;
use ethsync::LightSyncProvider;
use ethcore::account_provider::AccountProvider;
use crypto::DEFAULT_MAC;
use light::client::LightChainClient;
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::dispatch::{LightDispatcher, DEFAULT_MAC};
use v1::helpers::dispatch::LightDispatcher;
use v1::helpers::light_fetch::LightFetch;
use v1::metadata::Metadata;
use v1::traits::Parity;

View File

@ -27,6 +27,7 @@ mod signer;
mod signing;
mod signing_unsafe;
mod rpc;
mod secretstore;
mod traces;
mod web3;
@ -45,3 +46,4 @@ pub use self::signing::SigningQueueClient;
pub use self::signing_unsafe::SigningUnsafeClient;
pub use self::traces::TracesClient;
pub use self::rpc::RpcClient;
pub use self::secretstore::SecretStoreClient;

View File

@ -34,12 +34,12 @@ use ethcore::client::{MiningBlockChainClient};
use ethcore::mode::Mode;
use ethcore::account_provider::AccountProvider;
use updater::{Service as UpdateService};
use crypto::DEFAULT_MAC;
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use v1::helpers::{errors, ipfs, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::accounts::unwrap_provider;
use v1::helpers::dispatch::DEFAULT_MAC;
use v1::metadata::Metadata;
use v1::traits::Parity;
use v1::types::{

View File

@ -0,0 +1,85 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! SecretStore-specific rpc implementation.
use std::sync::{Arc, Weak};
use crypto::DEFAULT_MAC;
use ethkey::Secret;
use ethcore::account_provider::AccountProvider;
use jsonrpc_core::Error;
use v1::helpers::errors;
use v1::helpers::accounts::unwrap_provider;
use v1::helpers::secretstore::{encrypt_document, decrypt_document, decrypt_document_with_shadow};
use v1::traits::SecretStore;
use v1::types::{H160, H512, Bytes};
/// Parity implementation.
pub struct SecretStoreClient {
accounts: Option<Weak<AccountProvider>>,
}
impl SecretStoreClient {
/// Creates new SecretStoreClient
pub fn new(store: &Option<Arc<AccountProvider>>) -> Self {
SecretStoreClient {
accounts: store.as_ref().map(Arc::downgrade),
}
}
/// Attempt to get the `Arc<AccountProvider>`, errors if provider was not
/// set, or if upgrading the weak reference failed.
fn account_provider(&self) -> Result<Arc<AccountProvider>, Error> {
unwrap_provider(&self.accounts)
}
/// Decrypt public key using account' private key
fn decrypt_key(&self, address: H160, password: String, key: Bytes) -> Result<Vec<u8>, Error> {
let store = self.account_provider()?;
store.decrypt(address.into(), Some(password), &DEFAULT_MAC, &key.0)
.map_err(|e| errors::account("Could not decrypt key.", e))
}
/// Decrypt secret key using account' private key
fn decrypt_secret(&self, address: H160, password: String, key: Bytes) -> Result<Secret, Error> {
self.decrypt_key(address, password, key)
.and_then(|s| Secret::from_slice(&s).map_err(|e| errors::account("invalid secret", e)))
}
}
impl SecretStore for SecretStoreClient {
fn encrypt(&self, address: H160, password: String, key: Bytes, data: Bytes) -> Result<Bytes, Error> {
encrypt_document(self.decrypt_key(address, password, key)?, data.0)
.map(Into::into)
}
fn decrypt(&self, address: H160, password: String, key: Bytes, data: Bytes) -> Result<Bytes, Error> {
decrypt_document(self.decrypt_key(address, password, key)?, data.0)
.map(Into::into)
}
fn shadow_decrypt(&self, address: H160, password: String, decrypted_secret: H512, common_point: H512, decrypt_shadows: Vec<Bytes>, data: Bytes) -> Result<Bytes, Error> {
let mut shadows = Vec::with_capacity(decrypt_shadows.len());
for decrypt_shadow in decrypt_shadows {
shadows.push(self.decrypt_secret(address.clone(), password.clone(), decrypt_shadow)?);
}
decrypt_document_with_shadow(decrypted_secret.into(), common_point.into(), shadows, data.0)
.map(Into::into)
}
}

View File

@ -58,7 +58,7 @@ pub mod traits;
pub mod tests;
pub mod types;
pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc};
pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc, SecretStore};
pub use self::impls::*;
pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import, informant, dispatch};
pub use self::metadata::Metadata;

View File

@ -25,6 +25,7 @@ mod parity_accounts;
mod parity_set;
mod personal;
mod rpc;
mod secretstore;
mod signer;
mod signing;
mod traces;

View File

@ -0,0 +1,98 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use ethcore::account_provider::AccountProvider;
use serde_json;
use jsonrpc_core::{IoHandler, Success};
use v1::metadata::Metadata;
use v1::SecretStoreClient;
use v1::traits::secretstore::SecretStore;
struct Dependencies {
pub accounts: Arc<AccountProvider>,
}
impl Dependencies {
pub fn new() -> Self {
Dependencies {
accounts: Arc::new(AccountProvider::transient_provider()),
}
}
pub fn client(&self) -> SecretStoreClient {
SecretStoreClient::new(&Some(self.accounts.clone()))
}
fn default_client(&self) -> IoHandler<Metadata> {
let mut io = IoHandler::default();
io.extend_with(self.client().to_delegate());
io
}
}
#[test]
fn rpc_secretstore_encrypt_and_decrypt() {
let deps = Dependencies::new();
let io = deps.default_client();
// insert new account && unlock it
let secret = "c1f1cfe279a5c350d13795bce162941967340c8a228e6ba175489afc564a5bef".parse().unwrap();
deps.accounts.insert_account(secret, "password").unwrap();
// execute encryption request
let encryption_request = r#"{"jsonrpc": "2.0", "method": "secretstore_encrypt", "params":[
"0x5c2f3b4ec0c2234f8358697edc8b82a62e3ac995", "password",
"0x0440262acc06f1e13cb11b34e792cdf698673a16bb812163cb52689ac34c94ae47047b58f58d8b596d21ac7b03a55896132d07a7dc028b2dad88f6c5a90623fa5b30ff4b1ba385a98c970432d13417cf6d7facd62f86faaef15ca993735890da0cb3e417e2740fc72de7501eef083a12dd5a9ebe513b592b1740848576a936a1eb88fc553fc624b1cae41a0a4e074e34e2aaae686709f08d70e505c5acba12ef96017e89be675a2adb07c72c4e95814fbf",
"0xdeadbeef"
], "id": 1}"#;
let encryption_response = io.handle_request_sync(encryption_request).unwrap();
let encryption_response: Success = serde_json::from_str(&encryption_response).unwrap();
// execute decryption request
let decryption_request_left = r#"{"jsonrpc": "2.0", "method": "secretstore_decrypt", "params":[
"0x5c2f3b4ec0c2234f8358697edc8b82a62e3ac995", "password",
"0x0440262acc06f1e13cb11b34e792cdf698673a16bb812163cb52689ac34c94ae47047b58f58d8b596d21ac7b03a55896132d07a7dc028b2dad88f6c5a90623fa5b30ff4b1ba385a98c970432d13417cf6d7facd62f86faaef15ca993735890da0cb3e417e2740fc72de7501eef083a12dd5a9ebe513b592b1740848576a936a1eb88fc553fc624b1cae41a0a4e074e34e2aaae686709f08d70e505c5acba12ef96017e89be675a2adb07c72c4e95814fbf",""#;
let decryption_request_mid = encryption_response.result.as_str().unwrap();
let decryption_request_right = r#""
], "id": 2}"#;
let decryption_request = decryption_request_left.to_owned() + decryption_request_mid + decryption_request_right;
let decryption_response = io.handle_request_sync(&decryption_request).unwrap();
assert_eq!(decryption_response, r#"{"jsonrpc":"2.0","result":"0xdeadbeef","id":2}"#);
}
#[test]
fn rpc_secretstore_shadow_decrypt() {
let deps = Dependencies::new();
let io = deps.default_client();
// insert new account && unlock it
let secret = "82758356bf46b42710d3946a8efa612b7bf5e125e4d49f28facf1139db4a46f4".parse().unwrap();
deps.accounts.insert_account(secret, "password").unwrap();
// execute decryption request
let decryption_request = r#"{"jsonrpc": "2.0", "method": "secretstore_shadowDecrypt", "params":[
"0x00dfE63B22312ab4329aD0d28CaD8Af987A01932", "password",
"0x843645726384530ffb0c52f175278143b5a93959af7864460f5a4fec9afd1450cfb8aef63dec90657f43f55b13e0a73c7524d4e9a13c051b4e5f1e53f39ecd91",
"0x07230e34ebfe41337d3ed53b186b3861751f2401ee74b988bba55694e2a6f60c757677e194be2e53c3523cc8548694e636e6acb35c4e8fdc5e29d28679b9b2f3",
["0x049ce50bbadb6352574f2c59742f78df83333975cbd5cbb151c6e8628749a33dc1fa93bb6dffae5994e3eb98ae859ed55ee82937538e6adb054d780d1e89ff140f121529eeadb1161562af9d3342db0008919ca280a064305e5a4e518e93279de7a9396fe5136a9658e337e8e276221248c381c5384cd1ad28e5921f46ff058d5fbcf8a388fc881d0dd29421c218d51761"],
"0x2ddec1f96229efa2916988d8b2a82a47ef36f71c"
], "id": 1}"#;
let decryption_response = io.handle_request_sync(&decryption_request).unwrap();
assert_eq!(decryption_response, r#"{"jsonrpc":"2.0","result":"0xdeadbeef","id":1}"#);
}

View File

@ -28,6 +28,7 @@ pub mod personal;
pub mod signer;
pub mod traces;
pub mod rpc;
pub mod secretstore;
pub use self::web3::Web3;
pub use self::eth::{Eth, EthFilter};
@ -41,4 +42,4 @@ pub use self::personal::Personal;
pub use self::signer::Signer;
pub use self::traces::Traces;
pub use self::rpc::Rpc;
pub use self::secretstore::SecretStore;

View File

@ -0,0 +1,41 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! SecretStore-specific rpc interface.
use jsonrpc_core::Error;
use v1::types::{H160, H512, Bytes};
build_rpc_trait! {
/// Parity-specific rpc interface.
pub trait SecretStore {
/// Encrypt data with key, received from secret store.
/// Arguments: `account`, `password`, `key`, `data`.
#[rpc(name = "secretstore_encrypt")]
fn encrypt(&self, H160, String, Bytes, Bytes) -> Result<Bytes, Error>;
/// Decrypt data with key, received from secret store.
/// Arguments: `account`, `password`, `key`, `data`.
#[rpc(name = "secretstore_decrypt")]
fn decrypt(&self, H160, String, Bytes, Bytes) -> Result<Bytes, Error>;
/// Decrypt data with shadow key, received from secret store.
/// Arguments: `account`, `password`, `decrypted_secret`, `common_point`, `decrypt_shadows`, `data`.
#[rpc(name = "secretstore_shadowDecrypt")]
fn shadow_decrypt(&self, H160, String, H512, H512, Vec<Bytes>, Bytes) -> Result<Bytes, Error>;
}
}

View File

@ -24,9 +24,8 @@ use hyper::server::{Server as HttpServer, Request as HttpRequest, Response as Ht
use serde_json;
use url::percent_encoding::percent_decode;
use util::ToPretty;
use traits::KeyServer;
use serialization::SerializableDocumentEncryptedKeyShadow;
use serialization::{SerializableDocumentEncryptedKeyShadow, SerializableBytes};
use types::all::{Error, NodeAddress, RequestSignature, DocumentAddress, DocumentEncryptedKey, DocumentEncryptedKeyShadow};
/// Key server http-requests listener
@ -168,9 +167,10 @@ impl<T> HttpHandler for KeyServerHttpHandler<T> where T: KeyServer + 'static {
}
fn return_document_key(req: HttpRequest, mut res: HttpResponse, document_key: Result<DocumentEncryptedKey, Error>) {
let document_key = document_key.
and_then(|k| serde_json::to_vec(&SerializableBytes(k)).map_err(|e| Error::Serde(e.to_string())));
match document_key {
Ok(document_key) => {
let document_key = document_key.to_hex().into_bytes();
res.headers_mut().set(header::ContentType::plaintext());
if let Err(err) = res.send(&document_key) {
// nothing to do, but to log an error
@ -186,6 +186,7 @@ fn return_error(mut res: HttpResponse, err: Error) {
Error::BadSignature => *res.status_mut() = HttpStatusCode::BadRequest,
Error::AccessDenied => *res.status_mut() = HttpStatusCode::Forbidden,
Error::DocumentNotFound => *res.status_mut() = HttpStatusCode::NotFound,
Error::Serde(_) => *res.status_mut() = HttpStatusCode::BadRequest,
Error::Database(_) => *res.status_mut() = HttpStatusCode::InternalServerError,
Error::Internal(_) => *res.status_mut() = HttpStatusCode::InternalServerError,
}

View File

@ -67,7 +67,7 @@ impl KeyServer for KeyServerImpl {
let document_key = encryption_session.wait(None)?;
// encrypt document key with requestor public key
let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key)
let document_key = ethcrypto::ecies::encrypt(&public, &ethcrypto::DEFAULT_MAC, &document_key)
.map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?;
Ok(document_key)
}
@ -83,13 +83,13 @@ impl KeyServer for KeyServerImpl {
let document_key = decryption_session.wait()?.decrypted_secret;
// encrypt document key with requestor public key
let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key)
let document_key = ethcrypto::ecies::encrypt(&public, &ethcrypto::DEFAULT_MAC, &document_key)
.map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?;
Ok(document_key)
}
fn document_key_shadow(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKeyShadow, Error> {
let decryption_session = self.data.lock().cluster.new_decryption_session(document.clone(), signature.clone(), false)?;
let decryption_session = self.data.lock().cluster.new_decryption_session(document.clone(), signature.clone(), true)?;
decryption_session.wait().map_err(Into::into)
}
}
@ -118,7 +118,7 @@ impl KeyServerCore {
return;
},
};
let cluster = ClusterCore::new(el.handle(), config);
let cluster_client = cluster.and_then(|c| c.run().map(|_| c.client()));
tx.send(cluster_client.map_err(Into::into)).expect("Rx is blocking upper thread.");
@ -229,12 +229,12 @@ pub mod tests {
let secret = Random.generate().unwrap().secret().clone();
let signature = ethkey::sign(&secret, &document).unwrap();
let generated_key = key_servers[0].generate_document_key(&signature, &document, threshold).unwrap();
let generated_key = ethcrypto::ecies::decrypt_single_message(&secret, &generated_key).unwrap();
let generated_key = ethcrypto::ecies::decrypt(&secret, &ethcrypto::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.document_key(&signature, &document).unwrap();
let retrieved_key = ethcrypto::ecies::decrypt_single_message(&secret, &retrieved_key).unwrap();
let retrieved_key = ethcrypto::ecies::decrypt(&secret, &ethcrypto::DEFAULT_MAC, &retrieved_key).unwrap();
assert_eq!(retrieved_key, generated_key);
}
}
@ -251,12 +251,12 @@ pub mod tests {
let secret = Random.generate().unwrap().secret().clone();
let signature = ethkey::sign(&secret, &document).unwrap();
let generated_key = key_servers[0].generate_document_key(&signature, &document, *threshold).unwrap();
let generated_key = ethcrypto::ecies::decrypt_single_message(&secret, &generated_key).unwrap();
let generated_key = ethcrypto::ecies::decrypt(&secret, &ethcrypto::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.document_key(&signature, &document).unwrap();
let retrieved_key = ethcrypto::ecies::decrypt_single_message(&secret, &retrieved_key).unwrap();
let retrieved_key = ethcrypto::ecies::decrypt(&secret, &ethcrypto::DEFAULT_MAC, &retrieved_key).unwrap();
assert_eq!(retrieved_key, generated_key);
}
}

View File

@ -18,7 +18,8 @@ use std::cmp::{Ord, PartialOrd, Ordering};
use std::collections::{BTreeSet, BTreeMap};
use std::sync::Arc;
use parking_lot::{Mutex, Condvar};
use ethcrypto::ecies::encrypt_single_message;
use ethcrypto::ecies::encrypt;
use ethcrypto::DEFAULT_MAC;
use ethkey::{self, Secret, Public, Signature};
use key_server_cluster::{Error, AclStorage, DocumentKeyShare, NodeId, SessionId, DocumentEncryptedKeyShadow};
use key_server_cluster::cluster::Cluster;
@ -688,7 +689,7 @@ fn do_partial_decryption(node: &NodeId, requestor_public: &Public, is_shadow_dec
shadow_point: shadow_point,
decrypt_shadow: match decrypt_shadow {
None => None,
Some(decrypt_shadow) => Some(encrypt_single_message(requestor_public, &**decrypt_shadow)?),
Some(decrypt_shadow) => Some(encrypt(requestor_public, &DEFAULT_MAC, &**decrypt_shadow)?),
},
})
}
@ -1085,9 +1086,10 @@ mod tests {
assert!(decrypted_secret.common_point.is_some());
assert!(decrypted_secret.decrypt_shadows.is_some());
// check that KS client is able to restore original secret
use ethcrypto::ecies::decrypt_single_message;
use ethcrypto::DEFAULT_MAC;
use ethcrypto::ecies::decrypt;
let decrypt_shadows: Vec<_> = decrypted_secret.decrypt_shadows.unwrap().into_iter()
.map(|c| Secret::from_slice(&decrypt_single_message(key_pair.secret(), &c).unwrap()).unwrap())
.map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap()).unwrap())
.collect();
let decrypted_secret = math::decrypt_with_shadow_coefficients(decrypted_secret.decrypted_secret, decrypted_secret.common_point.unwrap(), decrypt_shadows).unwrap();
assert_eq!(decrypted_secret, SECRET_PLAIN.into());

View File

@ -34,9 +34,9 @@ pub struct SerializableDocumentEncryptedKeyShadow {
pub decrypt_shadows: Vec<SerializableBytes>,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
/// Serializable Bytes.
pub struct SerializableBytes(Bytes);
pub struct SerializableBytes(pub Bytes);
impl<T> From<T> for SerializableBytes where Bytes: From<T> {
fn from(s: T) -> SerializableBytes {
@ -60,7 +60,9 @@ impl Deref for SerializableBytes {
impl Serialize for SerializableBytes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(&(*self.0).to_hex())
let mut serialized = "0x".to_owned();
serialized.push_str(self.0.to_hex().as_ref());
serializer.serialize_str(serialized.as_ref())
}
}
@ -69,14 +71,18 @@ impl Deserialize for SerializableBytes {
where D: Deserializer
{
let s = String::deserialize(deserializer)?;
let data = s.from_hex().map_err(SerdeError::custom)?;
Ok(SerializableBytes(data))
if s.len() >= 2 && &s[0..2] == "0x" && s.len() & 1 == 0 {
let data = s[2..].from_hex().map_err(SerdeError::custom)?;
Ok(SerializableBytes(data))
} else {
Err(SerdeError::custom("invalid format"))
}
}
}
#[derive(Clone, Debug)]
/// Serializable Signature.
pub struct SerializableSignature(Signature);
pub struct SerializableSignature(pub Signature);
impl<T> From<T> for SerializableSignature where Signature: From<T> {
fn from(s: T) -> SerializableSignature {
@ -100,7 +106,9 @@ impl Deref for SerializableSignature {
impl Serialize for SerializableSignature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(&(*self.0).to_hex())
let mut serialized = "0x".to_owned();
serialized.push_str(self.0.to_hex().as_ref());
serializer.serialize_str(serialized.as_ref())
}
}
@ -116,7 +124,11 @@ impl Deserialize for SerializableSignature {
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
value.parse().map(|s| SerializableSignature(s)).map_err(SerdeError::custom)
if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 {
value[2..].parse().map(|s| SerializableSignature(s)).map_err(SerdeError::custom)
} else {
Err(SerdeError::custom("invalid format"))
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
@ -130,7 +142,7 @@ impl Deserialize for SerializableSignature {
#[derive(Clone, Debug)]
/// Serializable H256.
pub struct SerializableH256(H256);
pub struct SerializableH256(pub H256);
impl<T> From<T> for SerializableH256 where H256: From<T> {
fn from(s: T) -> SerializableH256 {
@ -154,7 +166,9 @@ impl Deref for SerializableH256 {
impl Serialize for SerializableH256 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(&(*self.0).to_hex())
let mut serialized = "0x".to_owned();
serialized.push_str(self.0.to_hex().as_ref());
serializer.serialize_str(serialized.as_ref())
}
}
@ -170,7 +184,11 @@ impl Deserialize for SerializableH256 {
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
value.parse().map(|s| SerializableH256(s)).map_err(SerdeError::custom)
if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 {
value[2..].parse().map(|s| SerializableH256(s)).map_err(SerdeError::custom)
} else {
Err(SerdeError::custom("invalid format"))
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
@ -184,7 +202,7 @@ impl Deserialize for SerializableH256 {
#[derive(Clone, Debug)]
/// Serializable EC scalar/secret key.
pub struct SerializableSecret(Secret);
pub struct SerializableSecret(pub Secret);
impl<T> From<T> for SerializableSecret where Secret: From<T> {
fn from(s: T) -> SerializableSecret {
@ -208,7 +226,9 @@ impl Deref for SerializableSecret {
impl Serialize for SerializableSecret {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(&(*self.0).to_hex())
let mut serialized = "0x".to_owned();
serialized.push_str(self.0.to_hex().as_ref());
serializer.serialize_str(serialized.as_ref())
}
}
@ -224,7 +244,11 @@ impl Deserialize for SerializableSecret {
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
value.parse().map(|s| SerializableSecret(s)).map_err(SerdeError::custom)
if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 {
value[2..].parse().map(|s| SerializableSecret(s)).map_err(SerdeError::custom)
} else {
Err(SerdeError::custom("invalid format"))
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
@ -238,7 +262,7 @@ impl Deserialize for SerializableSecret {
#[derive(Clone, Debug)]
/// Serializable EC point/public key.
pub struct SerializablePublic(Public);
pub struct SerializablePublic(pub Public);
impl<T> From<T> for SerializablePublic where Public: From<T> {
fn from(p: T) -> SerializablePublic {
@ -282,7 +306,9 @@ impl PartialOrd for SerializablePublic {
impl Serialize for SerializablePublic {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(&(*self.0).to_hex())
let mut serialized = "0x".to_owned();
serialized.push_str(self.0.to_hex().as_ref());
serializer.serialize_str(serialized.as_ref())
}
}
@ -298,7 +324,11 @@ impl Deserialize for SerializablePublic {
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
value.parse().map(|s| SerializablePublic(s)).map_err(SerdeError::custom)
if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 {
value[2..].parse().map(|s| SerializablePublic(s)).map_err(SerdeError::custom)
} else {
Err(SerdeError::custom("invalid format"))
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: SerdeError {
@ -309,3 +339,27 @@ impl Deserialize for SerializablePublic {
deserializer.deserialize(HashVisitor)
}
}
#[cfg(test)]
mod tests {
use serde_json;
use super::{SerializableBytes, SerializablePublic};
#[test]
fn serialize_and_deserialize_bytes() {
let bytes = SerializableBytes(vec![1, 2, 3, 4]);
let bytes_serialized = serde_json::to_string(&bytes).unwrap();
assert_eq!(&bytes_serialized, r#""0x01020304""#);
let bytes_deserialized: SerializableBytes = serde_json::from_str(&bytes_serialized).unwrap();
assert_eq!(bytes_deserialized, bytes);
}
#[test]
fn serialize_and_deserialize_public() {
let public = SerializablePublic("cac6c205eb06c8308d65156ff6c862c62b000b8ead121a4455a8ddeff7248128d895692136f240d5d1614dc7cc4147b1bd584bd617e30560bb872064d09ea325".parse().unwrap());
let public_serialized = serde_json::to_string(&public).unwrap();
assert_eq!(&public_serialized, r#""0xcac6c205eb06c8308d65156ff6c862c62b000b8ead121a4455a8ddeff7248128d895692136f240d5d1614dc7cc4147b1bd584bd617e30560bb872064d09ea325""#);
let public_deserialized: SerializablePublic = serde_json::from_str(&public_serialized).unwrap();
assert_eq!(public_deserialized, public);
}
}

View File

@ -16,6 +16,7 @@
use std::fmt;
use std::collections::BTreeMap;
use serde_json;
use ethkey;
use util;
@ -44,6 +45,8 @@ pub enum Error {
AccessDenied,
/// Requested document not found
DocumentNotFound,
/// Serialization/deserialization error
Serde(String),
/// Database-related error
Database(String),
/// Internal error
@ -107,12 +110,19 @@ impl fmt::Display for Error {
Error::BadSignature => write!(f, "Bad signature"),
Error::AccessDenied => write!(f, "Access dened"),
Error::DocumentNotFound => write!(f, "Document not found"),
Error::Serde(ref msg) => write!(f, "Serialization error: {}", msg),
Error::Database(ref msg) => write!(f, "Database error: {}", msg),
Error::Internal(ref msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Serde(err.to_string())
}
}
impl From<ethkey::Error> for Error {
fn from(err: ethkey::Error) -> Self {
Error::Internal(err.into())