From 182f0c0f73319e30395282877cc50811fde0ac74 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 | 7 +- Cargo.toml | 8 +- .../kbs_protocol/src/client/mod.rs | 2 +- attestation-agent/kbs_protocol/src/keypair.rs | 149 +++++++++++++----- 4 files changed, 122 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c03bbd524..ff1d9bd8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3370,12 +3370,13 @@ dependencies = [ [[package]] name = "kbs-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6441ed73b0faa50707d4de41c6b45c76654b661b96aaf7b26a41331eedc0a5" +version = "0.9.1" +source = "git+https://github.com/Xynnn007/kbs-types.git?rev=f56c565#f56c5651f86ea0da43e1db02d13be87ee1363018" dependencies = [ + "base64 0.22.1", "serde", "serde_json", + "thiserror 2.0.7", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 92c46d923..61747396d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,8 +34,12 @@ ctr = "0.9.2" env_logger = "0.11.5" hex = "0.4.3" hmac = "0.12.1" -jwt-simple = { version = "0.12", default-features = false, features = ["pure-rust"] } -kbs-types = "0.7.0" +jwt-simple = { version = "0.12", default-features = false, features = [ + "pure-rust", +] } +# TODO: change this rev to official repo +# Once https://github.com/virtee/kbs-types/pull/53 gets merged +kbs-types = { git = "https://github.com/Xynnn007/kbs-types.git", rev = "f56c565" } lazy_static = "1.5.0" log = "0.4.22" nix = "0.29" 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..4843001e9 100644 --- a/attestation-agent/kbs_protocol/src/keypair.rs +++ b/attestation-agent/kbs_protocol/src/keypair.rs @@ -3,80 +3,153 @@ // SPDX-License-Identifier: Apache-2.0 // -use anyhow::{Context, Result}; +use anyhow::{anyhow, bail, 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, cipher_text: 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::OAEP, cipher_text)?; + 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, cipher_text)?; + 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 RSA key"); + }; + + if crv != key.curve().as_ref() { + bail!("Unmatched curve: {}", crv); + } + + let cek = key.unwrap_key(cipher_text, 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 }) + Ok(Self { + key: TeeKey::Rsa(Box::new(keypair)), + }) } #[inline] pub fn to_pkcs1_pem(&self) -> Result> { - self.keypair.to_pkcs1_pem() + let TeeKey::Rsa(keypair) = &self.key else { + bail!("Unmatched key. Must be RSA key"); + }; + keypair.to_pkcs1_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::aes256gcm_decrypt( + Zeroizing::new(cek), + response.ciphertext, + response.iv, + aad, + response.tag, + )?; Ok(plaintext) } } - -#[derive(Deserialize)] -struct ProtectedHeader { - /// enryption algorithm for encrypted key - alg: String, - /// encryption algorithm for payload - enc: WrapType, -}