Skip to content

Commit

Permalink
runtime-sdk/modules/rofl: Add metadata and encrypted secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Jan 10, 2025
1 parent c481fe0 commit ee831f9
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 27 deletions.
14 changes: 14 additions & 0 deletions client-sdk/go/modules/rofl/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type Create struct {
Policy AppAuthPolicy `json:"policy"`
// Scheme is the identifier generation scheme.
Scheme IdentifierScheme `json:"scheme"`
// Metadata are arbitrary key/value pairs.
Metadata map[string]string `json:"metadata,omitempty"`
}

// IdentifierScheme is a ROFL application identifier generation scheme.
Expand All @@ -38,6 +40,11 @@ type Update struct {
Policy AppAuthPolicy `json:"policy"`
// Admin is the application administrator address.
Admin *types.Address `json:"admin"`

// Metadata are arbitrary key/value pairs.
Metadata map[string]string `json:"metadata,omitempty"`
// Secrets are arbitrary encrypted key/value pairs.
Secrets map[string][]byte `json:"secrets,omitempty"`
}

// Remove an existing ROFL application call.
Expand Down Expand Up @@ -84,6 +91,13 @@ type AppConfig struct {
Admin *types.Address `json:"admin"`
// Stake is the staked amount.
Stake types.BaseUnits `json:"stake"`

// Metadata are arbitrary key/value pairs.
Metadata map[string]string `json:"metadata,omitempty"`
// Secrets are arbitrary SEK-encrypted key/value pairs.
Secrets map[string][]byte `json:"secrets,omitempty"`
// SEK is the secrets encryption (public) key.
SEK x25519.PublicKey `json:"sek"`
}

// Registration is a ROFL enclave registration descriptor.
Expand Down
3 changes: 1 addition & 2 deletions rofl-appd/src/services/kms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use sp800_185::KMac;
use oasis_runtime_sdk::{
core::common::logger::get_logger,
crypto::signature::{ed25519, secp256k1, Signer},
keymanager,
modules::rofl::app::{client::DeriveKeyRequest, prelude::*},
};

Expand Down Expand Up @@ -92,7 +91,7 @@ const OASIS_KMS_ROOT_KEY_ID: &[u8] = b"oasis-runtime-sdk/rofl-appd: root key v1"
/// A key management service backed by the Oasis runtime.
pub struct OasisKmsService<A: App> {
running: AtomicBool,
root_key: Arc<Mutex<Option<keymanager::StateKey>>>,
root_key: Arc<Mutex<Option<Vec<u8>>>>,
env: Environment<A>,
logger: slog::Logger,
}
Expand Down
16 changes: 16 additions & 0 deletions runtime-sdk/src/modules/rofl/app/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ where
self.imp.estimate_gas(req).await
}

/// Retrieves application configuration.
pub async fn app_cfg(&self) -> Result<modules::rofl::types::AppConfig> {
self.imp.app_cfg().await
}

/// Sign a given transaction, submit it and wait for block inclusion.
///
/// This method supports multiple transaction signers.
Expand Down Expand Up @@ -337,6 +342,17 @@ where
self.query(round, "core.EstimateGas", req).await
}

/// Retrieves application configuration.
async fn app_cfg(&self) -> Result<modules::rofl::types::AppConfig> {
let round = self.latest_round().await?;
self.query(
round,
"rofl.App",
modules::rofl::types::AppQuery { id: A::id() },
)
.await
}

/// Run a closure inside a `CurrentState` context with store for the given round.
async fn with_store_for_round<F, R>(&self, round: u64, f: F) -> Result<R>
where
Expand Down
56 changes: 41 additions & 15 deletions runtime-sdk/src/modules/rofl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ pub static ADDRESS_APP_STAKE_POOL: Lazy<Address> =
Lazy::new(|| Address::from_module(MODULE_NAME, "app-stake-pool"));

/// Key derivation context.
static ROFL_DERIVE_KEY_CONTEXT: &[u8] = b"oasis-runtime-sdk/rofl: derive key v1";
pub static ROFL_DERIVE_KEY_CONTEXT: &[u8] = b"oasis-runtime-sdk/rofl: derive key v1";
/// Secrets encryption key identifier.
pub static ROFL_KEY_ID_SEK: &[u8] = b"oasis-runtime-sdk/rofl: secrets encryption key v1";

