From 7b9e7c7d7c8223210542d4a5431c30930b09ae28 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Mon, 25 Nov 2024 11:06:52 +0800 Subject: [PATCH] KBS: Update KBS protocol to 0.2.0 to fix JWE Fixes #583. Due to RFC 7516, the JWE AEAD Auth Tag should be expcilitly be included inside the `tag` part. Before this commit, the tag is actually included as the suffix of the `ciphertext`. We fix this by expcilitly extract the tag and include it into the jwe body. Also, we fix the AAD calculation logic, s.t. derived from ProtectedHeader which is also specifiled by RFC7516. This should be align with the guest-components side. This change will make the kbs_client not able to connect to the KBS. Thus we update the KBS protocol version from 0.1.1 to 0.2.0. Signed-off-by: Xynnn007 --- Cargo.lock | 29 +++++++++++++------ Cargo.toml | 2 +- deps/verifier/src/snp/mod.rs | 6 ++-- kbs/docs/kbs_attestation_protocol.md | 17 +++++++++-- kbs/src/attestation/backend.rs | 4 +-- kbs/src/jwe.rs | 43 +++++++++++++++------------- 6 files changed, 65 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c963b159..05e06bbdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix-codec" @@ -510,7 +510,7 @@ dependencies = [ "config", "const_format", "crypto", - "kbs-types", + "kbs-types 0.7.0", "log", "serde", "serde_json", @@ -538,7 +538,7 @@ dependencies = [ "futures", "hex", "jsonwebtoken", - "kbs-types", + "kbs-types 0.9.1", "lazy_static", "log", "openssl", @@ -581,7 +581,7 @@ dependencies = [ "hex", "hyper 0.14.31", "hyper-tls 0.5.0", - "kbs-types", + "kbs-types 0.7.0", "log", "occlum_dcap", "s390_pv", @@ -1327,7 +1327,7 @@ dependencies = [ "anyhow", "base64 0.22.1", "ctr", - "kbs-types", + "kbs-types 0.7.0", "rand", "rsa", "serde", @@ -2840,7 +2840,7 @@ dependencies = [ "env_logger 0.10.2", "jsonwebtoken", "jwt-simple", - "kbs-types", + "kbs-types 0.9.1", "kms", "lazy_static", "log", @@ -2894,6 +2894,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kbs-types" +version = "0.9.1" +source = "git+https://github.com/virtee/kbs-types.git?rev=a704036#a70403665bfaa61c0984ae05654df0ad72591e96" +dependencies = [ + "base64 0.22.1", + "serde", + "serde_json", + "thiserror 2.0.3", +] + [[package]] name = "kbs_protocol" version = "0.1.0" @@ -2905,7 +2916,7 @@ dependencies = [ "base64 0.22.1", "crypto", "jwt-simple", - "kbs-types", + "kbs-types 0.7.0", "log", "reqwest 0.12.9", "resource_uri", @@ -3007,7 +3018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -5895,7 +5906,7 @@ dependencies = [ "intel-tee-quote-verification-rs", "jsonwebkey", "jsonwebtoken", - "kbs-types", + "kbs-types 0.9.1", "log", "openssl", "reqwest 0.12.9", diff --git a/Cargo.toml b/Cargo.toml index b87b1d18b..a90282580 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ jwt-simple = { version = "0.12", default-features = false, features = [ "pure-rust", ] } kbs_protocol = { git = "https://github.com/confidential-containers/guest-components.git", rev = "e6999a3c0fd877dae9e68ea78b8b483062db32b8", default-features = false } -kbs-types = "0.7.0" +kbs-types = { git = "https://github.com/virtee/kbs-types.git", rev = "a704036" } kms = { git = "https://github.com/confidential-containers/guest-components.git", rev = "e6999a3c0fd877dae9e68ea78b8b483062db32b8", default-features = false } jsonwebtoken = { version = "9", default-features = false } log = "0.4.17" diff --git a/deps/verifier/src/snp/mod.rs b/deps/verifier/src/snp/mod.rs index 3012d19bf..2dce3b688 100644 --- a/deps/verifier/src/snp/mod.rs +++ b/deps/verifier/src/snp/mod.rs @@ -37,7 +37,7 @@ const LOADER_SPL_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .1); const KDS_CERT_SITE: &str = "https://kdsintf.amd.com"; const KDS_VCEK: &str = "/vcek/v1"; -/// Attestation report versions supported +/// Attestation report versions supported const REPORT_VERSION_MIN: u32 = 2; const REPORT_VERSION_MAX: u32 = 3; @@ -110,7 +110,9 @@ impl Verifier for Snp { // See Trustee Issue#589 https://github.com/confidential-containers/trustee/issues/589 if report.version < REPORT_VERSION_MIN || report.version > REPORT_VERSION_MAX { - return Err(anyhow!("Unexpected attestation report version. Check SNP Firmware ABI specification")); + return Err(anyhow!( + "Unexpected attestation report version. Check SNP Firmware ABI specification" + )); } if report.vmpl != 0 { diff --git a/kbs/docs/kbs_attestation_protocol.md b/kbs/docs/kbs_attestation_protocol.md index 8a77e84b8..f2f49454f 100644 --- a/kbs/docs/kbs_attestation_protocol.md +++ b/kbs/docs/kbs_attestation_protocol.md @@ -191,6 +191,7 @@ payload that follows the [JSON Web Encryption](https://www.rfc-editor.org/rfc/rf { "protected": "$jose_header", "encrypted_key": "$encrypted_key", + "aad": "$aad", "iv": "$iv", "ciphertext": "$ciphertext", "tag": "$tag" @@ -204,6 +205,7 @@ The above JWE JSON fields are defined as follows: let jose_header_string = format!(r#"{{"alg": "{}","enc": "{}"}}"#, alg, enc); let jose_header = base64_url::encode(&jose_header_string); let encrypted_key = base64_url::encode(enc_kbs_symkey); +let aad = base64_url::encode(additional_authenticated_data); let iv = base64_url::encode(initialization_vector); let ciphertext = base64_url::encode(response_output); @@ -212,13 +214,13 @@ tag = base64_url::encode(authentication_tag); ``` -- `alg` +- `protected.alg` Algorithm used to encrypt the encryption key at `encrypted_key`. Since the key is encrypted using the HW-TEE public key, `alg` must be the same value as described in the [`Attestation`](#attestation)'s `tee-pubkey` field. -- `enc` +- `protected.enc` Encryption algorithm used to encrypt the output of the KBS service API. @@ -227,6 +229,12 @@ Encryption algorithm used to encrypt the output of the KBS service API. The output of the KBS service API. It must be encrypted with the KBS-generated ephemeral key. +- `aad` (Required if AEAD is used) + +An input to an AEAD operation that is integrity protected but not encrypted. +Due to [JSON Web Encryption](https://www.rfc-editor.org/rfc/rfc7516), AAD field +should be calculated by `ASCII(BASE64URL(UTF8(JWE Protected Header)))` + - `iv` The input to a cryptographic primitive is used to provide the initial state. @@ -239,6 +247,11 @@ The encrypted symmetric key is used to encrypt `ciphertext`. This key is encrypted with the HW-TEE's public key, using the algorithm defined in `alg`. +- `tag` + +The authentication tag is used to authenticate the ciphertext. If the algorithm +described by `enc` used does not need it, this field is left blank. + ## Key Format ### Public Key diff --git a/kbs/src/attestation/backend.rs b/kbs/src/attestation/backend.rs index 6e130d0e8..d76374371 100644 --- a/kbs/src/attestation/backend.rs +++ b/kbs/src/attestation/backend.rs @@ -25,8 +25,8 @@ use super::{ }; static KBS_MAJOR_VERSION: u64 = 0; -static KBS_MINOR_VERSION: u64 = 1; -static KBS_PATCH_VERSION: u64 = 1; +static KBS_MINOR_VERSION: u64 = 2; +static KBS_PATCH_VERSION: u64 = 0; lazy_static! { static ref VERSION_REQ: VersionReq = { diff --git a/kbs/src/jwe.rs b/kbs/src/jwe.rs index 44e68d1b1..04d08dcb8 100644 --- a/kbs/src/jwe.rs +++ b/kbs/src/jwe.rs @@ -2,18 +2,19 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce}; +use std::collections::BTreeMap; + +use aes_gcm::{aead::AeadMutInPlace, Aes256Gcm, KeyInit, Nonce}; use anyhow::{anyhow, bail, Context, Result}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use kbs_types::{Response, TeePubKey}; +use kbs_types::{ProtectedHeader, Response, TeePubKey}; use rand::{rngs::OsRng, Rng}; use rsa::{BigUint, Pkcs1v15Encrypt, RsaPublicKey}; -use serde_json::json; const RSA_ALGORITHM: &str = "RSA1_5"; const AES_GCM_256_ALGORITHM: &str = "A256GCM"; -pub fn jwe(tee_pub_key: TeePubKey, payload_data: Vec) -> Result { +pub fn jwe(tee_pub_key: TeePubKey, mut payload_data: Vec) -> Result { let TeePubKey::RSA { alg, k_mod, k_exp } = tee_pub_key else { bail!("Only RSA key is support for TEE pub key") }; @@ -25,11 +26,19 @@ pub fn jwe(tee_pub_key: TeePubKey, payload_data: Vec) -> Result { let mut rng = rand::thread_rng(); let aes_sym_key = Aes256Gcm::generate_key(&mut OsRng); - let cipher = Aes256Gcm::new(&aes_sym_key); + let mut cipher = Aes256Gcm::new(&aes_sym_key); let iv = rng.gen::<[u8; 12]>(); let nonce = Nonce::from_slice(&iv); - let encrypted_payload_data = cipher - .encrypt(nonce, payload_data.as_slice()) + let protected = ProtectedHeader { + alg: RSA_ALGORITHM.to_string(), + enc: AES_GCM_256_ALGORITHM.to_string(), + other_fields: BTreeMap::new(), + }; + + let aad = protected.generate_aad().context("Generate JWE AAD")?; + + let tag = cipher + .encrypt_in_place_detached(nonce, &aad, &mut payload_data) .map_err(|e| anyhow!("AES encrypt Resource payload failed: {e}"))?; let k_mod = URL_SAFE_NO_PAD @@ -44,22 +53,16 @@ pub fn jwe(tee_pub_key: TeePubKey, payload_data: Vec) -> Result { let rsa_pub_key = RsaPublicKey::new(n, e).context("Building RSA key from modulus and exponent failed")?; let sym_key: &[u8] = aes_sym_key.as_slice(); - let wrapped_sym_key = rsa_pub_key + let encrypted_key = rsa_pub_key .encrypt(&mut rng, Pkcs1v15Encrypt, sym_key) .context("RSA encrypt sym key failed")?; - let protected_header = json!( - { - "alg": RSA_ALGORITHM.to_string(), - "enc": AES_GCM_256_ALGORITHM.to_string(), - }); - Ok(Response { - protected: serde_json::to_string(&protected_header) - .context("serde protected_header failed")?, - encrypted_key: URL_SAFE_NO_PAD.encode(wrapped_sym_key), - iv: URL_SAFE_NO_PAD.encode(iv), - ciphertext: URL_SAFE_NO_PAD.encode(encrypted_payload_data), - tag: "".to_string(), + protected, + encrypted_key, + iv: iv.into(), + ciphertext: payload_data, + aad: Some(aad), + tag: tag.to_vec(), }) }