Secret store - initial version (#4567)

* initial secret store commit

* various fixes

* license

* (sstore, secstore) -> secretstore

* marked KeyServer trait as IPC-ready

* fixed style

* ignore requests with Origin header

* fixed tests

* fixed Origin header check
This commit is contained in:
Svyatoslav Nikolsky 2017-02-20 18:13:21 +03:00 committed by Gav Wood
parent 68a25d9e14
commit b9665c7cfe
23 changed files with 931 additions and 8 deletions

18
Cargo.lock generated
View File

@ -20,6 +20,7 @@ dependencies = [
"ethcore-light 1.6.0",
"ethcore-logger 1.6.0",
"ethcore-rpc 1.6.0",
"ethcore-secretstore 1.0.0",
"ethcore-signer 1.6.0",
"ethcore-stratum 1.6.0",
"ethcore-util 1.6.0",
@ -635,6 +636,23 @@ dependencies = [
"transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethcore-secretstore"
version = "1.0.0"
dependencies = [
"ethcore-devtools 1.6.0",
"ethcore-ipc 1.6.0",
"ethcore-ipc-codegen 1.6.0",
"ethcore-ipc-nano 1.6.0",
"ethcore-util 1.6.0",
"ethcrypto 0.1.0",
"ethkey 0.2.0",
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethcore-signer"
version = "1.6.0"

View File

@ -49,6 +49,7 @@ parity-updater = { path = "updater" }
parity-reactor = { path = "util/reactor" }
ethcore-dapps = { path = "dapps", optional = true }
clippy = { version = "0.0.103", optional = true}
ethcore-secretstore = { path = "secret_store", optional = true }
[dev-dependencies]
ethcore-ipc-tests = { path = "ipc/tests" }
@ -83,6 +84,7 @@ evm-debug = ["ethcore/evm-debug"]
evm-debug-tests = ["ethcore/evm-debug-tests"]
slow-blocks = ["ethcore/slow-blocks"]
final = ["ethcore-util/final"]
secretstore = ["ethcore-secretstore"]
[[bin]]
path = "parity/main.rs"

View File

@ -185,7 +185,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result<(), String> {
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?;
// create dirs used by parity
cmd.dirs.create_dirs(false, false)?;
cmd.dirs.create_dirs(false, false, false)?;
// prepare client config
let mut client_config = to_client_config(
@ -356,7 +356,7 @@ fn start_client(
execute_upgrades(&dirs.base, &db_dirs, algorithm, compaction.compaction_profile(db_dirs.db_root_path().as_path()))?;
// create dirs used by parity
dirs.create_dirs(false, false)?;
dirs.create_dirs(false, false, false)?;
// prepare client config
let client_config = to_client_config(

View File

@ -67,6 +67,12 @@ path = "$HOME/.parity/dapps"
user = "test_user"
pass = "test_pass"
[secretstore]
disable = false
port = 8082
interface = "local"
path = "$HOME/.parity/secretstore"
[ipfs]
enable = false
port = 5001

View File

@ -38,6 +38,9 @@ port = 8080
user = "username"
pass = "password"
[secretstore]
port = 8082
[ipfs]
enable = false
port = 5001

View File

@ -189,6 +189,16 @@ usage! {
or |c: &Config| otry!(c.dapps).pass.clone().map(Some),
flag_dapps_apis_all: bool = false, or |_| None,
// Secret Store
flag_no_secretstore: bool = false,
or |c: &Config| otry!(c.secretstore).disable.clone(),
flag_secretstore_port: u16 = 8082u16,
or |c: &Config| otry!(c.secretstore).port.clone(),
flag_secretstore_interface: String = "local",
or |c: &Config| otry!(c.secretstore).interface.clone(),
flag_secretstore_path: String = "$BASE/secretstore",
or |c: &Config| otry!(c.secretstore).path.clone(),
// IPFS
flag_ipfs_api: bool = false,
or |c: &Config| otry!(c.ipfs).enable.clone(),
@ -327,6 +337,7 @@ struct Config {
rpc: Option<Rpc>,
ipc: Option<Ipc>,
dapps: Option<Dapps>,
secretstore: Option<SecretStore>,
ipfs: Option<Ipfs>,
mining: Option<Mining>,
footprint: Option<Footprint>,
@ -416,6 +427,14 @@ struct Dapps {
pass: Option<String>,
}
#[derive(Default, Debug, PartialEq, RustcDecodable)]
struct SecretStore {
disable: Option<bool>,
port: Option<u16>,
interface: Option<String>,
path: Option<String>,
}
#[derive(Default, Debug, PartialEq, RustcDecodable)]
struct Ipfs {
enable: Option<bool>,
@ -495,7 +514,8 @@ struct Misc {
mod tests {
use super::{
Args, ArgsError,
Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint, Snapshots, VM, Misc
Config, Operating, Account, Ui, Network, Rpc, Ipc, Dapps, Ipfs, Mining, Footprint,
Snapshots, VM, Misc, SecretStore,
};
use toml;
@ -650,6 +670,11 @@ mod tests {
flag_dapps_pass: Some("test_pass".into()),
flag_dapps_apis_all: false,
flag_no_secretstore: false,
flag_secretstore_port: 8082u16,
flag_secretstore_interface: "local".into(),
flag_secretstore_path: "$HOME/.parity/secretstore".into(),
// IPFS
flag_ipfs_api: false,
flag_ipfs_api_port: 5001u16,
@ -839,6 +864,12 @@ mod tests {
user: Some("username".into()),
pass: Some("password".into())
}),
secretstore: Some(SecretStore {
disable: None,
port: Some(8082),
interface: None,
path: None,
}),
ipfs: Some(Ipfs {
enable: Some(false),
port: Some(5001)

View File

@ -179,6 +179,16 @@ API and Console Options:
--ipfs-api-port PORT Configure on which port the IPFS HTTP API should listen.
(default: {flag_ipfs_api_port})
Secret Store Options:
--no-secretstore Disable Secret Store functionality. (default: {flag_no_secretstore})
--secretstore-port PORT Specify the port portion for Secret Store Key Server
(default: {flag_secretstore_port}).
--secretstore-interface IP Specify the hostname portion for Secret Store Key Server, IP
should be an interface's IP address, or local
(default: {flag_secretstore_interface}).
--secretstore-path PATH Specify directory where Secret Store should save its data.
(default: {flag_secretstore_path})
Sealing/Mining Options:
--author ADDRESS Specify the block author (aka "coinbase") address
for sending block rewards from sealed blocks.

View File

@ -39,6 +39,7 @@ use dir::{self, Directories, default_hypervisor_path, default_local_path, defaul
use dapps::Configuration as DappsConfiguration;
use ipfs::Configuration as IpfsConfiguration;
use signer::{Configuration as SignerConfiguration};
use secretstore::Configuration as SecretStoreConfiguration;
use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack};
use run::RunCmd;
use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockchain, ExportState, DataFormat};
@ -121,6 +122,7 @@ impl Configuration {
let dapps_conf = self.dapps_config();
let ipfs_conf = self.ipfs_config();
let signer_conf = self.signer_config();
let secretstore_conf = self.secretstore_config();
let format = self.format()?;
let cmd = if self.args.flag_version {
@ -346,6 +348,7 @@ impl Configuration {
dapps_conf: dapps_conf,
ipfs_conf: ipfs_conf,
signer_conf: signer_conf,
secretstore_conf: secretstore_conf,
dapp: self.dapp_to_open()?,
ui: self.args.cmd_ui,
name: self.args.flag_identity,
@ -542,6 +545,15 @@ impl Configuration {
}
}
fn secretstore_config(&self) -> SecretStoreConfiguration {
SecretStoreConfiguration {
enabled: self.secretstore_enabled(),
interface: self.secretstore_interface(),
port: self.args.flag_secretstore_port,
data_path: self.directories().secretstore,
}
}
fn ipfs_config(&self) -> IpfsConfiguration {
IpfsConfiguration {
enabled: self.args.flag_ipfs_api,
@ -787,6 +799,7 @@ impl Configuration {
let db_path = replace_home_for_db(&data_path, &local_path, &base_db_path);
let keys_path = replace_home(&data_path, &self.args.flag_keys_path);
let dapps_path = replace_home(&data_path, &self.args.flag_dapps_path);
let secretstore_path = replace_home(&data_path, &self.args.flag_secretstore_path);
let ui_path = replace_home(&data_path, &self.args.flag_ui_path);
if self.args.flag_geth && !cfg!(windows) {
@ -810,6 +823,7 @@ impl Configuration {
db: db_path,
dapps: dapps_path,
signer: ui_path,
secretstore: secretstore_path,
}
}
@ -851,6 +865,13 @@ impl Configuration {
}.into()
}
fn secretstore_interface(&self) -> String {
match self.args.flag_secretstore_interface.as_str() {
"local" => "127.0.0.1",
x => x,
}.into()
}
fn stratum_interface(&self) -> String {
match self.args.flag_stratum_interface.as_str() {
"local" => "127.0.0.1",
@ -863,6 +884,10 @@ impl Configuration {
!self.args.flag_dapps_off && !self.args.flag_no_dapps && cfg!(feature = "dapps")
}
fn secretstore_enabled(&self) -> bool {
!self.args.flag_no_secretstore && cfg!(feature = "secretstore")
}
fn ui_enabled(&self) -> bool {
if self.args.flag_force_ui {
return true;
@ -1083,7 +1108,7 @@ mod tests {
fn test_run_cmd() {
let args = vec!["parity"];
let conf = parse(&args);
assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(RunCmd {
let mut expected = RunCmd {
cache_config: Default::default(),
dirs: Default::default(),
spec: Default::default(),
@ -1113,6 +1138,7 @@ mod tests {
dapps_conf: Default::default(),
ipfs_conf: Default::default(),
signer_conf: Default::default(),
secretstore_conf: Default::default(),
ui: false,
dapp: None,
name: "".into(),
@ -1123,7 +1149,9 @@ mod tests {
check_seal: true,
download_old_blocks: true,
verifier_settings: Default::default(),
}));
};
expected.secretstore_conf.enabled = cfg!(feature = "secretstore");
assert_eq!(conf.into_command().unwrap().cmd, Cmd::Run(expected));
}
#[test]

View File

@ -45,6 +45,7 @@ pub struct Directories {
pub keys: String,
pub signer: String,
pub dapps: String,
pub secretstore: String,
}
impl Default for Directories {
@ -57,12 +58,13 @@ impl Default for Directories {
keys: replace_home(&data_dir, "$BASE/keys"),
signer: replace_home(&data_dir, "$BASE/signer"),
dapps: replace_home(&data_dir, "$BASE/dapps"),
secretstore: replace_home(&data_dir, "$BASE/secretstore"),
}
}
}
impl Directories {
pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool) -> Result<(), String> {
pub fn create_dirs(&self, dapps_enabled: bool, signer_enabled: bool, secretstore_enabled: bool) -> Result<(), String> {
fs::create_dir_all(&self.base).map_err(|e| e.to_string())?;
fs::create_dir_all(&self.db).map_err(|e| e.to_string())?;
fs::create_dir_all(&self.keys).map_err(|e| e.to_string())?;
@ -72,6 +74,9 @@ impl Directories {
if dapps_enabled {
fs::create_dir_all(&self.dapps).map_err(|e| e.to_string())?;
}
if secretstore_enabled {
fs::create_dir_all(&self.secretstore).map_err(|e| e.to_string())?;
}
Ok(())
}
@ -241,6 +246,7 @@ mod tests {
keys: replace_home(&data_dir, "$BASE/keys"),
signer: replace_home(&data_dir, "$BASE/signer"),
dapps: replace_home(&data_dir, "$BASE/dapps"),
secretstore: replace_home(&data_dir, "$BASE/secretstore"),
};
assert_eq!(expected, Directories::default());
}

View File

@ -66,6 +66,10 @@ extern crate log as rlog;
#[cfg(feature="stratum")]
extern crate ethcore_stratum;
#[cfg(feature="secretstore")]
extern crate ethcore_secretstore;
#[cfg(feature = "dapps")]
extern crate ethcore_dapps;
@ -101,6 +105,7 @@ mod rpc_apis;
mod run;
mod signer;
mod snapshot;
mod secretstore;
mod upgrade;
mod url;
mod user_defaults;

View File

@ -49,6 +49,7 @@ use user_defaults::UserDefaults;
use dapps;
use ipfs;
use signer;
use secretstore;
use modules;
use rpc_apis;
use rpc;
@ -96,6 +97,7 @@ pub struct RunCmd {
pub dapps_conf: dapps::Configuration,
pub ipfs_conf: ipfs::Configuration,
pub signer_conf: signer::Configuration,
pub secretstore_conf: secretstore::Configuration,
pub dapp: Option<String>,
pub ui: bool,
pub name: String,
@ -190,7 +192,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.db_root_path().as_path()))?;
// create dirs used by parity
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled)?;
cmd.dirs.create_dirs(cmd.dapps_conf.enabled, cmd.signer_conf.enabled, cmd.secretstore_conf.enabled)?;
// run in daemon mode
if let Some(pid_file) = cmd.daemon {
@ -422,6 +424,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
};
let signer_server = signer::start(cmd.signer_conf.clone(), signer_deps)?;
// secret store key server
let secretstore_deps = secretstore::Dependencies { };
let secretstore_key_server = secretstore::start(cmd.secretstore_conf.clone(), secretstore_deps);
// the ipfs server
let ipfs_server = match cmd.ipfs_conf.enabled {
true => Some(ipfs::start_server(cmd.ipfs_conf.port, client.clone())?),
@ -484,7 +490,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
let restart = wait_for_exit(panic_handler, Some(updater), can_restart);
// drop this stuff as soon as exit detected.
drop((http_server, ipc_server, dapps_server, signer_server, ipfs_server, event_loop));
drop((http_server, ipc_server, dapps_server, signer_server, secretstore_key_server, ipfs_server, event_loop));
info!("Finishing work, please wait...");

105
parity/secretstore.rs Normal file
View File

@ -0,0 +1,105 @@
// 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 dir::default_data_path;
use helpers::replace_home;
#[derive(Debug, PartialEq, Clone)]
/// Secret store configuration
pub struct Configuration {
/// Is secret store functionality enabled?
pub enabled: bool,
/// Interface to listen to
pub interface: String,
/// Port to listen to
pub port: u16,
/// Data directory path for secret store
pub data_path: String,
}
#[derive(Debug, PartialEq, Clone)]
/// Secret store dependencies
pub struct Dependencies {
// the only dependency will be BlockChainClient
}
#[cfg(not(feature = "secretstore"))]
mod server {
use super::{Configuration, Dependencies};
/// Noop key server implementation
pub struct KeyServer;
impl KeyServer {
/// Create new noop key server
pub fn new(_conf: Configuration, _deps: Dependencies) -> Result<Self, String> {
Ok(KeyServer)
}
}
}
#[cfg(feature="secretstore")]
mod server {
use ethcore_secretstore;
use super::{Configuration, Dependencies};
/// Key server
pub struct KeyServer {
_key_server: Box<ethcore_secretstore::KeyServer>,
}
impl KeyServer {
/// Create new key server
pub fn new(conf: Configuration, _deps: Dependencies) -> Result<Self, String> {
let conf = ethcore_secretstore::ServiceConfiguration {
listener_addr: conf.interface,
listener_port: conf.port,
data_path: conf.data_path,
};
let key_server = ethcore_secretstore::start(conf)
.map_err(Into::<String>::into)?;
Ok(KeyServer {
_key_server: key_server,
})
}
}
}
pub use self::server::KeyServer;
impl Default for Configuration {
fn default() -> Self {
let data_dir = default_data_path();
Configuration {
enabled: true,
interface: "127.0.0.1".to_owned(),
port: 8082,
data_path: replace_home(&data_dir, "$BASE/secretstore"),
}
}
}
/// Start secret store-related functionality
pub fn start(conf: Configuration, deps: Dependencies) -> Result<Option<KeyServer>, String> {
if !conf.enabled {
return Ok(None);
}
KeyServer::new(conf, deps)
.map(|s| Some(s))
}

25
secret_store/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
description = "Ethcore Secret Store"
name = "ethcore-secretstore"
version = "1.0.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
[build-dependencies]
ethcore-ipc-codegen = { path = "../ipc/codegen" }
[dependencies]
log = "0.3"
parking_lot = "0.3"
hyper = { version = "0.9", default-features = false }
url = "1.0"
ethcore-devtools = { path = "../devtools" }
ethcore-util = { path = "../util" }
ethcore-ipc = { path = "../ipc/rpc" }
ethcore-ipc-nano = { path = "../ipc/nano" }
ethcrypto = { path = "../ethcrypto" }
ethkey = { path = "../ethkey" }
[profile.release]
debug = true

22
secret_store/build.rs Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate ethcore_ipc_codegen;
fn main() {
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
ethcore_ipc_codegen::derive_ipc_cond("src/traits.rs", cfg!(feature="ipc")).unwrap();
}

View File

@ -0,0 +1,51 @@
// 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::collections::{HashMap, HashSet};
use parking_lot::RwLock;
use types::all::{Error, DocumentAddress, Public};
/// ACL storage of Secret Store
pub trait AclStorage: Send + Sync {
/// Check if requestor with `public` key can access document with hash `document`
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error>;
}
/// Dummy ACL storage implementation
#[derive(Default, Debug)]
pub struct DummyAclStorage {
prohibited: RwLock<HashMap<Public, HashSet<DocumentAddress>>>,
}
impl DummyAclStorage {
#[cfg(test)]
/// Prohibit given requestor access to given document
pub fn prohibit(&self, public: Public, document: DocumentAddress) {
self.prohibited.write()
.entry(public)
.or_insert_with(Default::default)
.insert(document);
}
}
impl AclStorage for DummyAclStorage {
fn check(&self, public: &Public, document: &DocumentAddress) -> Result<bool, Error> {
Ok(self.prohibited.read()
.get(public)
.map(|docs| !docs.contains(document))
.unwrap_or(true))
}
}

View File

@ -0,0 +1,176 @@
// 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::str::FromStr;
use std::sync::Arc;
use hyper::header;
use hyper::uri::RequestUri;
use hyper::method::Method as HttpMethod;
use hyper::status::StatusCode as HttpStatusCode;
use hyper::server::{Server as HttpServer, Request as HttpRequest, Response as HttpResponse, Handler as HttpHandler,
Listening as HttpListening};
use url::percent_encoding::percent_decode;
use util::ToPretty;
use traits::KeyServer;
use types::all::{Error, ServiceConfiguration, RequestSignature, DocumentAddress, DocumentEncryptedKey};
/// Key server http-requests listener
pub struct KeyServerHttpListener<T: KeyServer + 'static> {
_http_server: HttpListening,
handler: Arc<KeyServerSharedHttpHandler<T>>,
}
/// Parsed http request
#[derive(Debug, Clone, PartialEq)]
enum Request {
/// Invalid request
Invalid,
/// Request encryption key of given document for given requestor
GetDocumentKey(DocumentAddress, RequestSignature),
}
/// Cloneable http handler
struct KeyServerHttpHandler<T: KeyServer + 'static> {
handler: Arc<KeyServerSharedHttpHandler<T>>,
}
/// Shared http handler
struct KeyServerSharedHttpHandler<T: KeyServer + 'static> {
key_server: T,
}
impl<T> KeyServerHttpListener<T> where T: KeyServer + 'static {
/// Start KeyServer http listener
pub fn start(config: ServiceConfiguration, key_server: T) -> Result<Self, Error> {
let shared_handler = Arc::new(KeyServerSharedHttpHandler {
key_server: key_server,
});
let handler = KeyServerHttpHandler {
handler: shared_handler.clone(),
};
let listener_addr: &str = &format!("{}:{}", config.listener_addr, config.listener_port);
let http_server = HttpServer::http(&listener_addr).unwrap();
let http_server = http_server.handle(handler).unwrap();
let listener = KeyServerHttpListener {
_http_server: http_server,
handler: shared_handler,
};
Ok(listener)
}
}
impl<T> KeyServer for KeyServerHttpListener<T> where T: KeyServer + 'static {
fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKey, Error> {
self.handler.key_server.document_key(signature, document)
}
}
impl<T> HttpHandler for KeyServerHttpHandler<T> where T: KeyServer + 'static {
fn handle(&self, req: HttpRequest, mut res: HttpResponse) {
if req.method != HttpMethod::Get {
warn!(target: "secretstore", "Ignoring {}-request {}", req.method, req.uri);
*res.status_mut() = HttpStatusCode::NotFound;
return;
}
if req.headers.has::<header::Origin>() {
warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method, req.uri);
*res.status_mut() = HttpStatusCode::NotFound;
return;
}
match req.uri {
RequestUri::AbsolutePath(ref path) => match parse_request(&path) {
Request::GetDocumentKey(document, signature) => {
let document_key = self.handler.key_server.document_key(&signature, &document)
.map_err(|err| {
warn!(target: "secretstore", "GetDocumentKey request {} has failed with: {}", req.uri, err);
err
});
match document_key {
Ok(document_key) => {
let document_key = document_key.to_hex().into_bytes();
res.headers_mut().set(header::ContentType::plaintext());
if let Err(err) = res.send(&document_key) {
// nothing to do, but log error
warn!(target: "secretstore", "GetDocumentKey request {} response has failed with: {}", req.uri, err);
}
},
Err(Error::BadSignature) => *res.status_mut() = HttpStatusCode::BadRequest,
Err(Error::AccessDenied) => *res.status_mut() = HttpStatusCode::Forbidden,
Err(Error::DocumentNotFound) => *res.status_mut() = HttpStatusCode::NotFound,
Err(Error::Database(_)) => *res.status_mut() = HttpStatusCode::InternalServerError,
Err(Error::Internal(_)) => *res.status_mut() = HttpStatusCode::InternalServerError,
}
},
Request::Invalid => {
warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri);
*res.status_mut() = HttpStatusCode::BadRequest;
},
},
_ => {
warn!(target: "secretstore", "Ignoring invalid {}-request {}", req.method, req.uri);
*res.status_mut() = HttpStatusCode::NotFound;
},
};
}
}
fn parse_request(uri_path: &str) -> Request {
let uri_path = match percent_decode(uri_path.as_bytes()).decode_utf8() {
Ok(path) => path,
Err(_) => return Request::Invalid,
};
let path: Vec<String> = uri_path.trim_left_matches('/').split('/').map(Into::into).collect();
if path.len() != 2 || path[0].is_empty() || path[1].is_empty() {
return Request::Invalid;
}
let document = DocumentAddress::from_str(&path[0]);
let signature = RequestSignature::from_str(&path[1]);
match (document, signature) {
(Ok(document), Ok(signature)) => Request::GetDocumentKey(document, signature),
_ => Request::Invalid,
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::super::RequestSignature;
use super::{parse_request, Request};
#[test]
fn parse_request_successful() {
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"),
Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(),
RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap()));
assert_eq!(parse_request("/%30000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01"),
Request::GetDocumentKey("0000000000000000000000000000000000000000000000000000000000000001".into(),
RequestSignature::from_str("a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01").unwrap()));
}
#[test]
fn parse_request_failed() {
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001"), Request::Invalid);
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/"), Request::Invalid);
assert_eq!(parse_request("/a/b"), Request::Invalid);
assert_eq!(parse_request("/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01/0000000000000000000000000000000000000000000000000000000000000002"), Request::Invalid);
}
}

View File

@ -0,0 +1,124 @@
// 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 ethcrypto;
use ethkey;
use super::acl_storage::AclStorage;
use super::key_storage::KeyStorage;
use traits::KeyServer;
use types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey};
/// Secret store key server implementation
pub struct KeyServerImpl<T: AclStorage, U: KeyStorage> {
acl_storage: T,
key_storage: U,
}
impl<T, U> KeyServerImpl<T, U> where T: AclStorage, U: KeyStorage {
/// Create new key server instance
pub fn new(acl_storage: T, key_storage: U) -> Self {
KeyServerImpl {
acl_storage: acl_storage,
key_storage: key_storage,
}
}
}
impl<T, U> KeyServer for KeyServerImpl<T, U> where T: AclStorage, U: KeyStorage {
fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKey, Error> {
// recover requestor' public key from signature
let public = ethkey::recover(signature, document)
.map_err(|_| Error::BadSignature)?;
// check that requestor has access to the document
if !self.acl_storage.check(&public, document)? {
return Err(Error::AccessDenied);
}
// read unencrypted document key
let document_key = self.key_storage.get(document)?;
// encrypt document key with requestor public key
let document_key = ethcrypto::ecies::encrypt_single_message(&public, &document_key)
.map_err(|err| Error::Internal(format!("Error encrypting document key: {}", err)))?;
Ok(document_key)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use ethcrypto;
use ethkey::{self, Secret};
use acl_storage::DummyAclStorage;
use key_storage::KeyStorage;
use key_storage::tests::DummyKeyStorage;
use super::super::{Error, RequestSignature, DocumentAddress};
use super::{KeyServer, KeyServerImpl};
const DOCUMENT1: &'static str = "0000000000000000000000000000000000000000000000000000000000000001";
const DOCUMENT2: &'static str = "0000000000000000000000000000000000000000000000000000000000000002";
const KEY1: &'static str = "key1";
const PRIVATE1: &'static str = "03055e18a8434dcc9061cc1b81c4ef84dc7cf4574d755e52cdcf0c8898b25b11";
const PUBLIC2: &'static str = "dfe62f56bb05fbd85b485bac749f3410309e24b352bac082468ce151e9ddb94fa7b5b730027fe1c7c5f3d5927621d269f91aceb5caa3c7fe944677a22f88a318";
const PRIVATE2: &'static str = "0eb3816f4f705fa0fd952fb27b71b8c0606f09f4743b5b65cbc375bd569632f2";
fn create_key_server() -> KeyServerImpl<DummyAclStorage, DummyKeyStorage> {
let acl_storage = DummyAclStorage::default();
let key_storage = DummyKeyStorage::default();
key_storage.insert(DOCUMENT1.into(), KEY1.into()).unwrap();
acl_storage.prohibit(PUBLIC2.into(), DOCUMENT1.into());
KeyServerImpl::new(acl_storage, key_storage)
}
fn make_signature(secret: &str, document: &'static str) -> RequestSignature {
let secret = Secret::from_str(secret).unwrap();
let document: DocumentAddress = document.into();
ethkey::sign(&secret, &document).unwrap()
}
#[test]
fn document_key_succeeds() {
let key_server = create_key_server();
let signature = make_signature(PRIVATE1, DOCUMENT1);
let document_key = key_server.document_key(&signature, &DOCUMENT1.into()).unwrap();
let document_key = ethcrypto::ecies::decrypt_single_message(&Secret::from_str(PRIVATE1).unwrap(), &document_key);
assert_eq!(document_key, Ok(KEY1.into()));
}
#[test]
fn document_key_fails_when_bad_signature() {
let key_server = create_key_server();
let signature = RequestSignature::default();
let document_key = key_server.document_key(&signature, &DOCUMENT1.into());
assert_eq!(document_key, Err(Error::BadSignature));
}
#[test]
fn document_key_fails_when_acl_check_fails() {
let key_server = create_key_server();
let signature = make_signature(PRIVATE2, DOCUMENT1);
let document_key = key_server.document_key(&signature, &DOCUMENT1.into());
assert_eq!(document_key, Err(Error::AccessDenied));
}
#[test]
fn document_key_fails_when_document_not_found() {
let key_server = create_key_server();
let signature = make_signature(PRIVATE1, DOCUMENT2);
let document_key = key_server.document_key(&signature, &DOCUMENT2.into());
assert_eq!(document_key, Err(Error::DocumentNotFound));
}
}

View File

@ -0,0 +1,115 @@
// 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::path::PathBuf;
use util::Database;
use types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey};
/// Document encryption keys storage
pub trait KeyStorage: Send + Sync {
/// Insert document encryption key
fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error>;
/// Get document encryption key
fn get(&self, document: &DocumentAddress) -> Result<DocumentKey, Error>;
}
/// Persistent document encryption keys storage
pub struct PersistentKeyStorage {
db: Database,
}
impl PersistentKeyStorage {
/// Create new persistent document encryption keys storage
pub fn new(config: &ServiceConfiguration) -> Result<Self, Error> {
let mut db_path = PathBuf::from(&config.data_path);
db_path.push("db");
let db_path = db_path.to_str().ok_or(Error::Database("Invalid secretstore path".to_owned()))?;
Ok(PersistentKeyStorage {
db: Database::open_default(&db_path).map_err(Error::Database)?,
})
}
}
impl KeyStorage for PersistentKeyStorage {
fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> {
let mut batch = self.db.transaction();
batch.put(None, &document, &key);
self.db.write(batch).map_err(Error::Database)
}
fn get(&self, document: &DocumentAddress) -> Result<DocumentKey, Error> {
self.db.get(None, document)
.map_err(Error::Database)?
.ok_or(Error::DocumentNotFound)
.map(|key| key.to_vec())
}
}
#[cfg(test)]
pub mod tests {
use std::collections::HashMap;
use parking_lot::RwLock;
use devtools::RandomTempPath;
use super::super::types::all::{Error, ServiceConfiguration, DocumentAddress, DocumentKey};
use super::{KeyStorage, PersistentKeyStorage};
#[derive(Default)]
/// In-memory document encryption keys storage
pub struct DummyKeyStorage {
keys: RwLock<HashMap<DocumentAddress, DocumentKey>>,
}
impl KeyStorage for DummyKeyStorage {
fn insert(&self, document: DocumentAddress, key: DocumentKey) -> Result<(), Error> {
self.keys.write().insert(document, key);
Ok(())
}
fn get(&self, document: &DocumentAddress) -> Result<DocumentKey, Error> {
self.keys.read().get(document).cloned().ok_or(Error::DocumentNotFound)
}
}
#[test]
fn persistent_key_storage() {
let path = RandomTempPath::create_dir();
let config = ServiceConfiguration {
listener_addr: "0.0.0.0".to_owned(),
listener_port: 8082,
data_path: path.as_str().to_owned(),
};
let key1 = DocumentAddress::from(1);
let value1: DocumentKey = vec![0x77, 0x88];
let key2 = DocumentAddress::from(2);
let value2: DocumentKey = vec![0x11, 0x22];
let key3 = DocumentAddress::from(3);
let key_storage = PersistentKeyStorage::new(&config).unwrap();
key_storage.insert(key1.clone(), value1.clone()).unwrap();
key_storage.insert(key2.clone(), value2.clone()).unwrap();
assert_eq!(key_storage.get(&key1), Ok(value1.clone()));
assert_eq!(key_storage.get(&key2), Ok(value2.clone()));
assert_eq!(key_storage.get(&key3), Err(Error::DocumentNotFound));
drop(key_storage);
let key_storage = PersistentKeyStorage::new(&config).unwrap();
assert_eq!(key_storage.get(&key1), Ok(value1));
assert_eq!(key_storage.get(&key2), Ok(value2));
assert_eq!(key_storage.get(&key3), Err(Error::DocumentNotFound));
}
}

52
secret_store/src/lib.rs Normal file
View File

@ -0,0 +1,52 @@
// 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/>.
#[macro_use]
extern crate log;
extern crate hyper;
extern crate parking_lot;
extern crate url;
extern crate ethcore_devtools as devtools;
extern crate ethcore_util as util;
extern crate ethcore_ipc as ipc;
extern crate ethcrypto;
extern crate ethkey;
mod types;
mod traits {
#![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/traits.rs"));
}
mod acl_storage;
mod http_listener;
mod key_server;
mod key_storage;
pub use types::all::{DocumentAddress, DocumentKey, DocumentEncryptedKey, RequestSignature, Public,
Error, ServiceConfiguration};
pub use traits::{KeyServer};
/// Start new key server instance
pub fn start(config: ServiceConfiguration) -> Result<Box<KeyServer>, Error> {
let acl_storage = acl_storage::DummyAclStorage::default();
let key_storage = key_storage::PersistentKeyStorage::new(&config)?;
let key_server = key_server::KeyServerImpl::new(acl_storage, key_storage);
let listener = http_listener::KeyServerHttpListener::start(config, key_server)?;
Ok(Box::new(listener))
}

View File

@ -0,0 +1,24 @@
// 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 types::all::{Error, RequestSignature, DocumentAddress, DocumentEncryptedKey};
#[ipc(client_ident="RemoteKeyServer")]
/// Secret store key server
pub trait KeyServer: Send + Sync {
/// Request encryption key of given document for given requestor
fn document_key(&self, signature: &RequestSignature, document: &DocumentAddress) -> Result<DocumentEncryptedKey, Error>;
}

View File

@ -0,0 +1,77 @@
// 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::fmt;
use ethkey;
use util;
/// Document address type.
pub type DocumentAddress = util::H256;
/// Document key type.
pub type DocumentKey = util::Bytes;
/// Encrypted key type.
pub type DocumentEncryptedKey = util::Bytes;
/// Request signature type.
pub type RequestSignature = ethkey::Signature;
/// Public key type.
pub use ethkey::Public;
#[derive(Debug, Clone, PartialEq)]
#[binary]
/// Secret store error
pub enum Error {
/// Bad signature is passed
BadSignature,
/// Access to resource is denied
AccessDenied,
/// Requested document not found
DocumentNotFound,
/// Database-related error
Database(String),
/// Internal error
Internal(String),
}
#[derive(Debug)]
#[binary]
/// Secret store configuration
pub struct ServiceConfiguration {
/// Interface to listen to
pub listener_addr: String,
/// Port to listen to
pub listener_port: u16,
/// Data directory path for secret store
pub data_path: String,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Error::BadSignature => write!(f, "Bad signature"),
Error::AccessDenied => write!(f, "Access dened"),
Error::DocumentNotFound => write!(f, "Document not found"),
Error::Database(ref msg) => write!(f, "Database error: {}", msg),
Error::Internal(ref msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl Into<String> for Error {
fn into(self) -> String {
format!("{}", self)
}
}

View File

@ -0,0 +1,20 @@
// 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/>.
//! Types used in the public api
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/mod.rs.in"));

View File

@ -0,0 +1,17 @@
// 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/>.
pub mod all;