pub struct Module<Cfg: Config> {
_cfg: std::marker::PhantomData<Cfg>,
Expand Down Expand Up @@ -214,12 +216,20 @@ impl<Cfg: Config> Module<Cfg> {
&Cfg::STAKE_APP_CREATE,
)?;

// Generate the secret encryption (public) key.
let sek = Self::derive_app_key(ctx, &app_id, types::KeyKind::X25519, ROFL_KEY_ID_SEK)?
.input_keypair
.pk;

// Register the application.
let cfg = types::AppConfig {
id: app_id,
policy: body.policy,
admin: Some(creator),
stake: Cfg::STAKE_APP_CREATE,
metadata: body.metadata,
sek,
..Default::default()
};
state::set_app(cfg);

Expand Down Expand Up @@ -251,13 +261,17 @@ impl<Cfg: Config> Module<Cfg> {
return Ok(());
}

// Return early if nothing has actually changed.
if cfg.policy == body.policy && cfg.admin == body.admin {
return Ok(());
// If there is no SEK defined, regenerate it.
if cfg.sek == Default::default() {
cfg.sek = Self::derive_app_key(ctx, &body.id, types::KeyKind::X25519, ROFL_KEY_ID_SEK)?
.input_keypair
.pk;
}

cfg.policy = body.policy;
cfg.admin = body.admin;
cfg.metadata = body.metadata;
cfg.secrets = body.secrets;
state::set_app(cfg);

CurrentState::with(|state| state.emit_event(Event::AppUpdated { id: body.id }));
Expand Down Expand Up @@ -378,8 +392,8 @@ impl<Cfg: Config> Module<Cfg> {
return Err(Error::PlainCallFormatNotAllowed);
}

// Currently only simple keys are supported.
if body.kind != types::KeyKind::EntropyV0 || body.generation != 0 {
// Currently only generation zero keys are supported.
if body.generation != 0 {
return Err(Error::InvalidArgument);
}

Expand All @@ -398,23 +412,35 @@ impl<Cfg: Config> Module<Cfg> {
}

// Derive application key.
let key = Self::derive_app_key(ctx, &body.app, body.kind, &body.key_id)?;
let key = match body.kind {
types::KeyKind::EntropyV0 => key.state_key.0.into(),
types::KeyKind::X25519 => key.input_keypair.sk.as_ref().into(),
};

Ok(types::DeriveKeyResponse { key })
}

fn derive_app_key<C: Context>(
ctx: &C,
app: &app_id::AppId,
kind: types::KeyKind,
key_id: &[u8],
) -> Result<keymanager::KeyPair, Error> {
let key_id = keymanager::get_key_pair_id([
ROFL_DERIVE_KEY_CONTEXT,
body.app.as_ref(),
&[body.kind as u8],
&body.key_id,
app.as_ref(),
&[kind as u8],
key_id,
]);

let km = ctx
.key_manager()
.ok_or(Error::Abort(dispatcher::Error::KeyManagerFailure(
keymanager::KeyManagerError::NotInitialized,
)))?;
let key = km
.get_or_create_keys(key_id)
.map_err(|err| Error::Abort(dispatcher::Error::KeyManagerFailure(err)))?
.state_key;

Ok(types::DeriveKeyResponse { key })
km.get_or_create_keys(key_id)
.map_err(|err| Error::Abort(dispatcher::Error::KeyManagerFailure(err)))
}

/// Verify whether the given endorsement is allowed by the application policy.
Expand Down
3 changes: 1 addition & 2 deletions runtime-sdk/src/modules/rofl/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,8 @@ mod test {

let cfg = types::AppConfig {
id: app_id,
policy: Default::default(),
admin: Some(keys::alice::address()),
stake: Default::default(),
..Default::default()
};
set_app(cfg.clone());
let app = get_app(app_id).expect("application config should be created");
Expand Down
36 changes: 30 additions & 6 deletions runtime-sdk/src/modules/rofl/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,14 @@ fn test_app_stake_pool_address() {
#[test]
fn test_management_ops() {
let mut mock = mock::Mock::default();
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(false);
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(true);

TestRuntime::migrate(&ctx);

let create = types::Create {
policy: Default::default(),
scheme: Default::default(),
metadata: BTreeMap::from([("foo".into(), "bar".into())]),
};

// Bob attempts to create a new ROFL application, but he doesn't have enough to stake.
Expand Down Expand Up @@ -125,7 +126,7 @@ fn test_management_ops() {

// Simulate round advancing as application ID is generated from it.
mock.runtime_header.round += 1;
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(false);
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(true);

// Creating another application should get a different application ID.
let dispatch_result = signer_alice.call(&ctx, "rofl.Create", create.clone());
Expand All @@ -150,6 +151,9 @@ fn test_management_ops() {
policy: create.policy,
admin: Some(keys::alice::address()),
stake: BaseUnits::new(1_000, Denomination::NATIVE),
metadata: BTreeMap::from([("foo".into(), "bar".into())]),
sek: app_cfg.sek.clone(),
..Default::default()
}
);
let instances = Module::<Config>::get_instances(app_id).unwrap();
Expand All @@ -160,6 +164,7 @@ fn test_management_ops() {
id: app_id,
policy: Default::default(),
admin: Some(keys::bob::address()), // Transfer admin to bob.
..Default::default()
};

let dispatch_result = signer_bob.call(&ctx, "rofl.Update", update.clone());
Expand Down Expand Up @@ -187,6 +192,8 @@ fn test_management_ops() {
policy: update.policy,
admin: Some(keys::bob::address()),
stake: BaseUnits::new(1_000, Denomination::NATIVE),
sek: app_cfg.sek.clone(),
..Default::default()
}
);

Expand Down Expand Up @@ -214,13 +221,13 @@ fn test_management_ops() {
#[test]
fn test_create_scheme() {
let mut mock = mock::Mock::default();
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(false);
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(true);

TestRuntime::migrate(&ctx);

let create = types::Create {
policy: Default::default(),
scheme: types::IdentifierScheme::CreatorNonce,
..Default::default()
};

let mut signer_alice = mock::Signer::new(0, keys::alice::sigspec());
Expand All @@ -243,8 +250,8 @@ fn test_key_derivation() {
TestRuntime::migrate(&ctx);

let create = types::Create {
policy: Default::default(),
scheme: types::IdentifierScheme::CreatorNonce,
..Default::default()
};

let mut signer_alice = mock::Signer::new(0, keys::alice::sigspec());
Expand Down Expand Up @@ -301,7 +308,24 @@ fn test_key_derivation() {

// In mock mode all the keys are deterministic.
let result: types::DeriveKeyResponse = cbor::from_value(dispatch_result).unwrap();
assert_eq!(result.key.as_ref(), &[0x33; 32]);
assert_eq!(&result.key, &[0x33; 32]);

// Also try X25519 derivation.
let dispatch_result = signer_alice.call_opts(
&ctx,
"rofl.DeriveKey",
types::DeriveKey {
kind: types::KeyKind::X25519,
..derive.clone()
},
CallOptions {
encrypted: true,
..Default::default()
},
);
let dispatch_result = dispatch_result.result.unwrap();
let result: types::DeriveKeyResponse = cbor::from_value(dispatch_result).unwrap();
assert!(!result.key.is_empty());

// Ensure key identifier length limit is respected.
let dispatch_result = signer_alice.call_opts(
Expand Down
23 changes: 21 additions & 2 deletions runtime-sdk/src/modules/rofl/types.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::collections::BTreeMap;

use crate::{
core::{
common::crypto::{signature, x25519},
consensus::{beacon::EpochTime, registry},
},
crypto::signature::PublicKey,
keymanager,
types::{address::Address, token},
};

Expand All @@ -17,6 +18,8 @@ pub struct Create {
pub policy: AppAuthPolicy,
/// Identifier generation scheme.
pub scheme: IdentifierScheme,
/// Metadata (arbitrary key/value pairs).
pub metadata: BTreeMap<String, String>,
}

/// ROFL application identifier generation scheme.
Expand All @@ -37,6 +40,11 @@ pub struct Update {
pub policy: AppAuthPolicy,
/// Application administrator address.
pub admin: Option<Address>,

/// Metadata (arbitrary key/value pairs).
pub metadata: BTreeMap<String, String>,
/// Secrets (arbitrary encrypted key/value pairs).
pub secrets: BTreeMap<String, Vec<u8>>,
}

/// Remove an existing ROFL application call.
Expand All @@ -58,6 +66,13 @@ pub struct AppConfig {
pub admin: Option<Address>,
/// Staked amount.
pub stake: token::BaseUnits,

/// Metadata (arbitrary key/value pairs).
pub metadata: BTreeMap<String, String>,
/// Secrets (arbitrary encrypted key/value pairs).
pub secrets: BTreeMap<String, Vec<u8>>,
/// Secret encryption public key.
pub sek: x25519::PublicKey,
}

/// Register ROFL call.
Expand All @@ -79,8 +94,12 @@ pub struct Register {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
#[repr(u8)]
pub enum KeyKind {
/// Raw entropy derivation.
#[default]
EntropyV0 = 0,

/// X25519 key pair.
X25519 = 1,
}

/// Derive key call.
Expand All @@ -100,7 +119,7 @@ pub struct DeriveKey {
#[derive(Clone, Default, cbor::Encode, cbor::Decode)]
pub struct DeriveKeyResponse {
/// Derived key.
pub key: keymanager::StateKey,
pub key: Vec<u8>,
}

/// ROFL registration descriptor.
Expand Down

0 comments on commit ee831f9

Please sign in to comment.