From 5bb67c35eb85efd8cb7b37af60c53d56a9d27855 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Tue, 17 Dec 2024 16:34:02 +0800 Subject: [PATCH] kbs_protocol: Update protocol to v0.2.0 This patch updates KBS protocol to v0.2.0. The change mainly includes 1. Replace RSA-PKCS1v15 to ECDH-ES-A256KW. The former algorithm is not declared as deprecated in https://www.ietf.org/archive/id/draft-madden-jose-deprecate-none-rsa15-00.html#section-1.2 Also, some fixups to make the KBS protocol's Response fully compatible with JWE standard are made, including explicitly parse `tag` in the flattened JSON serialization. Signed-off-by: Xynnn007 --- Cargo.lock | 38 ++-- Cargo.toml | 2 +- .../attestation-agent/src/token/kbs.rs | 2 +- attestation-agent/kbs_protocol/src/builder.rs | 2 +- .../kbs_protocol/src/client/mod.rs | 2 +- attestation-agent/kbs_protocol/src/keypair.rs | 163 +++++++++++++----- .../kbs_protocol/src/token_provider/aa/mod.rs | 2 +- 7 files changed, 147 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85e4695b0..359a10a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,7 +383,7 @@ dependencies = [ "sha2 0.10.8", "strum", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.9", "tokio", "toml 0.8.20", "tonic", @@ -423,7 +423,7 @@ dependencies = [ "strum", "tdx-attest-rs", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.9", "tokio", ] @@ -506,7 +506,7 @@ dependencies = [ "serde_json", "sev 4.0.0", "sha2 0.10.8", - "thiserror 2.0.11", + "thiserror 2.0.9", "tss-esapi", "zerocopy 0.7.32", ] @@ -522,7 +522,7 @@ dependencies = [ "clap 4.2.7", "serde", "sev 4.0.0", - "thiserror 2.0.11", + "thiserror 2.0.9", "ureq", ] @@ -1183,7 +1183,7 @@ dependencies = [ "sha2 0.10.8", "strum", "tempfile", - "thiserror 2.0.11", + "thiserror 2.0.9", "tokio", "toml 0.8.20", "tonic", @@ -3359,7 +3359,7 @@ dependencies = [ "serde", "serde_json", "superboring", - "thiserror 2.0.11", + "thiserror 2.0.9", "zeroize", ] @@ -3405,12 +3405,14 @@ dependencies = [ [[package]] name = "kbs-types" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6441ed73b0faa50707d4de41c6b45c76654b661b96aaf7b26a41331eedc0a5" +checksum = "6db954f164e19a63a1eb7c04c46511167d68a5eb5025d4a435c1ba297f00dbf4" dependencies = [ + "base64 0.22.1", "serde", "serde_json", + "thiserror 2.0.9", ] [[package]] @@ -3437,7 +3439,7 @@ dependencies = [ "sha2 0.10.8", "tempfile", "testcontainers", - "thiserror 2.0.11", + "thiserror 2.0.9", "tokio", "ttrpc", "ttrpc-codegen", @@ -6459,11 +6461,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.9", ] [[package]] @@ -6479,9 +6481,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", @@ -7087,9 +7089,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "utoipa" -version = "5.3.1" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +checksum = "68e76d357bc95c7d0939c92c04c9269871a8470eea39cb1f0231eeadb0c47d0f" dependencies = [ "indexmap 2.7.1", "serde", @@ -7099,9 +7101,9 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.3.1" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +checksum = "564b03f8044ad6806bdc0d635e88be24967e785eef096df6b2636d2cc1e05d4b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index bbd90aa2b..2f4afffd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ hmac = "0.12.1" jwt-simple = { version = "0.12", default-features = false, features = [ "pure-rust", ] } -kbs-types = "0.7.0" +kbs-types = "0.10.0" log = "0.4.25" nix = "0.29" openssl = "0.10" diff --git a/attestation-agent/attestation-agent/src/token/kbs.rs b/attestation-agent/attestation-agent/src/token/kbs.rs index 16adb42ed..ebe2db07f 100644 --- a/attestation-agent/attestation-agent/src/token/kbs.rs +++ b/attestation-agent/attestation-agent/src/token/kbs.rs @@ -40,7 +40,7 @@ impl GetToken for KbsTokenGetter { let (token, tee_keypair) = client.get_token().await?; let message = Message { token: token.content, - tee_keypair: tee_keypair.to_pkcs1_pem()?.to_string(), + tee_keypair: tee_keypair.to_pem()?.to_string(), }; let res = serde_json::to_vec(&message)?; diff --git a/attestation-agent/kbs_protocol/src/builder.rs b/attestation-agent/kbs_protocol/src/builder.rs index 203ad10dc..11ef105fd 100644 --- a/attestation-agent/kbs_protocol/src/builder.rs +++ b/attestation-agent/kbs_protocol/src/builder.rs @@ -91,7 +91,7 @@ impl KbsClientBuilder { } let tee_key = match self.tee_key { - Some(key) => TeeKeyPair::from_pkcs1_pem(&key[..]).context("read tee public key")?, + Some(key) => TeeKeyPair::from_pem(&key[..]).context("read tee public key")?, None => TeeKeyPair::new()?, }; diff --git a/attestation-agent/kbs_protocol/src/client/mod.rs b/attestation-agent/kbs_protocol/src/client/mod.rs index 5febb6911..fdb7f23db 100644 --- a/attestation-agent/kbs_protocol/src/client/mod.rs +++ b/attestation-agent/kbs_protocol/src/client/mod.rs @@ -48,7 +48,7 @@ pub struct KbsClient { pub(crate) token: Option, } -pub const KBS_PROTOCOL_VERSION: &str = "0.1.1"; +pub const KBS_PROTOCOL_VERSION: &str = "0.2.0"; pub const KBS_GET_RESOURCE_MAX_ATTEMPT: u64 = 3; diff --git a/attestation-agent/kbs_protocol/src/keypair.rs b/attestation-agent/kbs_protocol/src/keypair.rs index 897724e3e..399047e0f 100644 --- a/attestation-agent/kbs_protocol/src/keypair.rs +++ b/attestation-agent/kbs_protocol/src/keypair.rs @@ -3,80 +3,161 @@ // SPDX-License-Identifier: Apache-2.0 // -use anyhow::{Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use crypto::{ + ec::{Curve, EcKeyPair, KeyWrapAlgorithm}, rsa::{PaddingMode, RSAKeyPair}, - WrapType, }; -use kbs_types::{Response, TeePubKey}; -use serde::Deserialize; +use kbs_types::{ProtectedHeader, Response, TeePubKey}; +use log::warn; use zeroize::Zeroizing; #[derive(Clone, Debug)] pub struct TeeKeyPair { - keypair: RSAKeyPair, + key: TeeKey, +} + +#[derive(Clone, Debug)] +pub enum TeeKey { + Rsa(Box), + Ec(Box), } impl TeeKeyPair { + /// Create a new Tee key pair. We by default to use EC key pair. pub fn new() -> Result { - Ok(Self { - keypair: RSAKeyPair::new()?, - }) + let key = TeeKey::Ec(Box::default()); + Ok(Self { key }) } /// Export TEE public key as specific structure. pub fn export_pubkey(&self) -> Result { - let k_mod = URL_SAFE_NO_PAD.encode(self.keypair.n()); - let k_exp = URL_SAFE_NO_PAD.encode(self.keypair.e()); + match &self.key { + TeeKey::Rsa(key) => { + let k_mod = URL_SAFE_NO_PAD.encode(key.n()); + let k_exp = URL_SAFE_NO_PAD.encode(key.e()); - Ok(TeePubKey::RSA { - alg: PaddingMode::PKCS1v15.as_ref().to_string(), - k_mod, - k_exp, - }) + Ok(TeePubKey::RSA { + alg: PaddingMode::OAEP.as_ref().to_string(), + k_mod, + k_exp, + }) + } + TeeKey::Ec(key) => { + let x = URL_SAFE_NO_PAD.encode(key.x()?); + let y = URL_SAFE_NO_PAD.encode(key.y()?); + + Ok(TeePubKey::EC { + crv: Curve::P256.as_ref().to_string(), + alg: KeyWrapAlgorithm::EcdhEsA256Kw.as_ref().to_string(), + x, + y, + }) + } + } } #[inline] - pub fn decrypt(&self, mode: PaddingMode, cipher_text: Vec) -> Result> { - self.keypair.decrypt(mode, cipher_text) + pub fn unwrap_cek(&self, header: &ProtectedHeader, wrapped_cek: Vec) -> Result> { + #[allow(deprecated)] + if &header.alg[..] == PaddingMode::PKCS1v15.as_ref() { + warn!("Use deprecated Rsa PKCSv1.5 algorithm!"); + let TeeKey::Rsa(key) = &self.key else { + bail!("Unmatched key. Must be RSA key"); + }; + + let cek = key.decrypt(PaddingMode::PKCS1v15, wrapped_cek)?; + Ok(cek) + } else if &header.alg[..] == PaddingMode::OAEP.as_ref() { + let TeeKey::Rsa(key) = &self.key else { + bail!("Unmatched key. Must be RSA key"); + }; + + let cek = key.decrypt(PaddingMode::OAEP, wrapped_cek)?; + Ok(cek) + } else if &header.alg[..] == KeyWrapAlgorithm::EcdhEsA256Kw.as_ref() { + let epk = header + .other_fields + .get("epk") + .ok_or(anyhow!("Invalid JWE ProtectedHeader. Without `epk`"))?; + let crv = epk + .get("crv") + .ok_or(anyhow!("Invalid JWE ProtectedHeader. Without `crv`"))? + .as_str() + .ok_or(anyhow!( + "Invalid JWE ProtectedHeader. `crv` is not a string" + ))?; + + let x = epk + .get("x") + .ok_or(anyhow!("Invalid JWE ProtectedHeader. Without `x`"))? + .as_str() + .ok_or(anyhow!("Invalid JWE ProtectedHeader. `x` is not a string"))?; + + let x = URL_SAFE_NO_PAD.decode(x)?; + + let y = epk + .get("y") + .ok_or(anyhow!("Invalid JWE ProtectedHeader. Without `y`"))? + .as_str() + .ok_or(anyhow!("Invalid JWE ProtectedHeader. `y` is not a string"))?; + + let y = URL_SAFE_NO_PAD.decode(y)?; + + let TeeKey::Ec(key) = &self.key else { + bail!("Unmatched key. Must be EC key"); + }; + + if crv != key.curve().as_ref() { + bail!("Unmatched curve: {}", crv); + } + + let cek = key.unwrap_key(wrapped_cek, x, y, KeyWrapAlgorithm::EcdhEsA256Kw)?; + Ok(cek) + } else { + bail!("Unsupported algorithm: {}", header.alg) + } } #[inline] - pub fn from_pkcs1_pem(pem: &str) -> Result { - let keypair = RSAKeyPair::from_pkcs1_pem(pem)?; - Ok(Self { keypair }) + pub fn from_pem(pem: &str) -> Result { + if let Ok(keypair) = RSAKeyPair::from_pkcs1_pem(pem) { + return Ok(Self { + key: TeeKey::Rsa(Box::new(keypair)), + }); + } + + let keypair = EcKeyPair::from_pkcs8_pem(pem) + .context("private key is not RSA (PKCS#1) nor EC P256 (PKCS#8)")?; + Ok(Self { + key: TeeKey::Ec(Box::new(keypair)), + }) } #[inline] - pub fn to_pkcs1_pem(&self) -> Result> { - self.keypair.to_pkcs1_pem() + pub fn to_pem(&self) -> Result> { + match &self.key { + TeeKey::Rsa(keypair) => keypair.to_pkcs1_pem(), + TeeKey::Ec(keypair) => keypair.to_pkcs8_pem(), + } } pub fn decrypt_response(&self, response: Response) -> Result> { - // deserialize the jose header and check that the key type matches - let protected: ProtectedHeader = serde_json::from_str(&response.protected)?; - let padding_mode = PaddingMode::try_from(&protected.alg[..]) - .context("Unsupported padding mode for wrapped key")?; - // unwrap the wrapped key - let wrapped_symkey: Vec = URL_SAFE_NO_PAD.decode(&response.encrypted_key)?; - let symkey = self.decrypt(padding_mode, wrapped_symkey)?; - - let iv = URL_SAFE_NO_PAD.decode(&response.iv)?; - let ciphertext = URL_SAFE_NO_PAD.decode(&response.ciphertext)?; + let cek = self.unwrap_cek(&response.protected, response.encrypted_key)?; - let plaintext = crypto::decrypt(Zeroizing::new(symkey), ciphertext, iv, protected.enc)?; + let aad = response.protected.generate_aad()?; + let plaintext = crypto::decrypt_aead( + Zeroizing::new(cek), + response.ciphertext, + response.iv, + aad, + response.tag, + crypto::WrapType::Aes256Gcm, + )?; Ok(plaintext) } } - -#[derive(Deserialize)] -struct ProtectedHeader { - /// enryption algorithm for encrypted key - alg: String, - /// encryption algorithm for payload - enc: WrapType, -} diff --git a/attestation-agent/kbs_protocol/src/token_provider/aa/mod.rs b/attestation-agent/kbs_protocol/src/token_provider/aa/mod.rs index 970666287..a91f83f1e 100644 --- a/attestation-agent/kbs_protocol/src/token_provider/aa/mod.rs +++ b/attestation-agent/kbs_protocol/src/token_provider/aa/mod.rs @@ -59,7 +59,7 @@ impl TokenProvider for AATokenProvider { })?; let token = Token::new(message.token) .map_err(|e| Error::AATokenProvider(format!("deserialize token failed: {e:?}")))?; - let tee_keypair = TeeKeyPair::from_pkcs1_pem(&message.tee_keypair).map_err(|e| { + let tee_keypair = TeeKeyPair::from_pem(&message.tee_keypair).map_err(|e| { Error::AATokenProvider(format!("deserialize tee keypair failed: {e:?}")) })?; Ok((token, tee_keypair))