345 lines
11 KiB
Rust
345 lines
11 KiB
Rust
// 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::BTreeMap;
|
|
use std::fmt::{Debug, Formatter, Error as FmtError};
|
|
use std::time;
|
|
use std::sync::Arc;
|
|
use parking_lot::{Condvar, Mutex};
|
|
use ethkey::{self, Public, Signature};
|
|
use key_server_cluster::{Error, NodeId, SessionId, KeyStorage, DocumentKeyShare};
|
|
use key_server_cluster::cluster::Cluster;
|
|
use key_server_cluster::cluster_sessions::ClusterSession;
|
|
use key_server_cluster::message::{Message, EncryptionMessage, InitializeEncryptionSession,
|
|
ConfirmEncryptionInitialization, EncryptionSessionError};
|
|
|
|
/// Encryption (distributed key generation) session.
|
|
/// Based on "ECDKG: A Distributed Key Generation Protocol Based on Elliptic Curve Discrete Logarithm" paper:
|
|
/// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.124.4128&rep=rep1&type=pdf
|
|
/// Brief overview:
|
|
/// 1) initialization: master node (which has received request for storing the secret) initializes the session on all other nodes
|
|
/// 2) master node sends common_point + encrypted_point to all other nodes
|
|
/// 3) common_point + encrypted_point are saved on all nodes
|
|
/// 4) in case of error, previous values are restored
|
|
pub struct SessionImpl {
|
|
/// Unique session id.
|
|
id: SessionId,
|
|
/// Public identifier of this node.
|
|
self_node_id: NodeId,
|
|
/// Encrypted data.
|
|
encrypted_data: Option<DocumentKeyShare>,
|
|
/// Key storage.
|
|
key_storage: Arc<KeyStorage>,
|
|
/// Cluster which allows this node to send messages to other nodes in the cluster.
|
|
cluster: Arc<Cluster>,
|
|
/// Session nonce.
|
|
nonce: u64,
|
|
/// SessionImpl completion condvar.
|
|
completed: Condvar,
|
|
/// Mutable session data.
|
|
data: Mutex<SessionData>,
|
|
}
|
|
|
|
/// SessionImpl creation parameters
|
|
pub struct SessionParams {
|
|
/// SessionImpl identifier.
|
|
pub id: SessionId,
|
|
/// Id of node, on which this session is running.
|
|
pub self_node_id: Public,
|
|
/// Encrypted data (result of running generation_session::SessionImpl).
|
|
pub encrypted_data: Option<DocumentKeyShare>,
|
|
/// Key storage.
|
|
pub key_storage: Arc<KeyStorage>,
|
|
/// Cluster
|
|
pub cluster: Arc<Cluster>,
|
|
/// Session nonce.
|
|
pub nonce: u64,
|
|
}
|
|
|
|
/// Mutable data of encryption (distributed key generation) session.
|
|
#[derive(Debug)]
|
|
struct SessionData {
|
|
/// Current state of the session.
|
|
state: SessionState,
|
|
/// Nodes-specific data.
|
|
nodes: BTreeMap<NodeId, NodeData>,
|
|
/// Encryption session result.
|
|
result: Option<Result<(), Error>>,
|
|
}
|
|
|
|
/// Mutable node-specific data.
|
|
#[derive(Debug, Clone)]
|
|
struct NodeData {
|
|
// === Values, filled during initialization phase ===
|
|
/// Flags marking that node has confirmed session initialization.
|
|
pub initialization_confirmed: bool,
|
|
}
|
|
|
|
/// Encryption (distributed key generation) session state.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum SessionState {
|
|
// === Initialization states ===
|
|
/// Every node starts in this state.
|
|
WaitingForInitialization,
|
|
/// Master node waits for every other node to confirm initialization.
|
|
WaitingForInitializationConfirm,
|
|
|
|
// === Final states of the session ===
|
|
/// Encryption data is saved.
|
|
Finished,
|
|
/// Failed to save encryption data.
|
|
Failed,
|
|
}
|
|
|
|
impl SessionImpl {
|
|
/// Create new encryption session.
|
|
pub fn new(params: SessionParams) -> Result<Self, Error> {
|
|
check_encrypted_data(¶ms.encrypted_data)?;
|
|
|
|
Ok(SessionImpl {
|
|
id: params.id,
|
|
self_node_id: params.self_node_id,
|
|
encrypted_data: params.encrypted_data,
|
|
key_storage: params.key_storage,
|
|
cluster: params.cluster,
|
|
nonce: params.nonce,
|
|
completed: Condvar::new(),
|
|
data: Mutex::new(SessionData {
|
|
state: SessionState::WaitingForInitialization,
|
|
nodes: BTreeMap::new(),
|
|
result: None,
|
|
}),
|
|
})
|
|
}
|
|
|
|
/// Get this node Id.
|
|
pub fn node(&self) -> &NodeId {
|
|
&self.self_node_id
|
|
}
|
|
|
|
/// Wait for session completion.
|
|
pub fn wait(&self, timeout: Option<time::Duration>) -> Result<(), Error> {
|
|
Self::wait_session(&self.completed, &self.data, timeout, |data| data.result.clone())
|
|
}
|
|
|
|
|
|
/// Start new session initialization. This must be called on master node.
|
|
pub fn initialize(&self, requestor_signature: Signature, common_point: Public, encrypted_point: Public) -> Result<(), Error> {
|
|
let mut data = self.data.lock();
|
|
|
|
// check state
|
|
if data.state != SessionState::WaitingForInitialization {
|
|
return Err(Error::InvalidStateForRequest);
|
|
}
|
|
|
|
// update state
|
|
data.state = SessionState::WaitingForInitializationConfirm;
|
|
data.nodes.extend(self.cluster.nodes().into_iter().map(|n| (n, NodeData {
|
|
initialization_confirmed: &n == self.node(),
|
|
})));
|
|
|
|
// TODO: id signature is not enough here, as it was already used in key generation
|
|
// TODO: there could be situation when some nodes have failed to store encrypted data
|
|
// => potential problems during restore. some confirmation step is needed (2pc)?
|
|
// save encryption data
|
|
if let Some(mut encrypted_data) = self.encrypted_data.clone() {
|
|
// check that the requester is the author of the encrypted data
|
|
let requestor_public = ethkey::recover(&requestor_signature, &self.id)?;
|
|
if encrypted_data.author != requestor_public {
|
|
return Err(Error::AccessDenied);
|
|
}
|
|
|
|
encrypted_data.common_point = Some(common_point.clone());
|
|
encrypted_data.encrypted_point = Some(encrypted_point.clone());
|
|
self.key_storage.update(self.id.clone(), encrypted_data)
|
|
.map_err(|e| Error::KeyStorage(e.into()))?;
|
|
}
|
|
|
|
// start initialization
|
|
if data.nodes.len() > 1 {
|
|
self.cluster.broadcast(Message::Encryption(EncryptionMessage::InitializeEncryptionSession(InitializeEncryptionSession {
|
|
session: self.id.clone().into(),
|
|
session_nonce: self.nonce,
|
|
requestor_signature: requestor_signature.into(),
|
|
common_point: common_point.into(),
|
|
encrypted_point: encrypted_point.into(),
|
|
})))
|
|
} else {
|
|
data.state = SessionState::Finished;
|
|
data.result = Some(Ok(()));
|
|
self.completed.notify_all();
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// When session initialization message is received.
|
|
pub fn on_initialize_session(&self, sender: NodeId, message: &InitializeEncryptionSession) -> Result<(), Error> {
|
|
debug_assert!(self.id == *message.session);
|
|
debug_assert!(&sender != self.node());
|
|
|
|
let mut data = self.data.lock();
|
|
|
|
// check state
|
|
if data.state != SessionState::WaitingForInitialization {
|
|
return Err(Error::InvalidStateForRequest);
|
|
}
|
|
|
|
// check that the requester is the author of the encrypted data
|
|
if let Some(mut encrypted_data) = self.encrypted_data.clone() {
|
|
let requestor_public = ethkey::recover(&message.requestor_signature.clone().into(), &self.id)?;
|
|
if encrypted_data.author != requestor_public {
|
|
return Err(Error::AccessDenied);
|
|
}
|
|
|
|
// save encryption data
|
|
encrypted_data.common_point = Some(message.common_point.clone().into());
|
|
encrypted_data.encrypted_point = Some(message.encrypted_point.clone().into());
|
|
self.key_storage.update(self.id.clone(), encrypted_data)
|
|
.map_err(|e| Error::KeyStorage(e.into()))?;
|
|
}
|
|
|
|
// update state
|
|
data.state = SessionState::Finished;
|
|
|
|
// send confirmation back to master node
|
|
self.cluster.send(&sender, Message::Encryption(EncryptionMessage::ConfirmEncryptionInitialization(ConfirmEncryptionInitialization {
|
|
session: self.id.clone().into(),
|
|
session_nonce: self.nonce,
|
|
})))
|
|
}
|
|
|
|
/// When session initialization confirmation message is reeived.
|
|
pub fn on_confirm_initialization(&self, sender: NodeId, message: &ConfirmEncryptionInitialization) -> Result<(), Error> {
|
|
debug_assert!(self.id == *message.session);
|
|
debug_assert!(&sender != self.node());
|
|
|
|
let mut data = self.data.lock();
|
|
debug_assert!(data.nodes.contains_key(&sender));
|
|
|
|
// check if all nodes have confirmed initialization
|
|
data.nodes.get_mut(&sender)
|
|
.expect("message is received from cluster; nodes contains all cluster nodes; qed")
|
|
.initialization_confirmed = true;
|
|
if !data.nodes.values().all(|n| n.initialization_confirmed) {
|
|
return Ok(());
|
|
}
|
|
|
|
// update state
|
|
data.state = SessionState::Finished;
|
|
data.result = Some(Ok(()));
|
|
self.completed.notify_all();
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ClusterSession for SessionImpl {
|
|
type Id = SessionId;
|
|
|
|
fn type_name() -> &'static str {
|
|
"encryption"
|
|
}
|
|
|
|
fn id(&self) -> SessionId {
|
|
self.id.clone()
|
|
}
|
|
|
|
fn is_finished(&self) -> bool {
|
|
let data = self.data.lock();
|
|
data.state == SessionState::Failed
|
|
|| data.state == SessionState::Finished
|
|
}
|
|
|
|
fn on_node_timeout(&self, node: &NodeId) {
|
|
let mut data = self.data.lock();
|
|
|
|
warn!("{}: encryption session failed because {} connection has timeouted", self.node(), node);
|
|
|
|
data.state = SessionState::Failed;
|
|
data.result = Some(Err(Error::NodeDisconnected));
|
|
self.completed.notify_all();
|
|
}
|
|
|
|
fn on_session_timeout(&self) {
|
|
let mut data = self.data.lock();
|
|
|
|
warn!("{}: encryption session failed with timeout", self.node());
|
|
|
|
data.state = SessionState::Failed;
|
|
data.result = Some(Err(Error::NodeDisconnected));
|
|
self.completed.notify_all();
|
|
}
|
|
|
|
fn on_session_error(&self, node: &NodeId, error: Error) {
|
|
// error in encryption session is considered fatal
|
|
// => broadcast error if error occured on this node
|
|
if *node == self.self_node_id {
|
|
// do not bother processing send error, as we already processing error
|
|
let _ = self.cluster.broadcast(Message::Encryption(EncryptionMessage::EncryptionSessionError(EncryptionSessionError {
|
|
session: self.id.clone().into(),
|
|
session_nonce: self.nonce,
|
|
error: error.clone().into(),
|
|
})));
|
|
}
|
|
|
|
let mut data = self.data.lock();
|
|
|
|
warn!("{}: encryption session failed with error: {} from {}", self.node(), error, node);
|
|
|
|
data.state = SessionState::Failed;
|
|
data.result = Some(Err(error));
|
|
self.completed.notify_all();
|
|
}
|
|
|
|
fn on_message(&self, sender: &NodeId, message: &Message) -> Result<(), Error> {
|
|
if Some(self.nonce) != message.session_nonce() {
|
|
return Err(Error::ReplayProtection);
|
|
}
|
|
|
|
match message {
|
|
&Message::Encryption(ref message) => match message {
|
|
&EncryptionMessage::InitializeEncryptionSession(ref message) =>
|
|
self.on_initialize_session(sender.clone(), message),
|
|
&EncryptionMessage::ConfirmEncryptionInitialization(ref message) =>
|
|
self.on_confirm_initialization(sender.clone(), message),
|
|
&EncryptionMessage::EncryptionSessionError(ref message) => {
|
|
self.on_session_error(sender, Error::Io(message.error.clone().into()));
|
|
Ok(())
|
|
},
|
|
},
|
|
_ => unreachable!("cluster checks message to be correct before passing; qed"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Debug for SessionImpl {
|
|
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
|
write!(f, "Encryption session {} on {}", self.id, self.self_node_id)
|
|
}
|
|
}
|
|
|
|
fn check_encrypted_data(encrypted_data: &Option<DocumentKeyShare>) -> Result<(), Error> {
|
|
if let &Some(ref encrypted_data) = encrypted_data {
|
|
// check that common_point and encrypted_point are still not set yet
|
|
if encrypted_data.common_point.is_some() || encrypted_data.encrypted_point.is_some() {
|
|
return Err(Error::CompletedSessionId);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|