From 352e0c76981bed80235369ab98a3ce8279a6e433 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Thu, 12 Dec 2024 17:44:17 +0800 Subject: [PATCH] DO NOT MERGE: only for test Signed-off-by: Xynnn007 --- Cargo.lock | 27 +++- Cargo.toml | 4 +- attestation-agent/deps/crypto/Cargo.toml | 7 +- .../deps/crypto/src/asymmetric.rs | 32 ++++- .../deps/crypto/src/native/mod.rs | 1 + .../deps/crypto/src/native/rsa.rs | 1 + attestation-agent/deps/crypto/src/rust/mod.rs | 2 + attestation-agent/deps/crypto/src/rust/rsa.rs | 1 + .../deps/crypto/src/symmetric.rs | 2 + .../kbs_protocol/src/client/rcar_client.rs | 41 +++--- attestation-agent/kbs_protocol/src/keypair.rs | 130 ++++++++++++++---- 11 files changed, 196 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7232f036d..48ce15c85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "aes-kw" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" +dependencies = [ + "aes", +] + [[package]] name = "ahash" version = "0.7.8" @@ -1107,6 +1116,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "confidential-data-hub" version = "0.1.0" @@ -1375,11 +1393,14 @@ name = "crypto" version = "0.1.0" dependencies = [ "aes-gcm", + "aes-kw", "anyhow", "base64 0.22.1", + "concat-kdf", "ctr", "kbs-types", "openssl", + "p256", "rand", "rsa", "rstest", @@ -3349,7 +3370,7 @@ dependencies = [ [[package]] name = "kbs-types" version = "0.9.1" -source = "git+https://github.com/virtee/kbs-types.git?rev=a704036#a70403665bfaa61c0984ae05654df0ad72591e96" +source = "git+https://github.com/Xynnn007/kbs-types.git?rev=f56c565#f56c5651f86ea0da43e1db02d13be87ee1363018" dependencies = [ "base64 0.22.1", "serde", @@ -6164,9 +6185,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "superboring" diff --git a/Cargo.toml b/Cargo.toml index 087c9ab74..91d1d2ee0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,9 @@ hmac = "0.12.1" jwt-simple = { version = "0.12", default-features = false, features = [ "pure-rust", ] } -kbs-types = { git = "https://github.com/virtee/kbs-types.git", rev = "a704036" } +# 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/deps/crypto/Cargo.toml b/attestation-agent/deps/crypto/Cargo.toml index 18c41431a..45efd7b2d 100644 --- a/attestation-agent/deps/crypto/Cargo.toml +++ b/attestation-agent/deps/crypto/Cargo.toml @@ -7,11 +7,14 @@ edition = "2021" [dependencies] aes-gcm = { workspace = true, optional = true } +aes-kw = "0.2.1" anyhow.workspace = true base64.workspace = true +concat-kdf = "0.1.0" ctr = { workspace = true, optional = true } kbs-types.workspace = true -openssl = { workspace = true, features = ["vendored"], optional = true} +openssl = { workspace = true, features = ["vendored"], optional = true } +p256 = { version = "0.13.1", features = ["ecdh"] } rand.workspace = true rsa = { workspace = true, optional = true } serde.workspace = true @@ -26,4 +29,4 @@ rstest.workspace = true [features] default = ["rust-crypto"] rust-crypto = ["dep:aes-gcm", "ctr", "rsa"] -openssl = ["dep:openssl"] \ No newline at end of file +openssl = ["dep:openssl"] diff --git a/attestation-agent/deps/crypto/src/asymmetric.rs b/attestation-agent/deps/crypto/src/asymmetric.rs index 09413ce85..4dd3d94ea 100644 --- a/attestation-agent/deps/crypto/src/asymmetric.rs +++ b/attestation-agent/deps/crypto/src/asymmetric.rs @@ -15,9 +15,12 @@ pub mod rsa { /// more information. #[derive(EnumString, AsRefStr)] pub enum PaddingMode { - #[strum(serialize = "RSA-OAEP")] + /// RSAES OAEP using SHA-256 and MGF1 with SHA-256 + #[strum(serialize = "RSA-OAEP-256")] OAEP, + /// RSA PKCS#1 v1.5 + #[deprecated] #[strum(serialize = "RSA1_5")] PKCS1v15, } @@ -26,3 +29,30 @@ pub mod rsa { pub const RSA_KTY: &str = "RSA"; } + +pub mod ec { + #[cfg(feature = "openssl")] + pub use crate::native::ec::*; + + #[cfg(all(feature = "rust-crypto", not(feature = "openssl")))] + pub use crate::rust::ec::*; + + /// The elliptic curve key type + pub const EC_KTY: &str = "EC"; + + /// Definations of different key wrapping algorithms. Refer to + /// for + /// more information. + #[derive(EnumString, AsRefStr)] + pub enum KeyWrapAlgorithm { + /// ECDH-ES using Concat KDF and CEK wrapped with "A256KW" + #[strum(serialize = "ECDH-ES+A256KW")] + EcdhEsA256Kw, + } + + #[derive(EnumString, AsRefStr)] + pub enum Curve { + #[strum(serialize = "P-256")] + P256, + } +} diff --git a/attestation-agent/deps/crypto/src/native/mod.rs b/attestation-agent/deps/crypto/src/native/mod.rs index 031f400ce..0002b5d4d 100644 --- a/attestation-agent/deps/crypto/src/native/mod.rs +++ b/attestation-agent/deps/crypto/src/native/mod.rs @@ -8,4 +8,5 @@ pub mod aes256ctr; pub mod aes256gcm; +pub mod ec; pub mod rsa; diff --git a/attestation-agent/deps/crypto/src/native/rsa.rs b/attestation-agent/deps/crypto/src/native/rsa.rs index 445a1e37f..c4202624e 100644 --- a/attestation-agent/deps/crypto/src/native/rsa.rs +++ b/attestation-agent/deps/crypto/src/native/rsa.rs @@ -30,6 +30,7 @@ impl RSAKeyPair { .private_key .private_decrypt(&cipher_text, &mut plaintext, Padding::PKCS1_OAEP) .map_err(|e| anyhow!("RSA key decrypt OAEP failed: {:?}", e))?, + #[allow(deprecated)] PaddingMode::PKCS1v15 => self .private_key .private_decrypt(&cipher_text, &mut plaintext, Padding::PKCS1) diff --git a/attestation-agent/deps/crypto/src/rust/mod.rs b/attestation-agent/deps/crypto/src/rust/mod.rs index aa5393e4c..3d25d31a3 100644 --- a/attestation-agent/deps/crypto/src/rust/mod.rs +++ b/attestation-agent/deps/crypto/src/rust/mod.rs @@ -8,4 +8,6 @@ pub mod aes256ctr; pub mod aes256gcm; +pub mod ec; pub mod rsa; + diff --git a/attestation-agent/deps/crypto/src/rust/rsa.rs b/attestation-agent/deps/crypto/src/rust/rsa.rs index 088cf2e09..90f155774 100644 --- a/attestation-agent/deps/crypto/src/rust/rsa.rs +++ b/attestation-agent/deps/crypto/src/rust/rsa.rs @@ -41,6 +41,7 @@ impl RSAKeyPair { .private_key .decrypt(Oaep::new::(), &cipher_text) .map_err(|e| anyhow!("RSA key decrypt OAEP failed: {:?}", e)), + #[allow(deprecated)] PaddingMode::PKCS1v15 => self .private_key .decrypt(Pkcs1v15Encrypt, &cipher_text) diff --git a/attestation-agent/deps/crypto/src/symmetric.rs b/attestation-agent/deps/crypto/src/symmetric.rs index 3e0e474cc..1de09015c 100644 --- a/attestation-agent/deps/crypto/src/symmetric.rs +++ b/attestation-agent/deps/crypto/src/symmetric.rs @@ -15,6 +15,8 @@ use crate::native::*; #[cfg(all(feature = "rust-crypto", not(feature = "openssl")))] use crate::rust::*; +pub const AES_GCM_256_KEY_BITS: u32 = 256; + /// Supported WrapType, s.t. encryption algorithm using to encrypt the /// [PLBCO](https://github.com/confidential-containers/guest-components/blob/main/attestation-agent/docs/IMPLEMENTATION.md#encryption-and-decryption-of-container-image). /// TODO: Support more kinds of en/decryption schemes. diff --git a/attestation-agent/kbs_protocol/src/client/rcar_client.rs b/attestation-agent/kbs_protocol/src/client/rcar_client.rs index 155aae339..6f6eced5c 100644 --- a/attestation-agent/kbs_protocol/src/client/rcar_client.rs +++ b/attestation-agent/kbs_protocol/src/client/rcar_client.rs @@ -406,28 +406,25 @@ mod test { kbs_config.push("test/kbs-config.toml"); policy.push("test/policy.rego"); - let image = GenericImage::new( - "ghcr.io/confidential-containers/staged-images/kbs", - "latest", - ) - .with_exposed_port(8085) - .with_volume( - tmp.path().as_os_str().to_string_lossy(), - "/opt/confidential-containers/kbs/repository", - ) - .with_volume( - start_kbs_script.into_os_string().to_string_lossy(), - "/usr/local/bin/start_kbs.sh", - ) - .with_volume( - kbs_config.into_os_string().to_string_lossy(), - "/etc/kbs-config.toml", - ) - .with_volume( - policy.into_os_string().to_string_lossy(), - "/opa/confidential-containers/kbs/policy.rego", - ) - .with_entrypoint("/usr/local/bin/start_kbs.sh"); + let image = GenericImage::new("xynnn007/kbs", "ec-aead") + .with_exposed_port(8085) + .with_volume( + tmp.path().as_os_str().to_string_lossy(), + "/opt/confidential-containers/kbs/repository", + ) + .with_volume( + start_kbs_script.into_os_string().to_string_lossy(), + "/usr/local/bin/start_kbs.sh", + ) + .with_volume( + kbs_config.into_os_string().to_string_lossy(), + "/etc/kbs-config.toml", + ) + .with_volume( + policy.into_os_string().to_string_lossy(), + "/opa/confidential-containers/kbs/policy.rego", + ) + .with_entrypoint("/usr/local/bin/start_kbs.sh"); let kbs = docker.run(image); tokio::time::sleep(Duration::from_secs(10)).await; diff --git a/attestation-agent/kbs_protocol/src/keypair.rs b/attestation-agent/kbs_protocol/src/keypair.rs index 4fb307da5..f43338f21 100644 --- a/attestation-agent/kbs_protocol/src/keypair.rs +++ b/attestation-agent/kbs_protocol/src/keypair.rs @@ -3,63 +3,147 @@ // 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::rsa::{PaddingMode, RSAKeyPair}; -use kbs_types::{Response, TeePubKey}; +use crypto::{ + ec::{Curve, EcKeyPair, KeyWrapAlgorithm}, + rsa::{PaddingMode, RSAKeyPair}, +}; +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(RSAKeyPair), + Ec(EcKeyPair), } 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(EcKeyPair::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(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> { - let padding_mode = PaddingMode::try_from(&response.protected.alg[..]) - .context("Unsupported padding mode for wrapped key")?; - // unwrap the wrapped key - let symkey = self.decrypt(padding_mode, response.encrypted_key)?; + let cek = self.unwrap_cek(&response.protected, response.encrypted_key)?; let aad = response.protected.generate_aad()?; let plaintext = crypto::aes256gcm_decrypt( - Zeroizing::new(symkey), + Zeroizing::new(cek), response.ciphertext, response.iv, aad,