diff --git a/Cargo.lock b/Cargo.lock index 43a6d896..fca31445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1654,10 +1654,12 @@ version = "0.1.0" dependencies = [ "anyhow", "hex", + "hex_fmt", "kms-rpc", "ra-rpc", "ra-tls", "rocket", + "serde", "tracing", "tracing-subscriber", "yasna", diff --git a/kms-rpc/proto/kms_rpc.proto b/kms-rpc/proto/kms_rpc.proto index b7cfdc7b..6f5332ed 100644 --- a/kms-rpc/proto/kms_rpc.proto +++ b/kms-rpc/proto/kms_rpc.proto @@ -15,6 +15,5 @@ service KMS { message AppKeyResponse { string app_key = 1; string disk_crypt_key = 2; - string certificate_chain = 3; - string comment = 4; + repeated string certificate_chain = 3; } diff --git a/kms/Cargo.toml b/kms/Cargo.toml index 06c3a8d8..3c875541 100644 --- a/kms/Cargo.toml +++ b/kms/Cargo.toml @@ -6,10 +6,12 @@ edition = "2021" [dependencies] anyhow = "1.0.88" hex = "0.4.3" +hex_fmt = "0.3.0" kms-rpc = { version = "0.1.0", path = "../kms-rpc" } ra-rpc = { version = "0.1.0", path = "../ra-rpc" } ra-tls = { version = "0.1.0", path = "../ra-tls" } rocket = { git = "https://github.com/rwf2/Rocket", branch = "master", features = ["mtls"] } +serde = { version = "1.0.210", features = ["derive"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" yasna = "0.5.2" diff --git a/kms/assets/tmp-ca.cert b/kms/assets/tmp-ca.cert new file mode 100644 index 00000000..64773d4c --- /dev/null +++ b/kms/assets/tmp-ca.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCCATGgAwIBAgIUWXh1RpxZtF5Jp5cKhqhTYsRoEckwCgYIKoZIzj0EAwIw +LzEWMBQGA1UECgwNUGhhbGEgTmV0d29yazEVMBMGA1UEAwwMUGhhbGEgS01TIENB +MCAXDTc1MDEwMTAwMDAwMFoYDzQwOTYwMTAxMDAwMDAwWjAjMSEwHwYDVQQDDBhQ +aGFsYSBLTVMgQ2xpZW50IFRlbXAgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AASBvSl/4NYZDSE5fw0iH0mYNl23zCOQ5EzkmNM5C/05tMHUhmMFnqH1QNq/JkAi +TkRxWYtpuKF0VayktddtUORhozUwMzAdBgNVHQ4EFgQUWfxxVFm7zz1v/Olvr691 +WhWl7LswEgYDVR0TAQH/BAgwBgEB/wIBATAKBggqhkjOPQQDAgNJADBGAiEA63HF +gJl9PLKhhaGf745E0JklnEVp6oFYdYPEZGLybEoCIQDu5oDNww9pu60GXJ28K3Lf +ihCFs0CQqf9RT9gfS71h8A== +-----END CERTIFICATE----- diff --git a/kms/assets/tmp-ca.key b/kms/assets/tmp-ca.key new file mode 100644 index 00000000..869df600 --- /dev/null +++ b/kms/assets/tmp-ca.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgp0RkgbxCg0ytHj+f +Ox8zsDT4tvFCdUGqYLfEnkRFXsehRANCAASBvSl/4NYZDSE5fw0iH0mYNl23zCOQ +5EzkmNM5C/05tMHUhmMFnqH1QNq/JkAiTkRxWYtpuKF0VayktddtUORh +-----END PRIVATE KEY----- diff --git a/kms/kms.toml b/kms/kms.toml index 0822aa6b..1af6d6bd 100644 --- a/kms/kms.toml +++ b/kms/kms.toml @@ -5,13 +5,15 @@ ident = "Phala KMS" temp_dir = "/tmp" keep_alive = 10 log_level = "debug" +address = "0.0.0.0" +port = 8000 [default.tls] key = "assets/kms-www.key" certs = "assets/kms-www.cert" [default.tls.mutual] -ca_certs = "assets/ca.cert" +ca_certs = "assets/tmp-ca.cert" mandatory = false [default.limits] @@ -23,9 +25,10 @@ json = "1MiB" msgpack = "1MiB" string = "8KiB" -[public] -address = "0.0.0.0" -port = 8000 +[core] +root_ca_cert = "assets/ca.cert" +root_ca_key = "assets/ca.key" +subject_postfix = ".phala.network" [core.allowed_mr] mrtd = [ @@ -40,6 +43,3 @@ rtmr1 = [ rtmr2 = [ "569755c97d572d9ba2de9a655e9e786ae1cb3f73d28b6047d79678cce24036adba9fb080547e84b2ec98910032828ddd", ] -rtmr3 = [ - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", -] diff --git a/kms/src/config.rs b/kms/src/config.rs index 46731592..ca50a02d 100644 --- a/kms/src/config.rs +++ b/kms/src/config.rs @@ -1,8 +1,9 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use rocket::figment::{ providers::{Format, Toml}, Figment, }; +use serde::Deserialize; pub const CONFIG_FILENAME: &str = "kms.toml"; pub const SYSTEM_CONFIG_FILENAME: &str = "/etc/kms/kms.toml"; @@ -15,19 +16,18 @@ pub fn load_config_figment() -> Figment { .merge(Toml::file(CONFIG_FILENAME).nested()) } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize)] pub(crate) struct KmsConfig { pub allowed_mr: AllowedMr, + pub root_ca_cert: String, + pub root_ca_key: String, + pub subject_postfix: String, } impl KmsConfig { pub fn load() -> Result { let figment = load_config_figment(); - Self::from_figment(figment.select("core")) - } - pub fn from_figment(figment: Figment) -> Result { - let allowed_mr = AllowedMr::from_figment(figment.focus("allowed_mr"))?; - Ok(Self { allowed_mr }) + Ok(figment.select("core").extract()?) } } @@ -37,36 +37,41 @@ pub(crate) struct AllowedMr { pub rtmr0: Vec<[u8; 48]>, pub rtmr1: Vec<[u8; 48]>, pub rtmr2: Vec<[u8; 48]>, - pub rtmr3: Vec<[u8; 48]>, } -impl AllowedMr { - pub fn from_figment(figment: Figment) -> Result { - fn read_mrlist(figment: &Figment, name: &str) -> Result> { - let list = figment - .extract_inner::>(name) - .unwrap_or_default() - .into_iter() +impl<'de> Deserialize<'de> for AllowedMr { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct RawAllowedMr { + mrtd: Vec, + rtmr0: Vec, + rtmr1: Vec, + rtmr2: Vec, + } + + let raw = RawAllowedMr::deserialize(deserializer)?; + + fn parse_mrlist<'d, D: serde::Deserializer<'d>>( + list: Vec, + ) -> Result, D::Error> { + list.into_iter() .map(|s| { - let bytes = hex::decode(s)?; - let mr: [u8; 48] = bytes.try_into().ok().context("invalid MR config")?; - Ok(mr) + let bytes = hex::decode(&s).map_err(serde::de::Error::custom)?; + bytes + .try_into() + .map_err(|_| serde::de::Error::custom("invalid MR config")) }) - .collect::>>()?; - Ok(list) + .collect() } - let mrtd = read_mrlist(&figment, "mrtd")?; - let rtmr0 = read_mrlist(&figment, "rtmr0")?; - let rtmr1 = read_mrlist(&figment, "rtmr1")?; - let rtmr2 = read_mrlist(&figment, "rtmr2")?; - let rtmr3 = read_mrlist(&figment, "rtmr3")?; - Ok(Self { - mrtd, - rtmr0, - rtmr1, - rtmr2, - rtmr3, + Ok(AllowedMr { + mrtd: parse_mrlist::(raw.mrtd)?, + rtmr0: parse_mrlist::(raw.rtmr0)?, + rtmr1: parse_mrlist::(raw.rtmr1)?, + rtmr2: parse_mrlist::(raw.rtmr2)?, }) } } diff --git a/kms/src/main.rs b/kms/src/main.rs index f1b7815a..5274f759 100644 --- a/kms/src/main.rs +++ b/kms/src/main.rs @@ -12,11 +12,11 @@ async fn main() -> Result<()> { info!("Starting KMS"); info!("Supported methods:"); for method in main_service::rpc_methods() { - info!(" {method}"); + info!(" /prpc/{method}"); } let config = config::KmsConfig::load().context("Failed to read config file")?; - let state = main_service::KmsState::new(config); + let state = main_service::KmsState::new(config).context("Failed to initialize KMS state")?; let figment = config::load_config_figment().select("public"); let rocket = rocket::custom(figment) diff --git a/kms/src/main_service.rs b/kms/src/main_service.rs index 9e7a07a4..d710035f 100644 --- a/kms/src/main_service.rs +++ b/kms/src/main_service.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; use kms_rpc::{ kms_server::{KmsRpc, KmsServer}, AppKeyResponse, @@ -8,8 +8,11 @@ use kms_rpc::{ use ra_rpc::RpcCall; use ra_tls::{ attestation::Attestation, + cert::{CaCert, CertRequest}, + kdf::derive_ecdsa_key_pair, qvl::quote::{Report, TDReport10}, }; +use tracing::info; use crate::config::{AllowedMr, KmsConfig}; @@ -20,13 +23,23 @@ pub struct KmsState { struct KmsStateInner { config: KmsConfig, + root_ca: CaCert, } impl KmsState { - pub fn new(config: KmsConfig) -> Self { - Self { - inner: Arc::new(KmsStateInner { config }), - } + fn lock(&self) -> &KmsStateInner { + &self.inner + } + + pub fn new(config: KmsConfig) -> Result { + let ca_cert = CaCert::load(&config.root_ca_cert, &config.root_ca_key) + .context("Failed to load root CA certificate")?; + Ok(Self { + inner: Arc::new(KmsStateInner { + config, + root_ca: ca_cert, + }), + }) } } @@ -41,19 +54,9 @@ impl AllowedMr { && self.rtmr0.contains(&report.rt_mr0) && self.rtmr1.contains(&report.rt_mr1) && self.rtmr2.contains(&report.rt_mr2) - && self.rtmr3.contains(&report.rt_mr3) } } -#[derive(Debug)] -struct AttestedInfo { - mrtd: [u8; 48], - rtmr0: [u8; 48], - rtmr1: [u8; 48], - rtmr2: [u8; 48], - rtmr3: [u8; 48], -} - impl RpcHandler { fn ensure_attested(&self) -> Result<&Attestation> { let Some(attestation) = &self.attestation else { @@ -66,6 +69,12 @@ impl RpcHandler { Report::TD10(r) => r, Report::TD15(r) => r.base, }; + info!("Incoming report:"); + info!("MRTD: {:?}", hex_fmt::HexFmt(&report.mr_td)); + info!("RTMR0: {:?}", hex_fmt::HexFmt(&report.rt_mr0)); + info!("RTMR1: {:?}", hex_fmt::HexFmt(&report.rt_mr1)); + info!("RTMR2: {:?}", hex_fmt::HexFmt(&report.rt_mr2)); + info!("RTMR3: {:?}", hex_fmt::HexFmt(&report.rt_mr3)); if !self.state.inner.config.allowed_mr.is_allowed(&report) { bail!("Forbidden MR"); @@ -77,11 +86,40 @@ impl RpcHandler { impl KmsRpc for RpcHandler { async fn get_app_key(self) -> Result { let attest = self.ensure_attested()?; + let app_id = attest.decode_app_id().context("Failed to decode app ID")?; + let state = self.state.lock(); + + let app_key = derive_ecdsa_key_pair( + &state.root_ca.key, + &[app_id.as_bytes(), "app-key".as_bytes()], + ) + .context("Failed to derive app key")?; + + let app_disk_key = derive_ecdsa_key_pair( + &state.root_ca.key, + &[app_id.as_bytes(), "app-disk-key".as_bytes()], + ) + .context("Failed to derive app disk key")?; + + let subject = format!("{app_id}{}", state.config.subject_postfix); + let req = CertRequest::builder() + .subject(&subject) + .ca_level(1) + .quote(&attest.quote) + .event_log(&attest.event_log) + .app_info(&attest.app_info) + .key(&app_key) + .build(); + + let cert = state + .root_ca + .sign(req) + .context("Failed to sign certificate")?; + Ok(AppKeyResponse { - app_key: "example app key".to_string(), - disk_crypt_key: "example disk crypt key".to_string(), - certificate_chain: "example chain".to_string(), - comment: hex::encode(&attest.quote), + app_key: app_key.serialize_pem(), + disk_crypt_key: app_disk_key.serialize_pem(), + certificate_chain: vec![cert.pem(), state.root_ca.cert.pem()], }) } } diff --git a/tappd/src/rpc_service.rs b/tappd/src/rpc_service.rs index cbe5d1d5..a57d4e38 100644 --- a/tappd/src/rpc_service.rs +++ b/tappd/src/rpc_service.rs @@ -1,14 +1,11 @@ use std::sync::Arc; use anyhow::{Context, Result}; -use fs_err as fs; use ra_rpc::{Attestation, RpcCall}; use ra_tls::{ cert::{CaCert, CertRequest}, kdf::derive_ecdsa_key_pair, - rcgen, }; -use rcgen::{Certificate, CertificateParams, KeyPair}; use tappd_rpc::{ tappd_server::{TappdRpc, TappdServer}, DeriveKeyArgs, DeriveKeyResponse, TdxQuoteArgs, TdxQuoteResponse,