From a39a222bedd63a83557d7f8a172017dba592f5e1 Mon Sep 17 00:00:00 2001 From: Jiale Zhang Date: Tue, 11 Jul 2023 14:05:24 +0800 Subject: [PATCH 1/2] AA: Update TEE key when generate evidence We should not use the same pair of RSA keys for a long time, as this will bring forward security issues. This commit alleviates this issue by updating the TEE key every time the attestation is redone. Signed-off-by: Jiale Zhang --- attestation-agent/deps/crypto/src/teekey.rs | 20 +++++++++- attestation-agent/kbs_protocol/src/lib.rs | 42 ++++++++++++++------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/attestation-agent/deps/crypto/src/teekey.rs b/attestation-agent/deps/crypto/src/teekey.rs index 6bea1dbeb..232c09c81 100644 --- a/attestation-agent/deps/crypto/src/teekey.rs +++ b/attestation-agent/deps/crypto/src/teekey.rs @@ -8,7 +8,11 @@ use anyhow::*; use base64::Engine; use kbs_types::TeePubKey; -use rsa::{traits::PublicKeyParts, Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; +use rsa::pkcs1::DecodeRsaPublicKey; +use rsa::{ + traits::PublicKeyParts, PaddingScheme, Pkcs1v15Encrypt, PublicKeyParts, RsaPrivateKey, + RsaPublicKey, +}; use sha2::{Digest, Sha384}; const RSA_PUBKEY_LENGTH: usize = 2048; @@ -69,3 +73,17 @@ pub fn hash_chunks(chunks: Vec>) -> Vec { hasher.finalize().to_vec() } + +// Convert PKCS#1 PEM public key to TeePubKey +pub fn pkcs1_pem_to_teepubkey(pem: String) -> Result { + let public_key = RsaPublicKey::from_pkcs1_pem(&pem)?; + let k_mod = base64::encode(public_key.n().to_bytes_be()); + let k_exp = base64::encode(public_key.e().to_bytes_be()); + let tee_pubkey = TeePubKey { + kty: RSA_KEY_TYPE.to_string(), + alg: RSA_ALGORITHM.to_string(), + k_mod, + k_exp, + }; + Ok(tee_pubkey) +} diff --git a/attestation-agent/kbs_protocol/src/lib.rs b/attestation-agent/kbs_protocol/src/lib.rs index fc74747d5..48b459f73 100644 --- a/attestation-agent/kbs_protocol/src/lib.rs +++ b/attestation-agent/kbs_protocol/src/lib.rs @@ -7,8 +7,8 @@ use anyhow::*; use async_trait::async_trait; use attester::{detect_tee_type, Attester, Tee}; use core::time::Duration; -use crypto::{hash_chunks, TeeKey}; -use kbs_types::{Attestation, ErrorInformation}; +use crypto::{hash_chunks, pkcs1_pem_to_teepubkey, TeeKey}; +use kbs_types::{Attestation, ErrorInformation, TeePubKey}; use std::convert::TryFrom; use types::*; use url::Url; @@ -25,7 +25,7 @@ pub trait KbsRequest { /// Get confidential resource async fn http_get(&mut self, resource_url: String) -> Result>; /// Attestation and get attestation results token (Base64 endcoded) - async fn attest(&mut self, host_url: String) -> Result; + async fn attest(&mut self, url: String, tee_pubkey_pem: Option) -> Result; } type BoxedAttester = Box; @@ -111,6 +111,8 @@ impl KbsProtocolWrapper { Ok(KbsProtocolWrapper { tee: tee_type.to_string(), + tee_key: None, + nonce: String::default(), attester, tee_key, nonce: None, @@ -119,11 +121,11 @@ impl KbsProtocolWrapper { }) } - fn generate_evidence(&self) -> Result { - let tee_pubkey = self - .tee_key - .export_pubkey() - .map_err(|e| anyhow!("Export TEE pubkey failed: {:?}", e))?; + fn generate_evidence(&self, tee_pubkey: TeePubKey) -> Result { + let attester = self + .attester + .as_ref() + .ok_or_else(|| anyhow!("TEE attester missed"))?; let nonce = self .nonce @@ -161,7 +163,11 @@ impl KbsProtocolWrapper { &mut self.http_client } - async fn attestation(&mut self, kbs_root_url: String) -> Result { + async fn attestation( + &mut self, + kbs_root_url: String, + tee_pubkey_pem: Option, + ) -> Result { let challenge = self .http_client() .post(format!("{kbs_root_url}{KBS_PREFIX}/auth")) @@ -173,11 +179,21 @@ impl KbsProtocolWrapper { .await?; self.nonce = Some(challenge.nonce.clone()); + let pubkey = match tee_pubkey_pem { + Some(k) => pkcs1_pem_to_teepubkey(k)?, + None => { + let key = TeeKey::new()?; + self.tee_key = Some(key.clone()); + key.export_pubkey() + .map_err(|e| anyhow!("Export TEE pubkey failed: {:?}", e))? + } + }; + let attest_response = self .http_client() .post(format!("{kbs_root_url}{KBS_PREFIX}/attest")) .header("Content-Type", "application/json") - .json(&self.generate_evidence()?) + .json(&self.generate_evidence(pubkey)?) .send() .await?; @@ -213,7 +229,7 @@ impl KbsRequest for KbsProtocolWrapper { log::info!("CC-KBC: trying to request KBS, attempt {attempt}"); if !self.authenticated { - self.attestation(root_url.clone()).await?; + self.attestation(root_url.clone(), None).await?; } let res = self.http_client().get(&url_string).send().await?; @@ -243,8 +259,8 @@ impl KbsRequest for KbsProtocolWrapper { bail!("Request KBS: Attested but KBS still return Unauthorized") } - async fn attest(&mut self, host_url: String) -> Result { - self.attestation(host_url).await + async fn attest(&mut self, url: String, tee_pubkey_pem: Option) -> Result { + self.attestation(url, tee_pubkey_pem).await } } From d706e1eb1ad33fae851fdc392a41e9b4992ee56f Mon Sep 17 00:00:00 2001 From: Jiale Zhang Date: Tue, 11 Jul 2023 14:33:11 +0800 Subject: [PATCH 2/2] AA: Allow setting custom KBS certificates to enable TLS Add parameters for setting customized KBS Root certificate when creating HTTP client, so that TLS communication can be enabled in various deployment scenarios. Signed-off-by: Jiale Zhang --- attestation-agent/attester/src/lib.rs | 2 +- attestation-agent/deps/crypto/src/teekey.rs | 10 ++- attestation-agent/kbc/src/cc_kbc/mod.rs | 2 +- attestation-agent/kbs_protocol/src/lib.rs | 68 ++++++++++++++++----- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/attestation-agent/attester/src/lib.rs b/attestation-agent/attester/src/lib.rs index cc5d62e7f..463f78ff5 100644 --- a/attestation-agent/attester/src/lib.rs +++ b/attestation-agent/attester/src/lib.rs @@ -28,7 +28,7 @@ pub mod snp; /// - AzSnpVtpm: SEV-SNP TEE for Azure CVMs. /// - Snp: SEV-SNP TEE. /// - Sample: A dummy TEE that used to test/demo the KBC functionalities. -#[derive(Debug, EnumString, Display)] +#[derive(Debug, EnumString, Display, Clone, Copy)] #[strum(ascii_case_insensitive, serialize_all = "lowercase")] pub enum Tee { Tdx, diff --git a/attestation-agent/deps/crypto/src/teekey.rs b/attestation-agent/deps/crypto/src/teekey.rs index 232c09c81..c9bf2c170 100644 --- a/attestation-agent/deps/crypto/src/teekey.rs +++ b/attestation-agent/deps/crypto/src/teekey.rs @@ -6,13 +6,11 @@ //! Implementations of the TeeKey use anyhow::*; +use base64::engine::general_purpose::STANDARD; use base64::Engine; use kbs_types::TeePubKey; use rsa::pkcs1::DecodeRsaPublicKey; -use rsa::{ - traits::PublicKeyParts, PaddingScheme, Pkcs1v15Encrypt, PublicKeyParts, RsaPrivateKey, - RsaPublicKey, -}; +use rsa::{traits::PublicKeyParts, Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; use sha2::{Digest, Sha384}; const RSA_PUBKEY_LENGTH: usize = 2048; @@ -77,8 +75,8 @@ pub fn hash_chunks(chunks: Vec>) -> Vec { // Convert PKCS#1 PEM public key to TeePubKey pub fn pkcs1_pem_to_teepubkey(pem: String) -> Result { let public_key = RsaPublicKey::from_pkcs1_pem(&pem)?; - let k_mod = base64::encode(public_key.n().to_bytes_be()); - let k_exp = base64::encode(public_key.e().to_bytes_be()); + let k_mod = STANDARD.encode(public_key.n().to_bytes_be()); + let k_exp = STANDARD.encode(public_key.e().to_bytes_be()); let tee_pubkey = TeePubKey { kty: RSA_KEY_TYPE.to_string(), alg: RSA_ALGORITHM.to_string(), diff --git a/attestation-agent/kbc/src/cc_kbc/mod.rs b/attestation-agent/kbc/src/cc_kbc/mod.rs index 92c0ba1ae..dd09c5ec8 100644 --- a/attestation-agent/kbc/src/cc_kbc/mod.rs +++ b/attestation-agent/kbc/src/cc_kbc/mod.rs @@ -62,7 +62,7 @@ impl Kbc { Ok(Kbc { kbs_uri: url, token: None, - kbs_protocol_wrapper: KbsProtocolWrapper::new().unwrap(), + kbs_protocol_wrapper: KbsProtocolWrapper::new(vec![]).unwrap(), }) } diff --git a/attestation-agent/kbs_protocol/src/lib.rs b/attestation-agent/kbs_protocol/src/lib.rs index 48b459f73..439bd1b42 100644 --- a/attestation-agent/kbs_protocol/src/lib.rs +++ b/attestation-agent/kbs_protocol/src/lib.rs @@ -45,15 +45,24 @@ pub struct NoAttester; pub struct KbsProtocolWrapperBuilder { tee: T, attester: A, + kbs_certs: Vec, } type WrapperBuilder = KbsProtocolWrapperBuilder; +impl WrapperBuilder { + pub fn add_kbs_cert(mut self, cert_pem: String) -> Self { + self.kbs_certs.push(cert_pem); + self + } +} + impl WrapperBuilder { #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { tee: NoTee, attester: NoAttester, + kbs_certs: vec![], } } @@ -61,6 +70,7 @@ impl WrapperBuilder { WrapperBuilder { tee, attester: self.attester, + kbs_certs: self.kbs_certs, } } } @@ -70,6 +80,7 @@ impl WrapperBuilder { WrapperBuilder { tee: self.tee, attester, + kbs_certs: self.kbs_certs, } } } @@ -94,7 +105,7 @@ impl WrapperBuilder { impl TryFrom> for KbsProtocolWrapper { type Error = Error; fn try_from(builder: WrapperBuilder) -> Result { - let mut wrapper = Self::new()?; + let mut wrapper = Self::new(builder.kbs_certs)?; wrapper.tee = builder.tee.to_string(); wrapper.attester = Some(builder.attester); Ok(wrapper) @@ -102,7 +113,7 @@ impl TryFrom> for KbsProtocolWrapper { } impl KbsProtocolWrapper { - pub fn new() -> Result { + pub fn new(kbs_root_certs_pem: Vec) -> Result { let tee_key = TeeKey::new().map_err(|e| anyhow!("Generate TEE key failed: {e}"))?; // Detect TEE type of the current platform. let tee_type = detect_tee_type(); @@ -111,22 +122,15 @@ impl KbsProtocolWrapper { Ok(KbsProtocolWrapper { tee: tee_type.to_string(), - tee_key: None, - nonce: String::default(), attester, tee_key, nonce: None, - http_client: build_http_client()?, + http_client: build_http_client(kbs_root_certs_pem)?, authenticated: false, }) } fn generate_evidence(&self, tee_pubkey: TeePubKey) -> Result { - let attester = self - .attester - .as_ref() - .ok_or_else(|| anyhow!("TEE attester missed"))?; - let nonce = self .nonce .to_owned() @@ -183,9 +187,11 @@ impl KbsProtocolWrapper { Some(k) => pkcs1_pem_to_teepubkey(k)?, None => { let key = TeeKey::new()?; - self.tee_key = Some(key.clone()); - key.export_pubkey() - .map_err(|e| anyhow!("Export TEE pubkey failed: {:?}", e))? + let key_exp = key + .export_pubkey() + .map_err(|e| anyhow!("Export TEE pubkey failed: {:?}", e))?; + self.tee_key = key; + key_exp } }; @@ -264,14 +270,44 @@ impl KbsRequest for KbsProtocolWrapper { } } -fn build_http_client() -> Result { - reqwest::Client::builder() +fn build_http_client(kbs_root_certs_pem: Vec) -> Result { + let mut client_builder = reqwest::Client::builder() .cookie_store(true) .user_agent(format!( "attestation-agent-kbs-client/{}", env!("CARGO_PKG_VERSION") )) - .timeout(Duration::from_secs(KBS_REQ_TIMEOUT_SEC)) + .timeout(Duration::from_secs(KBS_REQ_TIMEOUT_SEC)); + + for custom_root_cert in kbs_root_certs_pem.iter() { + let cert = reqwest::Certificate::from_pem(custom_root_cert.as_bytes())?; + client_builder = client_builder.add_root_certificate(cert); + } + + client_builder .build() .map_err(|e| anyhow!("Build KBS http client failed: {:?}", e)) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_http_client() { + let test_certs = vec!["-----BEGIN CERTIFICATE----- +MIIBzTCCAX+gAwIBAgIUOGdGRmt/IDSVIem7iFwsuxnV62AwBQYDK2VwMGkxCzAJ +BgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkx +EDAOBgNVBAoMB0FsaWJhYmExDzANBgNVBAsMBkFsaXl1bjERMA8GA1UEAwwIS0JT +LXJvb3QwHhcNMjMwNzE0MDYzMzA1WhcNMjMwODEzMDYzMzA1WjBpMQswCQYDVQQG +EwJDTjERMA8GA1UECAwIU2hhbmdoYWkxETAPBgNVBAcMCFNoYW5naGFpMRAwDgYD +VQQKDAdBbGliYWJhMQ8wDQYDVQQLDAZBbGl5dW4xETAPBgNVBAMMCEtCUy1yb290 +MCowBQYDK2VwAyEAOo8z6/Ul3XvNBf2Oa7qDevljyhGSKyGMjV+4qneVNr+jOTA3 +MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdDgQWBBREKNLFRe7fCBKRffTv +x13TMfDeczAFBgMrZXADQQBpP6ABBkzVj3mF55nWUtP5vxwq3t91wqQJ6NyC7WsT +3Z29bFfJn7C280JfkCqiqeSZjYV/JjTepATH659kktcA +-----END CERTIFICATE-----" + .to_string()]; + assert!(build_http_client(test_certs).is_ok()); + } +}