Skip to content

Commit

Permalink
keymanager: Validate latest trust root height in key manager requests
Browse files Browse the repository at this point in the history
  • Loading branch information
peternose committed Sep 1, 2022
1 parent 15c1d95 commit 64ad8e0
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 17 deletions.
1 change: 1 addition & 0 deletions .changelog/4910.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
keymanager: Validate latest trust root height in key manager requests
26 changes: 23 additions & 3 deletions keymanager-api-common/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ pub struct SignedInitResponse {
/// Key manager replication request.
#[derive(Clone, Default, cbor::Encode, cbor::Decode)]
pub struct ReplicateRequest {
// Empty.
/// Latest trust root height.
pub height: Option<u64>,
}

impl ReplicateRequest {
pub fn new(height: Option<u64>) -> Self {
Self { height }
}
}

/// Key manager replication response.
Expand All @@ -103,15 +110,18 @@ pub struct ReplicateResponse {
/// from the master secret. They can be generated at any time.
#[derive(Clone, Default, cbor::Encode, cbor::Decode)]
pub struct LongTermKeyRequest {
/// Latest trust root height.
pub height: Option<u64>,
/// Runtime ID.
pub runtime_id: Namespace,
/// Key pair ID.
pub key_pair_id: KeyPairId,
}

impl LongTermKeyRequest {
pub fn new(runtime_id: Namespace, key_pair_id: KeyPairId) -> Self {
pub fn new(height: Option<u64>, runtime_id: Namespace, key_pair_id: KeyPairId) -> Self {
Self {
height,
runtime_id,
key_pair_id,
}
Expand All @@ -125,6 +135,8 @@ impl LongTermKeyRequest {
/// for the past few epochs relative to the consensus layer state.
#[derive(Clone, Default, cbor::Encode, cbor::Decode)]
pub struct EphemeralKeyRequest {
/// Latest trust root height.
pub height: Option<u64>,
/// Runtime ID.
pub runtime_id: Namespace,
/// Key pair ID.
Expand All @@ -134,8 +146,14 @@ pub struct EphemeralKeyRequest {
}

impl EphemeralKeyRequest {
pub fn new(runtime_id: Namespace, key_pair_id: KeyPairId, epoch: EpochTime) -> Self {
pub fn new(
height: Option<u64>,
runtime_id: Namespace,
key_pair_id: KeyPairId,
epoch: EpochTime,
) -> Self {
Self {
height,
runtime_id,
key_pair_id,
epoch,
Expand Down Expand Up @@ -225,6 +243,8 @@ pub enum KeyManagerError {
NotAuthorized,
#[error("invalid epoch")]
InvalidEpoch,
#[error("height is not fresh")]
HeightNotFresh,
#[error("key manager is not initialized")]
NotInitialized,
#[error("key manager state corrupted")]
Expand Down
55 changes: 48 additions & 7 deletions keymanager-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use oasis_core_client::RpcClient;
use oasis_core_keymanager_api_common::*;
use oasis_core_runtime::{
common::{namespace::Namespace, sgx::EnclaveIdentity},
consensus::beacon::EpochTime,
consensus::{beacon::EpochTime, verifier::Verifier},
enclave_rpc::session,
protocol::Protocol,
rak::RAK,
Expand All @@ -29,6 +29,8 @@ struct Inner {
runtime_id: Namespace,
/// RPC client.
rpc_client: RpcClient,
/// Consensus verifier.
consensus_verifier: Arc<dyn Verifier>,
/// Local cache for the long-term and ephemeral private keys fetched from
/// get_or_create_keys and get_or_create_ephemeral_keys KeyManager endpoints.
private_key_cache: RwLock<LruCache<(KeyPairId, Option<EpochTime>), KeyPair>>,
Expand All @@ -43,11 +45,17 @@ pub struct RemoteClient {
}

impl RemoteClient {
fn new(runtime_id: Namespace, rpc_client: RpcClient, keys_cache_sizes: usize) -> Self {
fn new(
runtime_id: Namespace,
rpc_client: RpcClient,
consensus_verifier: Arc<dyn Verifier>,
keys_cache_sizes: usize,
) -> Self {
Self {
inner: Arc::new(Inner {
runtime_id,
rpc_client,
consensus_verifier,
private_key_cache: RwLock::new(LruCache::new(keys_cache_sizes)),
public_key_cache: RwLock::new(LruCache::new(keys_cache_sizes)),
}),
Expand All @@ -60,6 +68,7 @@ impl RemoteClient {
runtime_id: Namespace,
enclaves: Option<HashSet<EnclaveIdentity>>,
protocol: Arc<Protocol>,
consensus_verifier: Arc<dyn Verifier>,
rak: Arc<RAK>,
keys_cache_sizes: usize,
) -> Self {
Expand All @@ -72,6 +81,7 @@ impl RemoteClient {
protocol,
KEY_MANAGER_ENDPOINT,
),
consensus_verifier,
keys_cache_sizes,
)
}
Expand All @@ -84,6 +94,7 @@ impl RemoteClient {
pub fn new_runtime(
runtime_id: Namespace,
protocol: Arc<Protocol>,
consensus_verifier: Arc<dyn Verifier>,
rak: Arc<RAK>,
keys_cache_sizes: usize,
signers: TrustedPolicySigners,
Expand Down Expand Up @@ -111,6 +122,7 @@ impl RemoteClient {
runtime_id,
enclaves,
protocol,
consensus_verifier,
rak,
keys_cache_sizes,
)
Expand Down Expand Up @@ -154,12 +166,17 @@ impl KeyManagerClient for RemoteClient {
// No entry in cache, fetch from key manager.
let inner = self.inner.clone();
Box::pin(async move {
let height = inner
.consensus_verifier
.latest_height()
.map_err(|err| KeyManagerError::Other(err.into()))?;

let keys: KeyPair = inner
.rpc_client
.call(
ctx,
METHOD_GET_OR_CREATE_KEYS,
LongTermKeyRequest::new(inner.runtime_id, key_pair_id),
LongTermKeyRequest::new(Some(height), inner.runtime_id, key_pair_id),
)
.await
.map_err(|err| KeyManagerError::Other(err.into()))?;
Expand All @@ -185,12 +202,17 @@ impl KeyManagerClient for RemoteClient {
// No entry in cache, fetch from key manager.
let inner = self.inner.clone();
Box::pin(async move {
let height = inner
.consensus_verifier
.latest_height()
.map_err(|err| KeyManagerError::Other(err.into()))?;

let key: Option<SignedPublicKey> = inner
.rpc_client
.call(
ctx,
METHOD_GET_PUBLIC_KEY,
LongTermKeyRequest::new(inner.runtime_id, key_pair_id),
LongTermKeyRequest::new(Some(height), inner.runtime_id, key_pair_id),
)
.await
.map_err(|err| KeyManagerError::Other(err.into()))?;
Expand Down Expand Up @@ -222,12 +244,17 @@ impl KeyManagerClient for RemoteClient {
// No entry in cache, fetch from key manager.
let inner = self.inner.clone();
Box::pin(async move {
let height = inner
.consensus_verifier
.latest_height()
.map_err(|err| KeyManagerError::Other(err.into()))?;

let keys: KeyPair = inner
.rpc_client
.call(
ctx,
METHOD_GET_OR_CREATE_EPHEMERAL_KEYS,
EphemeralKeyRequest::new(inner.runtime_id, key_pair_id, epoch),
EphemeralKeyRequest::new(Some(height), inner.runtime_id, key_pair_id, epoch),
)
.await
.map_err(|err| KeyManagerError::Other(err.into()))?;
Expand All @@ -254,12 +281,17 @@ impl KeyManagerClient for RemoteClient {
// No entry in cache, fetch from key manager.
let inner = self.inner.clone();
Box::pin(async move {
let height = inner
.consensus_verifier
.latest_height()
.map_err(|err| KeyManagerError::Other(err.into()))?;

let key: Option<SignedPublicKey> = inner
.rpc_client
.call(
ctx,
METHOD_GET_PUBLIC_EPHEMERAL_KEY,
EphemeralKeyRequest::new(inner.runtime_id, key_pair_id, epoch),
EphemeralKeyRequest::new(Some(height), inner.runtime_id, key_pair_id, epoch),
)
.await
.map_err(|err| KeyManagerError::Other(err.into()))?;
Expand All @@ -283,9 +315,18 @@ impl KeyManagerClient for RemoteClient {
) -> BoxFuture<Result<Option<MasterSecret>, KeyManagerError>> {
let inner = self.inner.clone();
Box::pin(async move {
let height = inner
.consensus_verifier
.latest_height()
.map_err(|err| KeyManagerError::Other(err.into()))?;

let rsp: ReplicateResponse = inner
.rpc_client
.call(ctx, METHOD_REPLICATE_MASTER_SECRET, ReplicateRequest {})
.call(
ctx,
METHOD_REPLICATE_MASTER_SECRET,
ReplicateRequest::new(Some(height)),
)
.await
.map_err(|err| KeyManagerError::Other(err.into()))?;
Ok(Some(rsp.master_secret))
Expand Down
4 changes: 4 additions & 0 deletions keymanager-lib/src/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ impl Kdf {
rctx.runtime_id,
Policy::global().may_replicate_from(),
rctx.protocol.clone(),
ctx.consensus_verifier.clone(),
ctx.rak.clone(),
1, // Not used, doesn't matter.
);
Expand Down Expand Up @@ -699,10 +700,12 @@ mod tests {
let runtime_id = Namespace::from(vec![1u8; 32]);
let key_pair_id = KeyPairId::from(vec![1u8; 32]);
let epoch = 1;
let height = None;

// LongTermKeyRequest's seed should depend on runtime_id and
// key_pair_id.
let req1 = LongTermKeyRequest {
height,
runtime_id,
key_pair_id,
};
Expand All @@ -721,6 +724,7 @@ mod tests {
// EphemeralKeyRequest's seed should depend on runtime_id, key_pair_id
// and epoch.
let req1 = EphemeralKeyRequest {
height,
epoch,
runtime_id,
key_pair_id,
Expand Down
36 changes: 31 additions & 5 deletions keymanager-lib/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ use oasis_core_runtime::{
enclave_rpc::Context as RpcContext,
};

/// Maximum age of ephemeral key in the number of epochs.
/// Maximum age of an ephemeral key in the number of epochs.
const MAX_EPHEMERAL_KEY_AGE: EpochTime = 10;
/// Maximum age of a fresh height in the number of blocks.
///
/// A height is considered fresh if it is not more than specified amount
/// of blocks lower than the height of the latest trust root.
const MAX_FRESH_HEIGHT_AGE: u64 = 50;

/// See `Kdf::get_or_create_keys`.
pub fn get_or_create_keys(req: &LongTermKeyRequest, ctx: &mut RpcContext) -> Result<KeyPair> {
authorize_private_key_generation(&req.runtime_id, ctx)?;
validate_height_freshness(req.height, ctx)?;

Kdf::global().get_or_create_keys(req)
}
Expand All @@ -39,6 +45,7 @@ pub fn get_or_create_ephemeral_keys(
) -> Result<KeyPair> {
authorize_private_key_generation(&req.runtime_id, ctx)?;
validate_epoch(req.epoch, ctx)?;
validate_height_freshness(req.height, ctx)?;

Kdf::global().get_or_create_keys(req)
}
Expand All @@ -59,10 +66,11 @@ pub fn get_public_ephemeral_key(

/// See `Kdf::replicate_master_secret`.
pub fn replicate_master_secret(
_req: &ReplicateRequest,
req: &ReplicateRequest,
ctx: &mut RpcContext,
) -> Result<ReplicateResponse> {
authorize_master_secret_replication(ctx)?;
validate_height_freshness(req.height, ctx)?;

Kdf::global().replicate_master_secret()
}
Expand Down Expand Up @@ -92,14 +100,32 @@ fn authenticate<'a>(ctx: &'a RpcContext) -> Result<&'a EnclaveIdentity> {
Ok(&si.verified_quote.identity)
}

// Validate that the epoch used for derivation of ephemeral private keys is not
// in the future or too far back in the past.
/// Validate that the epoch used for derivation of ephemeral private keys is not
/// in the future or too far back in the past.
fn validate_epoch(epoch: EpochTime, ctx: &RpcContext) -> Result<()> {
let consensus_state = ctx.consensus_verifier.latest_state()?;
let beacon_state = BeaconState::new(&consensus_state);
let consensus_epoch = beacon_state.epoch(Context::create_child(&ctx.io_ctx))?;
if consensus_epoch < epoch || consensus_epoch > epoch + MAX_EPHEMERAL_KEY_AGE {
return Err(KeyManagerError::InvalidEpoch.into());
return Err(anyhow::anyhow!(KeyManagerError::InvalidEpoch));
}
Ok(())
}

/// Validate that given height is fresh, i.e. the height is not more than
/// predefined number of blocks lower than the height of the latest trust root.
///
/// Key manager should use this validation to detect whether the runtimes
/// querying it have a fresh enough state.
fn validate_height_freshness(height: Option<u64>, ctx: &RpcContext) -> Result<()> {
// Outdated key manager clients will not send height in their requests.
// To ensure backwards compatibility we skip check in those cases.
// This should be removed in the future by making height mandatory.
if let Some(height) = height {
let latest_height = ctx.consensus_verifier.latest_height()?;
if latest_height > MAX_FRESH_HEIGHT_AGE && height < latest_height - MAX_FRESH_HEIGHT_AGE {
return Err(anyhow::anyhow!(KeyManagerError::HeightNotFresh));
}
}
Ok(())
}
4 changes: 2 additions & 2 deletions runtime/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ struct TxDispatchState {
/// State provided by the protocol upon successful initialization.
struct ProtocolState {
protocol: Arc<Protocol>,
consensus_verifier: Box<dyn Verifier>,
consensus_verifier: Arc<dyn Verifier>,
}

/// State held by the dispatcher, shared between all async tasks.
Expand Down Expand Up @@ -205,6 +205,7 @@ impl Dispatcher {

/// Start the dispatcher.
pub fn start(&self, protocol: Arc<Protocol>, consensus_verifier: Box<dyn Verifier>) {
let consensus_verifier = Arc::from(consensus_verifier);
let mut s = self.state.lock().unwrap();
*s = Some(ProtocolState {
protocol,
Expand Down Expand Up @@ -249,7 +250,6 @@ impl Dispatcher {
info!(self.logger, "Starting the runtime dispatcher");
let mut rpc_demux = RpcDemux::new(self.rak.clone());
let mut rpc_dispatcher = RpcDispatcher::default();
let consensus_verifier: Arc<dyn Verifier> = Arc::from(consensus_verifier);
let pre_init_state = PreInitState {
protocol: &protocol,
rak: &self.rak,
Expand Down
1 change: 1 addition & 0 deletions tests/runtimes/simple-keyvalue/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ pub fn main_with_version(version: Version) {
let km_client = Arc::new(oasis_core_keymanager_client::RemoteClient::new_runtime(
hi.runtime_id,
state.protocol.clone(),
state.consensus_verifier.clone(),
state.rak.clone(),
1024,
trusted_policy_signers(),
Expand Down

0 comments on commit 64ad8e0

Please sign in to comment.