From 340b883d925fc19592367e873e201cabe82493d7 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Wed, 9 Oct 2024 12:08:31 +0800 Subject: [PATCH 1/3] KBS: combine CoCo Token and Jwk Token verifier Actually, the ITA token and CoCo Token are both JWTs. They both need a JWK to verify the JWT. The difference is the way to gather the JWK. This commit combined the two logic, and add two ways to get the JWK. 1. From the configured JwkSet when launching KBS 2. From the JWT's Header's jwk field. The two ways will check the jwk endorsement in different ways. The first way is to configure the trusted JwkSet from the config. The second way is to configure the trusted CA in config. Then get the public key cert chain from Jwk's x5c field. The both ways are also supported in this patch. Rust does not provide a mature crate to verify cert chain, thus openssl is used in this patch. We also abondon rustls and openssl feature of KBS because openssl is by default used. Then we use openssl by default to make the code base simpler. Signed-off-by: Xynnn007 --- Cargo.lock | 101 ++------ kbs/Cargo.toml | 16 +- .../attestation/intel_trust_authority/mod.rs | 16 +- kbs/src/http/mod.rs | 2 - kbs/src/http/resource.rs | 31 +-- kbs/src/lib.rs | 58 +---- kbs/src/token/coco.rs | 216 ------------------ kbs/src/token/error.rs | 30 +++ kbs/src/token/jwk.rs | 139 +++++++++-- kbs/src/token/mod.rs | 110 ++++++--- 10 files changed, 261 insertions(+), 458 deletions(-) delete mode 100644 kbs/src/token/coco.rs create mode 100644 kbs/src/token/error.rs diff --git a/Cargo.lock b/Cargo.lock index 6bbce59a21..563f1cc708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,10 +135,8 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-openssl", - "tokio-rustls 0.23.4", "tokio-util", "tracing", - "webpki-roots 0.22.6", ] [[package]] @@ -2580,7 +2578,7 @@ dependencies = [ "base64 0.21.7", "js-sys", "pem", - "ring 0.17.8", + "ring", "serde", "serde_json", "simple_asn1", @@ -2682,8 +2680,6 @@ dependencies = [ "reqwest 0.12.5", "rsa 0.9.6", "rstest", - "rustls 0.20.9", - "rustls-pemfile 1.0.4", "scc", "semver", "serde", @@ -2768,7 +2764,7 @@ dependencies = [ "rand", "reqwest 0.12.5", "resource_uri", - "ring 0.17.8", + "ring", "serde", "serde_json", "sha2", @@ -2794,7 +2790,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -3938,7 +3934,7 @@ checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" dependencies = [ "bytes", "rand", - "ring 0.17.8", + "ring", "rustc-hash 2.0.0", "rustls 0.23.7", "slab", @@ -4244,21 +4240,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -4269,8 +4250,8 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] @@ -4415,18 +4396,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.21.12" @@ -4434,7 +4403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-webpki 0.101.7", "sct", ] @@ -4447,7 +4416,7 @@ checksum = "ebbbdb961df0ad3f2652da8f3fdc4b36122f568f968f45ad3316f26c025c677b" dependencies = [ "log", "once_cell", - "ring 0.17.8", + "ring", "rustls-pki-types", "rustls-webpki 0.102.3", "subtle", @@ -4485,8 +4454,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -4495,9 +4464,9 @@ version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ - "ring 0.17.8", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -4643,8 +4612,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -5019,12 +4988,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -5412,17 +5375,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -5817,12 +5769,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -6067,25 +6013,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.25.4" diff --git a/kbs/Cargo.toml b/kbs/Cargo.toml index fc0ae81091..6ff78dd421 100644 --- a/kbs/Cargo.toml +++ b/kbs/Cargo.toml @@ -7,10 +7,10 @@ documentation.workspace = true edition.workspace = true [features] -default = ["coco-as-builtin", "resource", "opa", "rustls"] +default = ["coco-as-builtin", "resource", "opa"] # Feature that allows to access resources from KBS -resource = ["rsa", "dep:openssl", "reqwest", "aes-gcm", "jsonwebtoken"] +resource = ["rsa", "reqwest", "aes-gcm", "jsonwebtoken"] # Support a backend attestation service for KBS as = [] @@ -36,17 +36,11 @@ coco-as-grpc = ["coco-as", "mobc", "tonic", "tonic-build", "prost"] # Use Intel TA as backend attestation service intel-trust-authority-as = ["as", "reqwest", "resource", "az-cvm-vtpm"] -# Use pure rust crypto stack for KBS -rustls = ["actix-web/rustls", "dep:rustls", "dep:rustls-pemfile"] - -# Use openssl crypto stack for KBS -openssl = ["actix-web/openssl", "dep:openssl"] - # Use aliyun KMS as KBS backend aliyun = ["kms/aliyun"] [dependencies] -actix-web.workspace = true +actix-web = { workspace = true, features = ["openssl"] } actix-web-httpauth.workspace = true aes-gcm = { version = "0.10.1", optional = true } anyhow.workspace = true @@ -69,8 +63,6 @@ rand = "0.8.5" regorus.workspace = true reqwest = { workspace = true, features = ["json"], optional = true } rsa = { version = "0.9.2", optional = true, features = ["sha2"] } -rustls = { version = "0.20.8", optional = true } -rustls-pemfile = { version = "1.0.4", optional = true } scc = "2" semver = "1.0.16" serde = { workspace = true, features = ["derive"] } @@ -81,7 +73,7 @@ time = { version = "0.3.23", features = ["std"] } tokio.workspace = true tonic = { workspace = true, optional = true } uuid = { version = "1.2.2", features = ["serde", "v4"] } -openssl = { version = "0.10.46", optional = true } +openssl = "0.10.55" az-cvm-vtpm = { version = "0.7.0", default-features = false, optional = true } [dev-dependencies] diff --git a/kbs/src/attestation/intel_trust_authority/mod.rs b/kbs/src/attestation/intel_trust_authority/mod.rs index 9f5ae4c4cf..7986fd4ec8 100644 --- a/kbs/src/attestation/intel_trust_authority/mod.rs +++ b/kbs/src/attestation/intel_trust_authority/mod.rs @@ -4,10 +4,7 @@ use super::Attest; use crate::attestation::{generic_generate_challenge, make_nonce}; -use crate::token::{ - jwk::JwkAttestationTokenVerifier, AttestationTokenVerifier, AttestationTokenVerifierConfig, - AttestationTokenVerifierType, -}; +use crate::token::{jwk::JwkAttestationTokenVerifier, AttestationTokenVerifierConfig}; use anyhow::*; use async_trait::async_trait; use az_cvm_vtpm::hcl::HclReport; @@ -16,8 +13,7 @@ use kbs_types::Challenge; use kbs_types::{Attestation, Tee}; use reqwest::header::{ACCEPT, CONTENT_TYPE, USER_AGENT}; use serde::{Deserialize, Serialize}; -use serde_json::from_value; -use serde_json::json; +use serde_json::{from_value, json}; use strum::{AsRefStr, Display, EnumString}; const SUPPORTED_HASH_ALGORITHMS_JSON_KEY: &str = "supported-hash-algorithms"; @@ -190,7 +186,7 @@ impl Attest for IntelTrustAuthority { .await .context("Failed to verify attestation token")?; - let claims = serde_json::from_str::(&token) + let claims = serde_json::from_value::(token) .context("Failed to deserialize attestation token claims")?; // check unmatched policy @@ -287,8 +283,10 @@ impl Attest for IntelTrustAuthority { impl IntelTrustAuthority { pub async fn new(config: IntelTrustAuthorityConfig) -> Result { let token_verifier = JwkAttestationTokenVerifier::new(&AttestationTokenVerifierConfig { - attestation_token_type: AttestationTokenVerifierType::Jwk, - trusted_certs_paths: vec![config.certs_file.clone()], + extra_teekey_paths: vec![], + trusted_certs_paths: vec![], + trusted_jwk_sets: vec![config.certs_file.clone()], + insecure_key: true, }) .await .context("Failed to initialize token verifier")?; diff --git a/kbs/src/http/mod.rs b/kbs/src/http/mod.rs index 426653a398..4d4c443236 100644 --- a/kbs/src/http/mod.rs +++ b/kbs/src/http/mod.rs @@ -11,8 +11,6 @@ use crate::policy_engine::PolicyEngine; use crate::resource::{set_secret_resource, Repository, ResourceDesc}; #[cfg(feature = "as")] use crate::session::{SessionMap, KBS_SESSION_ID}; -#[cfg(feature = "resource")] -use crate::token::AttestationTokenVerifier; use actix_web::Responder; use actix_web::{body::BoxBody, web, HttpRequest, HttpResponse}; use jwt_simple::prelude::Ed25519PublicKey; diff --git a/kbs/src/http/resource.rs b/kbs/src/http/resource.rs index abf8aed54d..ee5e6e66bf 100644 --- a/kbs/src/http/resource.rs +++ b/kbs/src/http/resource.rs @@ -15,15 +15,10 @@ use rsa::{BigUint, Pkcs1v15Encrypt, RsaPublicKey}; use serde::Deserialize; use serde_json::{json, Deserializer, Value}; -use crate::raise_error; +use crate::{raise_error, token::TokenVerifier}; use super::*; -#[cfg(feature = "as")] -const TOKEN_TEE_PUBKEY_PATH: &str = AS_TOKEN_TEE_PUBKEY_PATH; -#[cfg(not(feature = "as"))] -const TOKEN_TEE_PUBKEY_PATH: &str = "/customized_claims/runtime_data/tee-pubkey"; - #[allow(unused_assignments)] /// GET /resource/{repository}/{type}/{tag} /// GET /resource/{type}/{tag} @@ -31,7 +26,7 @@ pub(crate) async fn get_resource( request: HttpRequest, repository: web::Data>>, #[cfg(feature = "as")] map: web::Data, - token_verifier: web::Data>>, + token_verifier: web::Data, #[cfg(feature = "policy")] policy_engine: web::Data, ) -> Result { #[allow(unused_mut)] @@ -45,20 +40,16 @@ pub(crate) async fn get_resource( c } else { debug!("Get pkey from auth header"); - get_attest_claims_from_header(&request, token_verifier).await? + get_attest_claims_from_header(&request, &token_verifier).await? }; let claims: Value = serde_json::from_str(&claims_str).map_err(|e| { Error::AttestationClaimsParseFailed(format!("illegal attestation claims: {e}")) })?; - let pkey_value = - claims - .pointer(TOKEN_TEE_PUBKEY_PATH) - .ok_or(Error::AttestationClaimsParseFailed(String::from( - "Failed to find `tee-pubkey` in the attestation claims", - )))?; - let pubkey = TeePubKey::deserialize(pkey_value).map_err(|e| { - Error::AttestationClaimsParseFailed(format!("illegal attestation claims: {e}")) + let pubkey = token_verifier.extract_tee_public_key(claims).map_err(|e| { + Error::AttestationClaimsParseFailed(format!( + "Failed to extract public key in attestation claims: {e:?}" + )) })?; let resource_description = ResourceDesc { @@ -168,7 +159,7 @@ async fn get_attest_claims_from_session( async fn get_attest_claims_from_header( request: &HttpRequest, - token_verifier: web::Data>>, + token_verifier: &web::Data, ) -> Result { let bearer = Authorization::::parse(request) .map_err(|e| Error::InvalidRequest(format!("parse Authorization header failed: {e}")))? @@ -177,11 +168,11 @@ async fn get_attest_claims_from_header( let token = bearer.token().to_string(); let claims = token_verifier - .read() - .await .verify(token) .await - .map_err(|e| Error::TokenParseFailed(format!("verify token failed: {e}")))?; + .map_err(|e| Error::TokenParseFailed(format!("verify token failed: {e:?}")))?; + let claims = serde_json::to_string(&claims) + .map_err(|_| Error::TokenParseFailed("failed to serialize claims".into()))?; Ok(claims) } diff --git a/kbs/src/lib.rs b/kbs/src/lib.rs index b46abf82f2..1bc7ed052b 100644 --- a/kbs/src/lib.rs +++ b/kbs/src/lib.rs @@ -22,6 +22,7 @@ use anyhow::{anyhow, bail, Context, Result}; #[cfg(feature = "as")] use attestation::AttestationService; use jwt_simple::prelude::Ed25519PublicKey; +use openssl::ssl::SslAcceptorBuilder; #[cfg(feature = "resource")] use resource::RepositoryConfig; #[cfg(feature = "as")] @@ -30,12 +31,6 @@ use std::{net::SocketAddr, path::PathBuf}; #[cfg(feature = "resource")] use token::AttestationTokenVerifierConfig; -#[cfg(feature = "rustls")] -use rustls::ServerConfig; - -#[cfg(feature = "openssl")] -use openssl::ssl::SslAcceptorBuilder; - #[cfg(feature = "as")] use crate::session::SessionMap; @@ -148,45 +143,6 @@ impl ApiServer { }) } - #[cfg(feature = "rustls")] - fn tls_config(&self) -> Result { - use rustls::{Certificate, PrivateKey}; - use rustls_pemfile::{certs, read_one, Item}; - use std::fs::File; - use std::io::BufReader; - - let cert_file = &mut BufReader::new(File::open( - self.certificate - .clone() - .ok_or_else(|| anyhow!("Missing certificate"))?, - )?); - - let key_file = &mut BufReader::new(File::open( - self.private_key - .clone() - .ok_or_else(|| anyhow!("Missing private key"))?, - )?); - - let cert_chain = certs(cert_file)? - .iter() - .map(|c| Certificate(c.clone())) - .collect(); - - let key = match read_one(key_file)? { - Some(Item::RSAKey(key)) | Some(Item::PKCS8Key(key)) | Some(Item::ECKey(key)) => { - Ok(PrivateKey(key)) - } - None | Some(_) => Err(anyhow!("Invalid private key file")), - }?; - - ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_single_cert(cert_chain, key) - .map_err(anyhow::Error::from) - } - - #[cfg(feature = "openssl")] fn tls_config(&self) -> Result { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; @@ -240,7 +196,7 @@ impl ApiServer { #[cfg(feature = "resource")] let token_verifier = - crate::token::create_token_verifier(self.attestation_token_config.clone()).await?; + crate::token::TokenVerifier::from_config(self.attestation_token_config.clone()).await?; #[cfg(feature = "policy")] let policy_engine = PolicyEngine::new(&self.policy_engine_config).await?; @@ -306,15 +262,7 @@ impl ApiServer { }); if !self.insecure { - let tls_server = { - cfg_if::cfg_if! { - if #[cfg(feature = "openssl")] { - http_server.bind_openssl(&self.sockets[..], self.tls_config()?)? - } else { - http_server.bind_rustls(&self.sockets[..], self.tls_config()?)? - } - } - }; + let tls_server = http_server.bind_openssl(&self.sockets[..], self.tls_config()?)?; tls_server.run().await.map_err(anyhow::Error::from) } else { diff --git a/kbs/src/token/coco.rs b/kbs/src/token/coco.rs deleted file mode 100644 index 76e40be0c8..0000000000 --- a/kbs/src/token/coco.rs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) 2023 by Alibaba. -// Licensed under the Apache License, Version 2.0, see LICENSE for details. -// SPDX-License-Identifier: Apache-2.0 - -use crate::token::{AttestationTokenVerifier, AttestationTokenVerifierConfig}; -use anyhow::*; -use async_trait::async_trait; -use base64::engine::general_purpose::URL_SAFE_NO_PAD; -use base64::Engine; -use log::warn; -use openssl::hash::MessageDigest; -use openssl::pkey::PKey; -use openssl::rsa::Rsa; -use openssl::sign::Verifier; -use openssl::stack::Stack; -use openssl::x509::store::{X509Store, X509StoreBuilder}; -use openssl::x509::{X509StoreContext, X509}; -use serde_json::Value; - -pub struct CoCoAttestationTokenVerifier { - trusted_certs: X509Store, -} - -impl CoCoAttestationTokenVerifier { - pub fn new(config: &AttestationTokenVerifierConfig) -> Result { - let mut store_builder = X509StoreBuilder::new()?; - - // check all files in trusted_certs_paths but don't exit (only warn). - // the result can be an empty trust store. - for path in &config.trusted_certs_paths { - std::fs::read(path).map_or_else( - |e| warn!("Failed to read trusted certificate: {e}"), - |pem| { - let _ = X509::from_pem(&pem) - .and_then(|certs| store_builder.add_cert(certs.to_owned())) - .map_err(|e| warn!("Failed to add certificate to trust store: {e}")); - }, - ); - } - - Ok(Self { - trusted_certs: store_builder.build(), - }) - } -} - -#[async_trait] -impl AttestationTokenVerifier for CoCoAttestationTokenVerifier { - async fn verify(&self, token: String) -> Result { - let split_token: Vec<&str> = token.split('.').collect(); - if !split_token.len() == 3 { - bail!("Illegal JWT format") - } - - let header = URL_SAFE_NO_PAD.decode(split_token[0])?; - let claims = URL_SAFE_NO_PAD.decode(split_token[1])?; - let signature = URL_SAFE_NO_PAD.decode(split_token[2])?; - - let header_value = serde_json::from_slice::(&header)?; - let claims_value = serde_json::from_slice::(&claims)?; - - let now = time::OffsetDateTime::now_utc().unix_timestamp(); - let Some(exp) = claims_value["exp"].as_i64() else { - bail!("token expiration unset"); - }; - if exp < now { - bail!("token expired"); - } - if let Some(nbf) = claims_value["nbf"].as_i64() { - if nbf > now { - bail!("before validity"); - } - } - - let jwk_value = claims_value["jwk"].as_object().ok_or_else(|| anyhow!("CoCo Attestation Token Claims must contain public key (JWK format) to verify signature"))?; - let jwk = serde_json::to_string(&jwk_value)?; - let rsa_jwk = serde_json::from_str::(&jwk)?; - let payload = format!("{}.{}", &split_token[0], &split_token[1]) - .as_bytes() - .to_vec(); - - match header_value["alg"].as_str() { - Some("RS384") => { - if rsa_jwk.alg != *"RS384" { - bail!("Unmatched RSA JWK alg"); - } - rs384_verify(&payload, &signature, &rsa_jwk)?; - } - None => { - bail!("Miss `alg` in JWT header") - } - _ => { - bail!("Unsupported JWT algrithm") - } - } - - if self.trusted_certs.all_certificates().is_empty() { - warn!("No Trusted Certificate in Config, skip verification of JWK cert of Attestation Token"); - return Ok(serde_json::to_string(&claims_value)?); - }; - - let mut cert_chain: Vec = vec![]; - - // Get certificate chain from 'x5c' or 'x5u' in JWK. - if let Some(x5c) = rsa_jwk.x5c { - for base64_der_cert in x5c { - let der_cert = URL_SAFE_NO_PAD.decode(base64_der_cert)?; - let cert = X509::from_der(&der_cert)?; - cert_chain.push(cert) - } - } else if let Some(x5u) = rsa_jwk.x5u { - download_cert_chain(x5u, &mut cert_chain).await?; - } else { - bail!("Missing certificate in Attestation Token JWK"); - } - - // Check certificate is valid and trustworthy - let mut untrusted_stack = Stack::::new()?; - for cert in cert_chain.iter().skip(1) { - untrusted_stack.push(cert.clone())?; - } - let mut context = X509StoreContext::new()?; - if !context.init( - &self.trusted_certs, - &cert_chain[0], - &untrusted_stack, - |ctx| ctx.verify_cert(), - )? { - bail!("Untrusted certificate in Attestation Token JWK"); - }; - - // Check the public key in JWK is consistent with the public key in certificate - let n = openssl::bn::BigNum::from_slice(&URL_SAFE_NO_PAD.decode(&rsa_jwk.n)?)?; - let e = openssl::bn::BigNum::from_slice(&URL_SAFE_NO_PAD.decode(&rsa_jwk.e)?)?; - let rsa_public_key = Rsa::from_public_components(n, e)?; - let rsa_pkey = PKey::from_rsa(rsa_public_key)?; - let cert_pub_key = cert_chain[0].public_key()?; - if !cert_pub_key.public_eq(&rsa_pkey) { - bail!("Certificate Public Key Mismatched in Attestation Token"); - } - - Ok(serde_json::to_string(&claims_value)?) - } -} - -#[allow(dead_code)] -#[derive(serde::Deserialize, Clone, Debug)] -struct RsaJWK { - kty: String, - alg: String, - n: String, - e: String, - x5u: Option, - x5c: Option>, -} - -fn rs384_verify(payload: &[u8], signature: &[u8], jwk: &RsaJWK) -> Result<()> { - let n = openssl::bn::BigNum::from_slice(&URL_SAFE_NO_PAD.decode(&jwk.n)?)?; - let e = openssl::bn::BigNum::from_slice(&URL_SAFE_NO_PAD.decode(&jwk.e)?)?; - let rsa_public_key = Rsa::from_public_components(n, e)?; - let rsa_pkey = PKey::from_rsa(rsa_public_key)?; - - let mut verifier = Verifier::new(MessageDigest::sha384(), &rsa_pkey)?; - verifier.update(payload)?; - - if !verifier.verify(signature)? { - bail!("RS384 verify failed") - } - - Ok(()) -} - -async fn download_cert_chain(url: String, chain: &mut Vec) -> Result<()> { - let res = reqwest::get(url).await?; - match res.status() { - reqwest::StatusCode::OK => { - let pem_cert_chain = res.text().await?; - parse_pem_cert_chain(pem_cert_chain, chain)?; - } - _ => { - bail!( - "Request x5u in Attestation Token JWK Failed, Response: {:?}", - res.text().await? - ); - } - } - - Ok(()) -} - -fn parse_pem_cert_chain(pem_cert_chain: String, chain: &mut Vec) -> Result<()> { - for pem in pem_cert_chain.split("-----END CERTIFICATE-----") { - let trimmed = format!("{}\n-----END CERTIFICATE-----", pem.trim()); - if !trimmed.starts_with("-----BEGIN CERTIFICATE-----") { - continue; - } - let cert = X509::from_pem(trimmed.as_bytes()) - .map_err(|_| anyhow!("Invalid PEM certificate chain"))?; - chain.push(cert); - } - - Ok(()) -} - -#[allow(unused_imports)] -mod test { - use super::*; - - #[test] - fn test_parse_pem_cert_chain() { - let pem_cert_chain = std::fs::read_to_string("test/data/test_cert_chain.pem").unwrap(); - let mut chain: Vec = Vec::new(); - assert!(parse_pem_cert_chain(pem_cert_chain, &mut chain).is_ok()); - assert_eq!(chain.len(), 2); - } -} diff --git a/kbs/src/token/error.rs b/kbs/src/token/error.rs new file mode 100644 index 0000000000..835de1e90c --- /dev/null +++ b/kbs/src/token/error.rs @@ -0,0 +1,30 @@ +// Copyright (c) 2024 by Alibaba. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use log::error; +use strum::AsRefStr; +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, AsRefStr, Debug)] +pub enum Error { + #[error("Failed to verify Attestation Token")] + TokenVerificationFailed { + #[source] + source: anyhow::Error, + }, + + #[error("Failed to initialize Token Verifier")] + TokenVerifierInitialization { + #[source] + source: anyhow::Error, + }, + + #[error("Tee public key is not found inside the claims of token")] + NoTeePubKeyClaimFound, + + #[error("Failed to parse Tee public key")] + TeePubKeyParseFailed, +} diff --git a/kbs/src/token/jwk.rs b/kbs/src/token/jwk.rs index 2bb29eec6d..b51b7c9a1c 100644 --- a/kbs/src/token/jwk.rs +++ b/kbs/src/token/jwk.rs @@ -2,10 +2,18 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use crate::token::{AttestationTokenVerifier, AttestationTokenVerifierConfig}; -use anyhow::*; -use async_trait::async_trait; -use jsonwebtoken::{decode, decode_header, jwk, Algorithm, DecodingKey, Validation}; +use crate::token::AttestationTokenVerifierConfig; +use anyhow::{anyhow, bail, Context}; +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::Engine; +use jsonwebtoken::jwk::{AlgorithmParameters, Jwk}; +use jsonwebtoken::{decode, decode_header, jwk, Algorithm, DecodingKey, Header, Validation}; +use openssl::bn::BigNum; +use openssl::pkey::PKey; +use openssl::stack::Stack; +use openssl::x509::store::X509StoreBuilder; +use openssl::x509::X509StoreContext; +use openssl::{rsa::Rsa, x509::X509}; use reqwest::{get, Url}; use serde::Deserialize; use serde_json::Value; @@ -14,6 +22,7 @@ use std::io::BufReader; use std::result::Result::Ok; use std::str::FromStr; use thiserror::Error; +use tokio::fs; const OPENID_CONFIG_URL_SUFFIX: &str = ".well-known/openid-configuration"; @@ -32,11 +41,14 @@ struct OpenIDConfig { jwks_uri: String, } +#[derive(Clone)] pub struct JwkAttestationTokenVerifier { - trusted_certs: jwk::JwkSet, + trusted_jwk_sets: jwk::JwkSet, + trusted_certs: Vec, + insecure_key: bool, } -pub async fn get_jwks_from_file_or_url(p: &str) -> Result { +async fn get_jwks_from_file_or_url(p: &str) -> Result { let mut url = Url::parse(p).map_err(|e| JwksGetError::InvalidSourcePath(e.to_string()))?; match url.scheme() { "https" => { @@ -73,37 +85,122 @@ pub async fn get_jwks_from_file_or_url(p: &str) -> Result Result { - let mut trusted_certs = jwk::JwkSet { keys: Vec::new() }; + pub async fn new(config: &AttestationTokenVerifierConfig) -> anyhow::Result { + let mut trusted_jwk_sets = jwk::JwkSet { keys: Vec::new() }; - for path in config.trusted_certs_paths.iter() { + for path in config.trusted_jwk_sets.iter() { match get_jwks_from_file_or_url(path).await { - Ok(mut jwkset) => trusted_certs.keys.append(&mut jwkset.keys), + Ok(mut jwkset) => trusted_jwk_sets.keys.append(&mut jwkset.keys), Err(e) => log::warn!("error getting JWKS: {:?}", e), } } - Ok(Self { trusted_certs }) + let mut trusted_certs = Vec::new(); + for path in &config.trusted_certs_paths { + let cert_content = fs::read(path).await.map_err(|_| { + JwksGetError::AccessFailed(format!("failed to read certificate {path}")) + })?; + let cert = X509::from_pem(&cert_content)?; + trusted_certs.push(cert); + } + + Ok(Self { + trusted_jwk_sets, + trusted_certs, + insecure_key: config.insecure_key, + }) } -} -#[async_trait] -impl AttestationTokenVerifier for JwkAttestationTokenVerifier { - async fn verify(&self, token: String) -> Result { - if self.trusted_certs.keys.is_empty() { + fn verify_jwk_endorsement(&self, key: &Jwk) -> anyhow::Result<()> { + let AlgorithmParameters::RSA(rsa) = &key.algorithm else { + bail!("Only supports RSA JWK now"); + }; + + let n = URL_SAFE_NO_PAD + .decode(&rsa.n) + .context("decode RSA public key parameter n")?; + let n = BigNum::from_slice(&n)?; + let e = URL_SAFE_NO_PAD + .decode(&rsa.e) + .context("decode RSA public key parameter e")?; + let e = BigNum::from_slice(&e)?; + + let public_key = Rsa::from_public_components(n, e)?; + let public_key = PKey::from_rsa(public_key)?; + + let Some(x5c) = &key.common.x509_chain else { + bail!("No x5c extension inside JWK. Malwared public key.") + }; + + if x5c.is_empty() { + bail!("No x5c extension inside JWK. Malwared public key.") + } + + let pem = x5c[0].split('\n').collect::(); + let der = URL_SAFE_NO_PAD.decode(pem).context("Illegal x5c cert")?; + + let leaf_cert = X509::from_der(&der).context("malwared x509 in x5c")?; + // verify the public key matches the leaf cert + if !public_key.public_eq(leaf_cert.public_key()?.as_ref()) { + bail!("jwk does not match x5c"); + }; + + let mut cert_chain = Stack::new()?; + for cert in &x5c[1..] { + let pem = cert.split('\n').collect::(); + let der = URL_SAFE_NO_PAD.decode(&pem).context("Illegal x5c cert")?; + + let cert = X509::from_der(&der).context("malwared x509 in x5c")?; + cert_chain.push(cert)?; + } + + let mut trust_store_builder = X509StoreBuilder::new()?; + for cert in &self.trusted_certs { + trust_store_builder.add_cert(cert.clone())?; + } + let trust_store = trust_store_builder.build(); + + // verify the cert chain + let mut ctx = X509StoreContext::new()?; + if !ctx.init(&trust_store, &leaf_cert, &cert_chain, |c| c.verify_cert())? { + bail!("The JWK is malwared because no trust anchor can verify it."); + } + Ok(()) + } + + fn get_verification_jwk<'a>(&'a self, header: &'a Header) -> anyhow::Result<&'a Jwk> { + if let Some(key) = &header.jwk { + if self.insecure_key { + return Ok(key); + } + if self.trusted_certs.is_empty() { + bail!("Cannot verify token since trusted cert is empty"); + }; + self.verify_jwk_endorsement(key)?; + return Ok(key); + } + + if self.trusted_jwk_sets.keys.is_empty() { bail!("Cannot verify token since trusted JWK Set is empty"); }; - let kid = decode_header(&token) - .context("Failed to decode attestation token header")? + let kid = header .kid + .as_ref() .ok_or(anyhow!("Failed to decode kid in the token header"))?; let key = &self - .trusted_certs - .find(&kid) + .trusted_jwk_sets + .find(kid) .ok_or(anyhow!("Failed to find Jwk with kid {kid} in JwkSet"))?; + Ok(key) + } + + pub async fn verify(&self, token: String) -> anyhow::Result { + let header = decode_header(&token).context("Failed to decode attestation token header")?; + + let key = self.get_verification_jwk(&header)?; let key_alg = key .common .key_algorithm @@ -116,7 +213,7 @@ impl AttestationTokenVerifier for JwkAttestationTokenVerifier { let token_data = decode::(&token, &dkey, &Validation::new(alg)) .context("Failed to decode attestation token")?; - Ok(serde_json::to_string(&token_data.claims)?) + Ok(token_data.claims) } } diff --git a/kbs/src/token/mod.rs b/kbs/src/token/mod.rs index 30f5090617..6381c9041a 100644 --- a/kbs/src/token/mod.rs +++ b/kbs/src/token/mod.rs @@ -2,54 +2,92 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use anyhow::*; -use async_trait::async_trait; +use jwk::JwkAttestationTokenVerifier; +use kbs_types::TeePubKey; +use log::debug; use serde::Deserialize; -use std::sync::Arc; -use strum::EnumString; -use tokio::sync::RwLock; +use serde_json::Value; -mod coco; +mod error; pub(crate) mod jwk; +pub use error::*; -#[async_trait] -pub trait AttestationTokenVerifier { - /// Verify an signed attestation token. - /// Returns the custom claims JSON string of the token. - async fn verify(&self, token: String) -> Result; -} - -#[derive(Deserialize, Default, Debug, Clone, EnumString)] -pub enum AttestationTokenVerifierType { - #[default] - CoCo, - Jwk, -} +pub const TOKEN_TEE_PUBKEY_PATH_ITA: &str = "/attester_runtime_data/tee-pubkey"; +pub const TOKEN_TEE_PUBKEY_PATH_COCO: &str = "/customized_claims/runtime_data/tee-pubkey"; -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, PartialEq, Default)] pub struct AttestationTokenVerifierConfig { #[serde(default)] - pub attestation_token_type: AttestationTokenVerifierType, + /// The paths to the tee public key in the JWT body. For example, + /// `/attester_runtime_data/tee-pubkey` refers to the key + /// `attester_runtime_data.tee-pubkey` inside the JWT body claims. + /// + /// If a JWT is received, the [`TokenVerifier`] will try to extract + /// the tee public key from built-in ones ([`TOKEN_TEE_PUBKEY_PATH_ITA`], + /// [`TOKEN_TEE_PUBKEY_PATH_COCO`]) and the configured `extra_teekey_paths`. + /// + /// This field will default to an empty vector. + pub extra_teekey_paths: Vec, - /// Trusted Certificates file (PEM format) path (for "CoCo") or a valid Url - /// (file:// and https:// schemes accepted) pointing to a local JWKSet file + /// Trusted Certificates file (PEM format) paths use to verify Attestation + /// Token Signature. + #[serde(default)] + pub trusted_certs_paths: Vec, + + /// Urls (file:// and https:// schemes accepted) pointing to a local JWKSet file /// or to an OpenID configuration url giving a pointer to JWKSet certificates /// (for "Jwk") to verify Attestation Token Signature. #[serde(default)] - pub trusted_certs_paths: Vec, + pub trusted_jwk_sets: Vec, + + /// Whether a JWK that directly comes from the JWT token is allowed to verify + /// the signature. This is insecure as it will not check the endorsement of + /// the JWK. If this option is set to false, the JWK will be looked up from + /// the key store configured during launching the KBS with kid field in the JWT, + /// or be checked against the configured trusted CA certs. + #[serde(default = "bool::default")] + pub insecure_key: bool, } -pub async fn create_token_verifier( - config: AttestationTokenVerifierConfig, -) -> Result>> { - match config.attestation_token_type { - AttestationTokenVerifierType::CoCo => Ok(Arc::new(RwLock::new( - coco::CoCoAttestationTokenVerifier::new(&config)?, - )) - as Arc>), - AttestationTokenVerifierType::Jwk => Ok(Arc::new(RwLock::new( - jwk::JwkAttestationTokenVerifier::new(&config).await?, - )) - as Arc>), +#[derive(Clone)] +pub struct TokenVerifier { + verifier: JwkAttestationTokenVerifier, + extra_teekey_paths: Vec, +} + +impl TokenVerifier { + pub async fn verify(&self, token: String) -> Result { + self.verifier + .verify(token) + .await + .map_err(|e| Error::TokenVerificationFailed { source: e }) + } + + pub async fn from_config(config: AttestationTokenVerifierConfig) -> Result { + let verifier = JwkAttestationTokenVerifier::new(&config) + .await + .map_err(|e| Error::TokenVerifierInitialization { source: e })?; + + let mut extra_teekey_paths = config.extra_teekey_paths; + extra_teekey_paths.push(TOKEN_TEE_PUBKEY_PATH_ITA.into()); + extra_teekey_paths.push(TOKEN_TEE_PUBKEY_PATH_COCO.into()); + + Ok(Self { + verifier, + extra_teekey_paths, + }) + } + + /// Different attestation service would embed tee public key + /// in different parts of the claims. + pub fn extract_tee_public_key(&self, claim: Value) -> Result { + for path in &self.extra_teekey_paths { + if let Some(pkey_value) = claim.pointer(path) { + debug!("Extract tee public key from {path}"); + return TeePubKey::deserialize(pkey_value).map_err(|_| Error::TeePubKeyParseFailed); + } + } + + Err(Error::NoTeePubKeyClaimFound) } } From 679a7b8b4d6e7ff86dc1714671999e7272d7d4e7 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Wed, 9 Oct 2024 12:09:51 +0800 Subject: [PATCH 2/3] AS: move JWK to the JWT Header field Due to RFC 7515, JWK should be part of a JOSE Header rather than claim body. Signed-off-by: Xynnn007 --- attestation-service/src/token/simple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attestation-service/src/token/simple.rs b/attestation-service/src/token/simple.rs index 20c1d9652b..23281c3237 100644 --- a/attestation-service/src/token/simple.rs +++ b/attestation-service/src/token/simple.rs @@ -92,6 +92,7 @@ impl AttestationTokenBroker for SimpleAttestationTokenBroker { let header_value = json!({ "typ": "JWT", "alg": SIMPLE_TOKEN_ALG, + "jwk": serde_json::from_str::(&self.pubkey_jwks()?)?["keys"][0].clone(), }); let header_string = serde_json::to_string(&header_value)?; let header_b64 = URL_SAFE_NO_PAD.encode(header_string.as_bytes()); @@ -109,7 +110,6 @@ impl AttestationTokenBroker for SimpleAttestationTokenBroker { "iss": self.config.issuer_name.clone(), "iat": now.unix_timestamp(), "jti": id, - "jwk": serde_json::from_str::(&self.pubkey_jwks()?)?["keys"][0].clone(), "nbf": now.unix_timestamp(), "exp": exp.unix_timestamp(), }) From eadfd273547743f041f31077bae6dce176e9fb3e Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Wed, 9 Oct 2024 12:12:51 +0800 Subject: [PATCH 3/3] docs/ci: abondon HTTPS_CRYPTO and update token verifier config Due to latest change, KBS will not maintain both rustls and openssl suites for HTTPS. Thus we need to delete all the options of HTTPS_CRYPTO config in documents and codes. Also, the latest change changes the config format of `attestation_token_config`, this patch also applies the change. Signed-off-by: Xynnn007 --- .github/workflows/kbs-rust.yml | 2 +- .github/workflows/push-kbs-image-to-ghcr.yml | 7 +----- deps/verifier/src/se/README.md | 6 ++--- kbs/Makefile | 7 +++--- kbs/README.md | 5 +--- kbs/config/docker-compose/kbs-config.toml | 2 +- kbs/config/kbs-config-grpc.toml | 2 +- .../kbs-config-intel-trust-authority.toml | 1 - kbs/config/kbs-config.toml | 2 +- kbs/config/kubernetes/base/kbs-config.toml | 2 +- kbs/config/kubernetes/ita/kbs-config.toml | 1 - kbs/docker/Dockerfile | 3 +-- kbs/docker/coco-as-grpc/Dockerfile | 3 +-- kbs/docker/intel-trust-authority/Dockerfile | 3 +-- kbs/docker/rhel-ubi/Dockerfile | 2 +- kbs/docs/config.md | 25 ++++++++++++++----- kbs/docs/self-signed-https.md | 2 +- kbs/quickstart.md | 20 ++++++++++++--- kbs/test/config/kbs.toml | 2 +- kbs/test/config/resource-kbs.toml | 1 - 20 files changed, 55 insertions(+), 43 deletions(-) diff --git a/.github/workflows/kbs-rust.yml b/.github/workflows/kbs-rust.yml index f99c102a20..d468963b74 100644 --- a/.github/workflows/kbs-rust.yml +++ b/.github/workflows/kbs-rust.yml @@ -59,7 +59,7 @@ jobs: - name: KBS Build [Built-in CoCo AS, OpenSSL] working-directory: kbs - run: make HTTPS_CRYPTO=openssl + run: make - name: KBS Build [gRPC CoCo AS, RustTLS] working-directory: kbs diff --git a/.github/workflows/push-kbs-image-to-ghcr.yml b/.github/workflows/push-kbs-image-to-ghcr.yml index 4c4a25e9ef..ed30c3b6b4 100644 --- a/.github/workflows/push-kbs-image-to-ghcr.yml +++ b/.github/workflows/push-kbs-image-to-ghcr.yml @@ -25,15 +25,12 @@ jobs: include: - tag: kbs docker_file: kbs/docker/Dockerfile - https_crypto: openssl name: build-in AS - tag: kbs-grpc-as docker_file: kbs/docker/coco-as-grpc/Dockerfile - https_crypto: rustls name: gRPC AS - tag: kbs-ita-as docker_file: kbs/docker/intel-trust-authority/Dockerfile - https_crypto: rustls name: Intel Trust Authority AS runs-on: ${{ matrix.instance }} @@ -56,12 +53,10 @@ jobs: run: | commit_sha=${{ github.sha }} arch=$(uname -m) - https_crypto=${{ matrix.https_crypto }} - [ "${arch}" = "s390x" ] && https_crypto=openssl DOCKER_BUILDKIT=1 docker build -f "${{ matrix.docker_file }}" --push \ -t "ghcr.io/confidential-containers/staged-images/${{ matrix.tag }}:${commit_sha}-${arch}" \ -t "ghcr.io/confidential-containers/staged-images/${{ matrix.tag }}:latest-${arch}" \ - --build-arg ARCH="${arch}" --build-arg HTTPS_CRYPTO="${https_crypto}" . + --build-arg ARCH="${arch}" . publish_multi_arch_image: needs: build_and_push diff --git a/deps/verifier/src/se/README.md b/deps/verifier/src/se/README.md index 0ee09c0882..d2cfa5a4ba 100644 --- a/deps/verifier/src/se/README.md +++ b/deps/verifier/src/se/README.md @@ -67,7 +67,7 @@ openssl pkey -in kbs.key -pubout -out kbs.pem - Build KBS ```bash -cargo install --locked --debug --path kbs/src/kbs --no-default-features --features coco-as-builtin,openssl,resource,opa +cargo install --locked --debug --path kbs/src/kbs --no-default-features --features coco-as-builtin,resource,opa ``` - Prepare the material retrieved above, similar as: @@ -101,7 +101,7 @@ auth_public_key = "/kbs/kbs.pem" insecure_http = true [attestation_token_config] -attestation_token_type = "CoCo" +insecure_key = true [as_config] work_dir = "/opt/confidential-containers/attestation-service" @@ -128,7 +128,7 @@ export SE_SKIP_CERTS_VERIFICATION=true ## (Option 2) Launch KBS via docker-compose - Build the docker image ``` -DOCKER_BUILDKIT=1 docker build --build-arg HTTPS_CRYPTO="openssl" --build-arg ARCH="s390x" -t ghcr.io/confidential-containers/staged-images/kbs:latest . -f kbs/docker/Dockerfile +DOCKER_BUILDKIT=1 docker build --build-arg --build-arg ARCH="s390x" -t ghcr.io/confidential-containers/staged-images/kbs:latest . -f kbs/docker/Dockerfile ``` - Prepare a docker compose file, similar as: diff --git a/kbs/Makefile b/kbs/Makefile index 33d76f642d..90c6267d33 100644 --- a/kbs/Makefile +++ b/kbs/Makefile @@ -1,5 +1,4 @@ AS_TYPE ?= coco-as -HTTPS_CRYPTO ?= rustls POLICY_ENGINE ?= ALIYUN ?= false @@ -39,16 +38,16 @@ build: background-check-kbs .PHONY: background-check-kbs background-check-kbs: - cargo build -p kbs --locked --release --no-default-features --features $(AS_FEATURE),resource,$(HTTPS_CRYPTO),$(POLICY_ENGINE),$(FEATURES) + cargo build -p kbs --locked --release --no-default-features --features $(AS_FEATURE),resource,$(POLICY_ENGINE),$(FEATURES) .PHONY: passport-issuer-kbs passport-issuer-kbs: - cargo build -p kbs --locked --release --no-default-features --features $(AS_FEATURE),$(HTTPS_CRYPTO),$(FEATURES) + cargo build -p kbs --locked --release --no-default-features --features $(AS_FEATURE),$(FEATURES) mv ../target/release/kbs ../target/release/issuer-kbs .PHONY: passport-resource-kbs passport-resource-kbs: - cargo build -p kbs --locked --release --no-default-features --features $(HTTPS_CRYPTO),resource,$(POLICY_ENGINE),$(FEATURES) + cargo build -p kbs --locked --release --no-default-features --features resource,$(POLICY_ENGINE),$(FEATURES) mv ../target/release/kbs ../target/release/resource-kbs .PHONY: cli diff --git a/kbs/README.md b/kbs/README.md index fd322f3ab3..6b6e69c6e1 100644 --- a/kbs/README.md +++ b/kbs/README.md @@ -90,11 +90,10 @@ The Makefile supports a number of other configuration parameters. For example, ```shell -make background-check-kbs [HTTPS_CRYPTO=?] [POLICY_ENGINE=?] [AS_TYPES=?] [COCO_AS_INTEGRATION_TYPE=?] [ALIYUN=?] +make background-check-kbs [POLICY_ENGINE=?] [AS_TYPES=?] [COCO_AS_INTEGRATION_TYPE=?] [ALIYUN=?] ``` The parameters -- `HTTPS_CRYPTO`: either `rustls` or `openssl` can be specified. If not provided, `rustls` is default. - `POLICY_ENGINE`: The KBS has a policy engine to facilitate access control. This should not be confused with the policy engine in the AS, which determines whether or not TEE evidence is valid. `POLICY_ENGINE` determines which type of policy engine the KBS will use. Today only `opa` is supported. The KBS can also be built without a policy engine if it is not required. - `AS_TYPES`: The KBS supports multiple backend attestation services. `AS_TYPES` selects which verifier to use. The options are `coco-as` and `intel-trust-authority-as`. @@ -103,8 +102,6 @@ if it is not required. ## HTTPS Support The KBS can use HTTPS. This requires a crypto backend. -`HTTPS_CRYPTO` determines which backend will be used. -The options are `rustls` and `openssl`. The default is `rustls`. If you want a self-signed cert for test cases, please refer to [the document](docs/self-signed-https.md). diff --git a/kbs/config/docker-compose/kbs-config.toml b/kbs/config/docker-compose/kbs-config.toml index b999639892..461d2aa549 100644 --- a/kbs/config/docker-compose/kbs-config.toml +++ b/kbs/config/docker-compose/kbs-config.toml @@ -3,7 +3,7 @@ auth_public_key = "/opt/confidential-containers/kbs/user-keys/public.pub" insecure_http = true [attestation_token_config] -attestation_token_type = "CoCo" +insecure_key = true [grpc_config] as_addr = "http://as:50004" diff --git a/kbs/config/kbs-config-grpc.toml b/kbs/config/kbs-config-grpc.toml index 04bfd13810..4bc5969173 100644 --- a/kbs/config/kbs-config-grpc.toml +++ b/kbs/config/kbs-config-grpc.toml @@ -2,7 +2,7 @@ insecure_http = true insecure_api = true [attestation_token_config] -attestation_token_type = "CoCo" +insecure_key = true [grpc_config] as_addr = "http://127.0.0.1:50004" diff --git a/kbs/config/kbs-config-intel-trust-authority.toml b/kbs/config/kbs-config-intel-trust-authority.toml index 48d435b64f..070841da69 100644 --- a/kbs/config/kbs-config-intel-trust-authority.toml +++ b/kbs/config/kbs-config-intel-trust-authority.toml @@ -2,7 +2,6 @@ insecure_http = true insecure_api = true [attestation_token_config] -attestation_token_type = "Jwk" trusted_certs_paths = ["https://portal.trustauthority.intel.com"] [intel_trust_authority_config] diff --git a/kbs/config/kbs-config.toml b/kbs/config/kbs-config.toml index d04fd53408..7b99f03599 100644 --- a/kbs/config/kbs-config.toml +++ b/kbs/config/kbs-config.toml @@ -2,7 +2,7 @@ insecure_http = true insecure_api = true [attestation_token_config] -attestation_token_type = "CoCo" +insecure_key = true [repository_config] type = "LocalFs" diff --git a/kbs/config/kubernetes/base/kbs-config.toml b/kbs/config/kubernetes/base/kbs-config.toml index c6544eece9..67d01a6ffa 100644 --- a/kbs/config/kubernetes/base/kbs-config.toml +++ b/kbs/config/kubernetes/base/kbs-config.toml @@ -5,7 +5,7 @@ auth_public_key = "/kbs/kbs.pem" insecure_http = true [attestation_token_config] -attestation_token_type = "CoCo" +insecure_key = true [as_config] work_dir = "/opt/confidential-containers/attestation-service" diff --git a/kbs/config/kubernetes/ita/kbs-config.toml b/kbs/config/kubernetes/ita/kbs-config.toml index 0bba5e3f2c..044864e78d 100644 --- a/kbs/config/kubernetes/ita/kbs-config.toml +++ b/kbs/config/kubernetes/ita/kbs-config.toml @@ -5,7 +5,6 @@ auth_public_key = "/kbs/kbs.pem" insecure_http = true [attestation_token_config] -attestation_token_type = "Jwk" trusted_certs_paths = ["https://portal.trustauthority.intel.com"] [intel_trust_authority_config] diff --git a/kbs/docker/Dockerfile b/kbs/docker/Dockerfile index 2831d6a5b8..f6bd8294a0 100644 --- a/kbs/docker/Dockerfile +++ b/kbs/docker/Dockerfile @@ -1,6 +1,5 @@ FROM rust:slim as builder ARG ARCH=x86_64 -ARG HTTPS_CRYPTO=rustls ARG ALIYUN=false ENV DEBIAN_FRONTEND noninteractive @@ -37,7 +36,7 @@ RUN if [ "${ARCH}" = "x86_64" ]; then curl -fsSL https://download.01.org/intel-s WORKDIR /usr/src/kbs COPY . . -RUN cd kbs && make AS_FEATURE=coco-as-builtin HTTPS_CRYPTO=${HTTPS_CRYPTO} POLICY_ENGINE=opa ALIYUN=${ALIYUN} && \ +RUN cd kbs && make AS_FEATURE=coco-as-builtin POLICY_ENGINE=opa ALIYUN=${ALIYUN} && \ make install-kbs FROM ubuntu:22.04 diff --git a/kbs/docker/coco-as-grpc/Dockerfile b/kbs/docker/coco-as-grpc/Dockerfile index 2a96e9045d..67f099e6ac 100644 --- a/kbs/docker/coco-as-grpc/Dockerfile +++ b/kbs/docker/coco-as-grpc/Dockerfile @@ -1,6 +1,5 @@ FROM rust:latest as builder ARG ARCH=x86_64 -ARG HTTPS_CRYPTO=rustls ARG ALIYUN=false WORKDIR /usr/src/kbs @@ -9,7 +8,7 @@ COPY . . RUN apt-get update && apt install -y protobuf-compiler git # Build and Install KBS -RUN cd kbs && make AS_FEATURE=coco-as-grpc HTTPS_CRYPTO=${HTTPS_CRYPTO} POLICY_ENGINE=opa ALIYUN=${ALIYUN} && \ +RUN cd kbs && make AS_FEATURE=coco-as-grpc POLICY_ENGINE=opa ALIYUN=${ALIYUN} && \ make install-kbs FROM ubuntu:22.04 diff --git a/kbs/docker/intel-trust-authority/Dockerfile b/kbs/docker/intel-trust-authority/Dockerfile index a2b4f650e2..0638b9cf82 100644 --- a/kbs/docker/intel-trust-authority/Dockerfile +++ b/kbs/docker/intel-trust-authority/Dockerfile @@ -1,5 +1,4 @@ FROM rust:latest as builder -ARG HTTPS_CRYPTO=rustls ARG ALIYUN=false WORKDIR /usr/src/kbs @@ -8,7 +7,7 @@ COPY . . RUN apt-get update && apt install -y git # Build and Install KBS -RUN cd kbs && make AS_FEATURE=intel-trust-authority-as HTTPS_CRYPTO=${HTTPS_CRYPTO} POLICY_ENGINE=opa ALIYUN=${ALIYUN} && \ +RUN cd kbs && make AS_FEATURE=intel-trust-authority-as POLICY_ENGINE=opa ALIYUN=${ALIYUN} && \ make install-kbs FROM ubuntu:22.04 diff --git a/kbs/docker/rhel-ubi/Dockerfile b/kbs/docker/rhel-ubi/Dockerfile index 426c9a8d31..a49ee0aaee 100644 --- a/kbs/docker/rhel-ubi/Dockerfile +++ b/kbs/docker/rhel-ubi/Dockerfile @@ -15,7 +15,7 @@ dnf -y install --nogpgcheck --repofrompath "sgx,file:///root/sgx_rpm_local_repo" # Build. WORKDIR /usr/src/kbs COPY . . -ARG KBS_FEATURES=coco-as-builtin,rustls,resource,opa +ARG KBS_FEATURES=coco-as-builtin,resource,opa RUN \ cargo install --locked --root /usr/local/ --path kbs --bin kbs --no-default-features --features ${KBS_FEATURES} && \ # Collect linked files necessary for the binary to run. diff --git a/kbs/docs/config.md b/kbs/docs/config.md index 6181e3d98e..1d2498bf17 100644 --- a/kbs/docs/config.md +++ b/kbs/docs/config.md @@ -39,12 +39,26 @@ The following properties can be set under the `attestation_token_config` section | Property | Type | Description | Required | Default | |----------------------------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------| -| `attestation_token_config` | String | Attestation token broker type. Valid values: `CoCo`, `Jwk` | Yes | - | -| `trusted_certs_paths` | String Array | Trusted Certificates file (PEM format) for `CoCo` or a valid Url (`file://` or `https://`) pointing to a JWKSet certificates (local or OpenID) for `Jwk` | No | - | +| `trusted_jwk_sets` | String Array | Valid Url (`file://` or `https://`) pointing to trusted JWKSets (local or OpenID) for Attestation Tokens trustworthy verification | No | - | +| `trusted_certs_paths` | String Array | Trusted Certificates file (PEM format) for Attestation Tokens trustworthy verification | No | - | +| `extra_teekey_paths` | String Array | User defined paths to the tee public key in the JWT body | No | - | +| `insecure_key` | Boolean | Whether to check the trustworthy of the JWK inside JWT. See comments. | No | `false` | +Each JWT contains a TEE Public Key. Users can use the `extra_teekey_paths` field to additionally specify the path of this Key in the JWT. +Example of `extra_teekey_paths` is `/attester_runtime_data/tee-pubkey` which refers to the key +`attester_runtime_data.tee-pubkey` inside the JWT body claims. By default CoCo AS Token and Intel TA +Token TEE Public Key paths are supported. -If `trusted_certs_paths` is set, KBS will forcibly check the validity of the Attestation Token signature public key certificate, -if not set this field, KBS will skip the verification of the certificate. +For Attestation Services like CoCo-AS, the public key to verify the JWT will be given +in the token's `jwk` field (with or without the public key cert chain `x5c`). + +- If `insecure_key` is set to `true`, KBS will ignore to verify the trustworthy of the `jwk`. +- If `insecure_key` is set to `false`, KBS will look up its `trusted_certs_paths` and the `x5c` +field to verify the trustworthy of the `jwk`. + +For Attestation Services like Intel TA, there will only be a `kid` field inside the JWT. +The `kid` field is used to look up the trusted jwk configured by KBS via `trusted_jwk_sets` to +verify the integrity and trustworthy of the JWT. ### Repository Configuration @@ -207,8 +221,7 @@ insecure_http = true insecure_api = true [attestation_token_config] -attestation_token_type = "Jwk" -trusted_certs_paths = ["https://portal.trustauthority.intel.com"] +trusted_jwk_sets = ["https://portal.trustauthority.intel.com"] [repository_config] type = "LocalFs" diff --git a/kbs/docs/self-signed-https.md b/kbs/docs/self-signed-https.md index 8899f304e2..4c36f62f8a 100644 --- a/kbs/docs/self-signed-https.md +++ b/kbs/docs/self-signed-https.md @@ -72,7 +72,7 @@ auth_public_key = "/etc/public.pub" insecure_api = true [attestation_token_config] -attestation_token_type = "CoCo" +insecure_key = true [repository_config] type = "LocalFs" diff --git a/kbs/quickstart.md b/kbs/quickstart.md index d793c750ad..5466fff13d 100644 --- a/kbs/quickstart.md +++ b/kbs/quickstart.md @@ -240,13 +240,27 @@ Adding the following content to JSON config file of gRPC AS: ### Configure trusted root certificate of KBS -Adding the following content to the config file of Resource KBS to specify trusted root certificate (PEM format), -which used to verify the trustworthy of the certificate in Attestation Token: +Attestation Tokens are now all in JWT format. + +Adding the following content to the config file of Resource KBS to specify trusted root certificate (PEM format) +or JWK set which are used to verify the trustworthy of the Attestation Token: ```toml [attestation_token_config] -attestation_token_type = "CoCo" +# Path of root certificate used to verify the trustworthy of `x5c` extension in the JWT trusted_certs_paths = ["/path/to/trusted_cacert.pem"] + +# URL (`path://` or `https://`) of the trusted JWK that can be indexed by `kid` in +# JWT Header. +trusted_jwk_sets = ["/url/to/trusted_jwk_set"] + +# For Attestation Services like CoCo-AS, the public key to verify the JWT will be given +# in the token's `jwk` field (with or without the public key cert chain `x5c`). +# +# - If this flag is set to `true`, KBS will ignore to verify the trustworthy of the `jwk`. +# - If this flag is set to `false`, KBS will look up its `trusted_certs_paths` and the `x5c` +# field to verify the trustworthy of the `jwk`. +insecure_key = false ``` If `trusted_certs_paths` field is not set, KBS will skip the verification of the certificate in Attestation Token. diff --git a/kbs/test/config/kbs.toml b/kbs/test/config/kbs.toml index 0f08b733f9..7c6314ab5a 100644 --- a/kbs/test/config/kbs.toml +++ b/kbs/test/config/kbs.toml @@ -5,7 +5,7 @@ private_key = "./work/https.key" certificate = "./work/https.crt" [attestation_token_config] -attestation_token_type = "CoCo" +trusted_certs_paths = ["./work/token-cert.pem"] [repository_config] type = "LocalFs" diff --git a/kbs/test/config/resource-kbs.toml b/kbs/test/config/resource-kbs.toml index 5c14ab5195..8abbec27eb 100644 --- a/kbs/test/config/resource-kbs.toml +++ b/kbs/test/config/resource-kbs.toml @@ -3,7 +3,6 @@ auth_public_key = "./work/kbs.pem" insecure_http = true [attestation_token_config] -attestation_token_type = "CoCo" trusted_certs_paths = ["./work/ca-cert.pem"] [repository_config]