SecretStore: expose restore_key_public in HTTP API (#10241)

This commit is contained in:
Svyatoslav Nikolsky 2019-06-05 15:04:00 +03:00 committed by David
parent f7dae48c17
commit 6be45367e9
9 changed files with 189 additions and 67 deletions

View File

@ -78,6 +78,22 @@ impl ServerKeyGenerator for KeyServerImpl {
.expect("when wait is called without timeout it always returns Some; qed")
.map_err(Into::into)
}
fn restore_key_public(&self, key_id: &ServerKeyId, author: &Requester) -> Result<Public, Error> {
// recover requestor' public key from signature
let address = author.address(key_id).map_err(Error::InsufficientRequesterData)?;
// negotiate key version && retrieve common key data
let negotiation_session = self.data.lock().cluster.new_key_version_negotiation_session(*key_id)?;
negotiation_session.wait()
.and_then(|_| negotiation_session.common_key_data())
.and_then(|key_share| if key_share.author == address {
Ok(key_share.public)
} else {
Err(Error::AccessDenied)
})
.map_err(Into::into)
}
}
impl DocumentKeyServer for KeyServerImpl {
@ -237,6 +253,10 @@ pub mod tests {
fn generate_key(&self, _key_id: &ServerKeyId, _author: &Requester, _threshold: usize) -> Result<Public, Error> {
unimplemented!("test-only")
}
fn restore_key_public(&self, _key_id: &ServerKeyId, _author: &Requester) -> Result<Public, Error> {
unimplemented!("test-only")
}
}
impl DocumentKeyServer for DummyKeyServer {

View File

@ -26,7 +26,7 @@ use key_server_cluster::decryption_session::SessionImpl as DecryptionSession;
use key_server_cluster::signing_session_ecdsa::SessionImpl as EcdsaSigningSession;
use key_server_cluster::signing_session_schnorr::SessionImpl as SchnorrSigningSession;
use key_server_cluster::message::{Message, KeyVersionNegotiationMessage, RequestKeyVersions,
KeyVersions, KeyVersionsError, FailedKeyVersionContinueAction};
KeyVersions, KeyVersionsError, FailedKeyVersionContinueAction, CommonKeyData};
use key_server_cluster::admin_sessions::ShareChangeSessionMeta;
// TODO [Opt]: change sessions so that versions are sent by chunks.
@ -97,8 +97,8 @@ struct SessionData {
pub state: SessionState,
/// Initialization confirmations.
pub confirmations: Option<BTreeSet<NodeId>>,
/// Key threshold.
pub threshold: Option<usize>,
/// Common key data that nodes have agreed upon.
pub key_share: Option<DocumentKeyShare>,
/// { Version => Nodes }
pub versions: Option<BTreeMap<H256, BTreeSet<NodeId>>>,
/// Session result.
@ -167,12 +167,11 @@ pub struct LargestSupportResultComputer;
impl<T> SessionImpl<T> where T: SessionTransport {
/// Create new session.
pub fn new(params: SessionParams<T>) -> Self {
let threshold = params.key_share.as_ref().map(|key_share| key_share.threshold);
SessionImpl {
core: SessionCore {
meta: params.meta,
sub_session: params.sub_session,
key_share: params.key_share,
key_share: params.key_share.clone(),
result_computer: params.result_computer,
transport: params.transport,
nonce: params.nonce,
@ -181,7 +180,12 @@ impl<T> SessionImpl<T> where T: SessionTransport {
data: Mutex::new(SessionData {
state: SessionState::WaitingForInitialization,
confirmations: None,
threshold: threshold,
key_share: params.key_share.map(|key_share| DocumentKeyShare {
threshold: key_share.threshold,
author: key_share.author,
public: key_share.public,
..Default::default()
}),
versions: None,
result: None,
continue_with: None,
@ -195,12 +199,6 @@ impl<T> SessionImpl<T> where T: SessionTransport {
&self.core.meta
}
/// Return key threshold.
pub fn key_threshold(&self) -> Result<usize, Error> {
self.data.lock().threshold.clone()
.ok_or(Error::InvalidStateForRequest)
}
/// Return result computer reference.
pub fn version_holders(&self, version: &H256) -> Result<BTreeSet<NodeId>, Error> {
Ok(self.data.lock().versions.as_ref().ok_or(Error::InvalidStateForRequest)?
@ -229,6 +227,12 @@ impl<T> SessionImpl<T> where T: SessionTransport {
.expect("wait_session returns Some if called without timeout; qed")
}
/// Retrieve common key data (author, threshold, public), if available.
pub fn common_key_data(&self) -> Result<DocumentKeyShare, Error> {
self.data.lock().key_share.clone()
.ok_or(Error::InvalidStateForRequest)
}
/// Initialize session.
pub fn initialize(&self, connected_nodes: BTreeSet<NodeId>) -> Result<(), Error> {
// check state
@ -322,7 +326,11 @@ impl<T> SessionImpl<T> where T: SessionTransport {
session: self.core.meta.id.clone().into(),
sub_session: self.core.sub_session.clone().into(),
session_nonce: self.core.nonce,
threshold: self.core.key_share.as_ref().map(|key_share| key_share.threshold),
key_common: self.core.key_share.as_ref().map(|key_share| CommonKeyData {
threshold: key_share.threshold,
author: key_share.author.into(),
public: key_share.public.into(),
}),
versions: self.core.key_share.as_ref().map(|key_share|
key_share.versions.iter().rev()
.filter(|v| v.id_numbers.contains_key(sender))
@ -357,12 +365,25 @@ impl<T> SessionImpl<T> where T: SessionTransport {
// remember versions that sender have
{
match message.threshold.clone() {
Some(threshold) if data.threshold.is_none() => {
data.threshold = Some(threshold);
match message.key_common.as_ref() {
Some(key_common) if data.key_share.is_none() => {
data.key_share = Some(DocumentKeyShare {
threshold: key_common.threshold,
author: key_common.author.clone().into(),
public: key_common.public.clone().into(),
..Default::default()
});
},
Some(key_common) => {
let prev_key_share = data.key_share.as_ref()
.expect("data.key_share.is_none() is matched by previous branch; qed");
if prev_key_share.threshold != key_common.threshold ||
prev_key_share.author.as_bytes() != key_common.author.as_bytes() ||
prev_key_share.public.as_bytes() != key_common.public.as_bytes()
{
return Err(Error::InvalidMessage);
}
},
Some(threshold) if data.threshold.as_ref() == Some(&threshold) => (),
Some(_) => return Err(Error::InvalidMessage),
None if message.versions.is_empty() => (),
None => return Err(Error::InvalidMessage),
}
@ -388,7 +409,8 @@ impl<T> SessionImpl<T> where T: SessionTransport {
let reason = "this field is filled on master node when initializing; try_complete is only called on initialized master node; qed";
let confirmations = data.confirmations.as_ref().expect(reason);
let versions = data.versions.as_ref().expect(reason);
if let Some(result) = core.result_computer.compute_result(data.threshold.clone(), confirmations, versions) {
let threshold = data.key_share.as_ref().map(|key_share| key_share.threshold);
if let Some(result) = core.result_computer.compute_result(threshold, confirmations, versions) {
// when the master node processing decryption service request, it starts with a key version negotiation session
// if the negotiation fails, only master node knows about it
// => if the error is fatal, only the master will know about it and report it to the contract && the request will never be rejected
@ -590,7 +612,7 @@ impl SessionResultComputer for LargestSupportResultComputer {
mod tests {
use std::sync::Arc;
use std::collections::{VecDeque, BTreeMap, BTreeSet};
use ethereum_types::{H512, Address};
use ethereum_types::{H512, H160, Address};
use ethkey::public_to_address;
use key_server_cluster::{NodeId, SessionId, Error, KeyStorage, DummyKeyStorage,
DocumentKeyShare, DocumentKeyShareVersion};
@ -600,7 +622,10 @@ mod tests {
use key_server_cluster::cluster_sessions::ClusterSession;
use key_server_cluster::admin_sessions::ShareChangeSessionMeta;
use key_server_cluster::decryption_session::create_default_decryption_session;
use key_server_cluster::message::{Message, KeyVersionNegotiationMessage, RequestKeyVersions, KeyVersions};
use key_server_cluster::message::{
Message, KeyVersionNegotiationMessage, RequestKeyVersions,
CommonKeyData, KeyVersions,
};
use super::{
SessionImpl, SessionTransport, SessionParams, FastestResultComputer, LargestSupportResultComputer,
SessionResultComputer, SessionState, ContinueAction, FailedContinueAction,
@ -759,7 +784,11 @@ mod tests {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
threshold: Some(10),
key_common: Some(CommonKeyData {
threshold: 10,
author: Default::default(),
public: Default::default(),
}),
versions: Vec::new(),
})), Err(Error::InvalidStateForRequest));
}
@ -775,7 +804,12 @@ mod tests {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
threshold: Some(0),
key_common: Some(CommonKeyData {
threshold: 0,
author: Default::default(),
public: Default::default(),
}),
versions: vec![version_id.clone().into()]
})), Ok(()));
assert_eq!(ml.session(0).data.lock().state, SessionState::Finished);
@ -784,14 +818,20 @@ mod tests {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
threshold: Some(0),
key_common: Some(CommonKeyData {
threshold: 0,
author: Default::default(),
public: Default::default(),
}),
versions: vec![version_id.clone().into()]
})), Ok(()));
assert_eq!(ml.session(0).data.lock().state, SessionState::Finished);
}
#[test]
fn negotiation_fails_if_wrong_threshold_sent() {
fn negotiation_fails_if_wrong_common_data_sent() {
fn run_test(key_common: CommonKeyData) {
let ml = MessageLoop::empty(3);
ml.session(0).initialize(ml.nodes.keys().cloned().collect()).unwrap();
@ -800,18 +840,41 @@ mod tests {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
threshold: Some(1),
key_common: Some(CommonKeyData {
threshold: 1,
author: Default::default(),
public: Default::default(),
}),
versions: vec![version_id.clone().into()]
})), Ok(()));
assert_eq!(ml.session(0).process_message(ml.node_id(2), &KeyVersionNegotiationMessage::KeyVersions(KeyVersions {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
threshold: Some(2),
key_common: Some(key_common),
versions: vec![version_id.clone().into()]
})), Err(Error::InvalidMessage));
}
run_test(CommonKeyData {
threshold: 2,
author: Default::default(),
public: Default::default(),
});
run_test(CommonKeyData {
threshold: 1,
author: H160::from_low_u64_be(1).into(),
public: Default::default(),
});
run_test(CommonKeyData {
threshold: 1,
author: H160::from_low_u64_be(2).into(),
public: Default::default(),
});
}
#[test]
fn negotiation_fails_if_threshold_empty_when_versions_are_not_empty() {
let ml = MessageLoop::empty(2);
@ -822,7 +885,7 @@ mod tests {
session: Default::default(),
sub_session: math::generate_random_scalar().unwrap().into(),
session_nonce: 0,
threshold: None,
key_common: None,
versions: vec![version_id.clone().into()]
})), Err(Error::InvalidMessage));
}
@ -832,9 +895,9 @@ mod tests {
let nodes = MessageLoop::prepare_nodes(2);
let version_id = (*math::generate_random_scalar().unwrap()).clone();
nodes.values().nth(0).unwrap().insert(Default::default(), DocumentKeyShare {
author: Default::default(),
author: H160::from_low_u64_be(2),
threshold: 1,
public: Default::default(),
public: H512::from_low_u64_be(3),
common_point: None,
encrypted_point: None,
versions: vec![DocumentKeyShareVersion {
@ -848,8 +911,13 @@ mod tests {
// we can't be sure that node has given key version because previous ShareAdd session could fail
assert!(ml.session(0).data.lock().state != SessionState::Finished);
// check that upon completion, threshold is known
assert_eq!(ml.session(0).key_threshold(), Ok(1));
// check that upon completion, commmon key data is known
assert_eq!(ml.session(0).common_key_data(), Ok(DocumentKeyShare {
author: H160::from_low_u64_be(2),
threshold: 1,
public: H512::from_low_u64_be(3),
..Default::default()
}));
}
#[test]

View File

@ -800,7 +800,7 @@ impl SessionImpl {
.wait()?
.expect("initialize_share_change_session is only called on share change master; negotiation session completes with some on master; qed");
let selected_version_holders = negotiation_session.version_holders(&selected_version)?;
let selected_version_threshold = negotiation_session.key_threshold()?;
let selected_version_threshold = negotiation_session.common_key_data()?.threshold;
// prepare session change plan && check if something needs to be changed
let old_nodes_set = selected_version_holders;

View File

@ -25,7 +25,7 @@ use key_server_cluster::cluster_sessions::ClusterSession;
use key_server_cluster::math;
use key_server_cluster::message::{Message, ShareAddMessage, ShareAddConsensusMessage, ConsensusMessageOfShareAdd,
InitializeConsensusSessionOfShareAdd, KeyShareCommon, NewKeysDissemination, ShareAddError,
ConfirmConsensusInitialization};
ConfirmConsensusInitialization, CommonKeyData};
use key_server_cluster::jobs::job_session::JobTransport;
use key_server_cluster::jobs::dummy_job::{DummyJob, DummyJobTransport};
use key_server_cluster::jobs::servers_set_change_access_job::{ServersSetChangeAccessJob, ServersSetChangeAccessRequest};
@ -469,9 +469,9 @@ impl<T> SessionImpl<T> where T: SessionTransport {
// update data
data.state = SessionState::WaitingForKeysDissemination;
data.new_key_share = Some(NewKeyShare {
threshold: message.threshold,
author: message.author.clone().into(),
joint_public: message.joint_public.clone().into(),
threshold: message.key_common.threshold,
author: message.key_common.author.clone().into(),
joint_public: message.key_common.public.clone().into(),
common_point: message.common_point.clone().map(Into::into),
encrypted_point: message.encrypted_point.clone().map(Into::into),
});
@ -645,9 +645,11 @@ impl<T> SessionImpl<T> where T: SessionTransport {
core.transport.send(new_node, ShareAddMessage::KeyShareCommon(KeyShareCommon {
session: core.meta.id.clone().into(),
session_nonce: core.nonce,
key_common: CommonKeyData {
threshold: old_key_share.threshold,
author: old_key_share.author.clone().into(),
joint_public: old_key_share.public.clone().into(),
author: old_key_share.author.into(),
public: old_key_share.public.into(),
},
common_point: old_key_share.common_point.clone().map(Into::into),
encrypted_point: old_key_share.encrypted_point.clone().map(Into::into),
id_numbers: old_key_version.id_numbers.iter()

View File

@ -970,12 +970,8 @@ pub struct KeyShareCommon {
pub session: MessageSessionId,
/// Session-level nonce.
pub session_nonce: u64,
/// Key threshold.
pub threshold: usize,
/// Author of key share entry.
pub author: SerializableAddress,
/// Joint public.
pub joint_public: SerializablePublic,
/// Common key data.
pub key_common: CommonKeyData,
/// Common (shared) encryption point.
pub common_point: Option<SerializablePublic>,
/// Encrypted point.
@ -1026,12 +1022,23 @@ pub struct KeyVersions {
pub sub_session: SerializableSecret,
/// Session-level nonce.
pub session_nonce: u64,
/// Key threshold.
pub threshold: Option<usize>,
/// Common key data, shared by all versions.
pub key_common: Option<CommonKeyData>,
/// Key versions.
pub versions: Vec<SerializableH256>,
}
/// Common key data.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommonKeyData {
/// Key threshold.
pub threshold: usize,
/// Author of the key entry.
pub author: SerializableAddress,
/// Joint public.
pub public: SerializablePublic,
}
/// When key versions error has occured.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyVersionsError {

View File

@ -34,8 +34,7 @@ type CurrentSerializableDocumentKeyShare = SerializableDocumentKeyShareV3;
type CurrentSerializableDocumentKeyVersion = SerializableDocumentKeyShareVersionV3;
/// Encrypted key share, stored by key storage on the single key server.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(test, derive(Default))]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct DocumentKeyShare {
/// Author of the entry.
pub author: Address,

View File

@ -40,6 +40,7 @@ use jsonrpc_server_utils::cors::{self, AllowCors, AccessControlAllowOrigin};
/// To generate server key: POST /shadow/{server_key_id}/{signature}/{threshold}
/// To store pregenerated encrypted document key: POST /shadow/{server_key_id}/{signature}/{common_point}/{encrypted_key}
/// To generate server && document key: POST /{server_key_id}/{signature}/{threshold}
/// To get public portion of server key: GET /server/{server_key_id}/{signature}
/// To get document key: GET /{server_key_id}/{signature}
/// To get document key shadow: GET /shadow/{server_key_id}/{signature}
/// To generate Schnorr signature with server key: GET /schnorr/{server_key_id}/{signature}/{message_hash}
@ -64,6 +65,8 @@ enum Request {
StoreDocumentKey(ServerKeyId, RequestSignature, Public, Public),
/// Generate encryption key.
GenerateDocumentKey(ServerKeyId, RequestSignature, usize),
/// Request public portion of server key.
GetServerKey(ServerKeyId, RequestSignature),
/// Request encryption key of given document for given requestor.
GetDocumentKey(ServerKeyId, RequestSignature),
/// Request shadow of encryption key of given document for given requestor.
@ -155,6 +158,15 @@ impl KeyServerHttpHandler {
err
}))
},
Request::GetServerKey(document, signature) => {
return_server_public_key(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.restore_key_public(&document, &signature.into()))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| {
warn!(target: "secretstore", "GetServerKey request {} has failed with: {}", req_uri, err);
err
}))
},
Request::GetDocumentKey(document, signature) => {
return_document_key(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.restore_document_key(&document, &signature.into()))
@ -361,8 +373,8 @@ fn parse_request(method: &HttpMethod, uri_path: &str, body: &[u8]) -> Request {
return parse_admin_request(method, path, body);
}
let (prefix, args_offset) = if &path[0] == "shadow" || &path[0] == "schnorr" || &path[0] == "ecdsa"
{ (&*path[0], 1) } else { ("", 0) };
let is_known_prefix = &path[0] == "shadow" || &path[0] == "schnorr" || &path[0] == "ecdsa" || &path[0] == "server";
let (prefix, args_offset) = if is_known_prefix { (&*path[0], 1) } else { ("", 0) };
let args_count = path.len() - args_offset;
if args_count < 2 || path[args_offset].is_empty() || path[args_offset + 1].is_empty() {
return Request::Invalid;
@ -388,6 +400,8 @@ fn parse_request(method: &HttpMethod, uri_path: &str, body: &[u8]) -> Request {
Request::StoreDocumentKey(document, signature, common_point, encrypted_key),
("", 3, &HttpMethod::POST, Some(Ok(threshold)), _, _, _) =>
Request::GenerateDocumentKey(document, signature, threshold),
("server", 2, &HttpMethod::GET, _, _, _, _) =>
Request::GetServerKey(document, signature),
("", 2, &HttpMethod::GET, _, _, _, _) =>
Request::GetDocumentKey(document, signature),
("shadow", 2, &HttpMethod::GET, _, _, _, _) =>
@ -466,6 +480,10 @@ mod tests {
Request::GenerateDocumentKey(H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(),
"a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01".parse().unwrap(),
2));
// GET /server/{server_key_id}/{signature} => get public portion of server key
assert_eq!(parse_request(&HttpMethod::GET, "/server/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01", Default::default()),
Request::GetServerKey(H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(),
"a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01".parse().unwrap()));
// GET /{server_key_id}/{signature} => get document key
assert_eq!(parse_request(&HttpMethod::GET, "/0000000000000000000000000000000000000000000000000000000000000001/a199fb39e11eefb61c78a4074a53c0d4424600a3e74aad4fb9d93a26c30d067e1d4d29936de0c73f19827394a1dd049480a0d581aee7ae7546968da7d3d1c2fd01", Default::default()),
Request::GetDocumentKey(H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(),

View File

@ -75,6 +75,10 @@ impl ServerKeyGenerator for Listener {
fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<Public, Error> {
self.key_server.generate_key(key_id, author, threshold)
}
fn restore_key_public(&self, key_id: &ServerKeyId, author: &Requester) -> Result<Public, Error> {
self.key_server.restore_key_public(key_id, author)
}
}
impl DocumentKeyServer for Listener {

View File

@ -40,6 +40,10 @@ pub trait ServerKeyGenerator {
/// `threshold + 1` is the minimal number of nodes, required to restore private key.
/// Result is a public portion of SK.
fn generate_key(&self, key_id: &ServerKeyId, author: &Requester, threshold: usize) -> Result<Public, Error>;
/// Retrieve public portion of previously generated SK.
/// `key_id` is identifier of previously generated SK.
/// `author` is the same author, that has created the server key.
fn restore_key_public(&self, key_id: &ServerKeyId, author: &Requester) -> Result<Public, Error>;
}
/// Document key (DK) server.