diff --git a/Cargo.lock b/Cargo.lock index c4f3e15ae..01e37d953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -415,7 +415,7 @@ dependencies = [ "env_logger 0.10.2", "jsonwebtoken", "jwt-simple", - "kbs-types", + "kbs-types 0.6.0", "lazy_static", "log", "mobc", @@ -539,7 +539,7 @@ dependencies = [ "env_logger 0.10.2", "futures", "hex", - "kbs-types", + "kbs-types 0.6.0", "lazy_static", "log", "openssl", @@ -579,7 +579,7 @@ dependencies = [ "csv-rs", "hyper", "hyper-tls", - "kbs-types", + "kbs-types 0.5.3", "log", "nix", "occlum_dcap", @@ -1315,7 +1315,7 @@ dependencies = [ "anyhow", "base64 0.21.7", "ctr", - "kbs-types", + "kbs-types 0.5.3", "rand", "rsa 0.9.6", "serde", @@ -2493,6 +2493,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kbs-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "febd73b2b1df274ea454d81ddf76f596af9754410b7ed6f988f2e1782a175da3" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "kbs_protocol" version = "0.1.0" @@ -2504,7 +2514,7 @@ dependencies = [ "base64 0.21.7", "crypto", "jwt-simple", - "kbs-types", + "kbs-types 0.5.3", "log", "reqwest", "resource_uri", @@ -5438,7 +5448,7 @@ dependencies = [ "hex", "jsonwebkey", "jsonwebtoken", - "kbs-types", + "kbs-types 0.6.0", "log", "openssl", "rstest", diff --git a/Cargo.toml b/Cargo.toml index 8d4aacc3f..4ffba909b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ clap = { version = "4", features = ["derive"] } config = "0.13.3" env_logger = "0.10.0" hex = "0.4.3" -kbs-types = "0.5.3" +kbs-types = "0.6.0" jsonwebtoken = { version = "9", default-features = false } log = "0.4.17" prost = "0.11.0" @@ -47,3 +47,4 @@ tokio = { version = "1.23.0", features = ["full"] } tempfile = "3.4.0" tonic = "0.8.1" tonic-build = "0.8.0" + diff --git a/attestation-service/README.md b/attestation-service/README.md index e64691e5d..56eed639b 100644 --- a/attestation-service/README.md +++ b/attestation-service/README.md @@ -13,6 +13,7 @@ Today, the AS can validate evidence from the following TEEs: - Hygon CSV - Intel TDX with vTPM on Azure - AMD SEV-SNP with vTPM on Azure +- IBM Secure Execution (SE) # Overview ``` @@ -80,6 +81,7 @@ Please refer to the individual verifiers for the specific format of the evidence - Azure TDX vTPM: [Evidence](./verifier/src/az_tdx_vtpm/mod.rs) - Arm CCA: [CcaEvidence](./verifier/src/cca/mod.rs) - Hygon CSV: [CsvEvidence](./verifier/src/csv/mod.rs) +- IBM Secure Execution (SE) [(SeEvidence)](./verifier/src/se/mod.rs) ## Output @@ -132,6 +134,7 @@ Supported Verifier Drivers: - `azsnpvtpm`: Verifier Driver for Azure vTPM based on SNP (Azure SNP vTPM) - `cca`: Verifier Driver for Confidential Compute Architecture (Arm CCA). - `csv`: Verifier Driver for China Security Virtualization (Hygon CSV). +- `se`: Verifier Driver for IBM Secure Execution (SE). ### Policy Engine diff --git a/attestation-service/attestation-service/Cargo.toml b/attestation-service/attestation-service/Cargo.toml index d3bda03d3..bbb354e54 100644 --- a/attestation-service/attestation-service/Cargo.toml +++ b/attestation-service/attestation-service/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [features] -default = [ "restful-bin", "rvps-grpc", "rvps-builtin", "all-verifier" ] +default = [ "restful-bin", "rvps-grpc", "rvps-builtin" ] all-verifier = [ "verifier/all-verifier" ] tdx-verifier = [ "verifier/tdx-verifier" ] sgx-verifier = [ "verifier/sgx-verifier" ] @@ -13,6 +13,7 @@ az-tdx-vtpm-verifier = [ "verifier/az-tdx-vtpm-verifier" ] snp-verifier = [ "verifier/snp-verifier" ] csv-verifier = [ "verifier/csv-verifier" ] cca-verifier = [ "verifier/cca-verifier" ] +se-verifier = [ "verifier/se-verifier" ] # Only for testing and CI rvps-builtin = [ "reference-value-provider-service" ] @@ -62,7 +63,11 @@ thiserror = { workspace = true, optional = true } tokio.workspace = true tonic = { workspace = true, optional = true } uuid = { version = "1.1.2", features = ["v4"] } -verifier = { path = "../verifier", default-features = false } +[target.'cfg(not(target_arch = "s390x"))'.dependencies] +verifier = { path = "../verifier", default-features = false, features = ["all-verifier"] } + +[target.'cfg(target_arch = "s390x")'.dependencies] +verifier = { path = "../verifier", default-features = false, features = ["se-verifier"] } [build-dependencies] shadow-rs.workspace = true diff --git a/attestation-service/attestation-service/src/bin/grpc/mod.rs b/attestation-service/attestation-service/src/bin/grpc/mod.rs index a0f0947ca..200051d6d 100644 --- a/attestation-service/attestation-service/src/bin/grpc/mod.rs +++ b/attestation-service/attestation-service/src/bin/grpc/mod.rs @@ -16,7 +16,8 @@ use tonic::{Request, Response, Status}; use crate::as_api::attestation_service_server::{AttestationService, AttestationServiceServer}; use crate::as_api::{ - AttestationRequest, AttestationResponse, SetPolicyRequest, SetPolicyResponse, Tee as GrpcTee, + AttestationRequest, AttestationResponse, ChallengeRequest, ChallengeResponse, SetPolicyRequest, + SetPolicyResponse, Tee as GrpcTee, }; use crate::rvps_api::reference_value_provider_service_server::{ @@ -39,6 +40,7 @@ fn to_kbs_tee(tee: GrpcTee) -> Tee { GrpcTee::AzSnpVtpm => Tee::AzSnpVtpm, GrpcTee::Cca => Tee::Cca, GrpcTee::AzTdxVtpm => Tee::AzTdxVtpm, + GrpcTee::Se => Tee::Se, } } @@ -199,6 +201,30 @@ impl AttestationService for Arc> { let res = AttestationResponse { attestation_token }; Ok(Response::new(res)) } + + async fn get_attestation_challenge( + &self, + request: Request, + ) -> Result, Status> { + let request: ChallengeRequest = request.into_inner(); + let tee = to_kbs_tee( + GrpcTee::from_i32(request.tee) + .ok_or_else(|| Status::aborted(format!("Invalid TEE {}", request.tee)))?, + ); + + let attestation_challenge = self + .read() + .await + .attestation_service + .generate_supplemental_challenge(tee, request.tee_params.clone()) + .await + .map_err(|e| Status::aborted(format!("Challenge: {e:?}")))?; + + let res = ChallengeResponse { + attestation_challenge, + }; + Ok(Response::new(res)) + } } #[tonic::async_trait] diff --git a/attestation-service/attestation-service/src/bin/restful-as.rs b/attestation-service/attestation-service/src/bin/restful-as.rs index 1f25da310..e03fd71f4 100644 --- a/attestation-service/attestation-service/src/bin/restful-as.rs +++ b/attestation-service/attestation-service/src/bin/restful-as.rs @@ -13,7 +13,7 @@ use strum::{AsRefStr, EnumString}; use thiserror::Error; use tokio::sync::RwLock; -use crate::restful::{attestation, get_policies, set_policy}; +use crate::restful::{attestation, get_challenge, get_policies, set_policy}; mod restful; @@ -48,6 +48,9 @@ enum WebApi { #[strum(serialize = "/policy")] Policy, + + #[strum(serialize = "/challenge")] + Challenge, } #[derive(Error, Debug)] @@ -100,6 +103,7 @@ async fn main() -> Result<(), RestfulError> { .route(web::post().to(set_policy)) .route(web::get().to(get_policies)), ) + .service(web::resource(WebApi::Challenge.as_ref()).route(web::post().to(get_challenge))) .app_data(web::Data::clone(&attestation_service)) }); diff --git a/attestation-service/attestation-service/src/bin/restful/mod.rs b/attestation-service/attestation-service/src/bin/restful/mod.rs index fba40f34a..9dcee1d13 100644 --- a/attestation-service/attestation-service/src/bin/restful/mod.rs +++ b/attestation-service/attestation-service/src/bin/restful/mod.rs @@ -44,6 +44,12 @@ pub struct AttestationRequest { policy_ids: Vec, } +#[derive(Debug, Serialize, Deserialize)] +pub struct ChallengeRequest { + tee: String, + tee_params: String, +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] enum Data { @@ -62,6 +68,7 @@ fn to_tee(tee: &str) -> anyhow::Result { "csv" => Tee::Csv, "sample" => Tee::Sample, "aztdxvtpm" => Tee::AzTdxVtpm, + "se" => Tee::Se, other => bail!("tee `{other} not supported`"), }; @@ -173,6 +180,21 @@ pub async fn set_policy( Ok(HttpResponse::Ok().body("")) } +/// This handler uses json extractor +pub async fn get_challenge( + request: web::Json, + cocoas: web::Data>>, +) -> Result { + let tee = to_tee(&request.tee)?; + let challenge = cocoas + .read() + .await + .generate_supplemental_challenge(tee, request.tee_params.clone()) + .await + .context("generate challenge")?; + Ok(HttpResponse::Ok().body(challenge)) +} + /// GET /policy /// GET /policy/{policy_id} pub async fn get_policies( diff --git a/attestation-service/attestation-service/src/lib.rs b/attestation-service/attestation-service/src/lib.rs index 4ba9cb813..41d4c0335 100644 --- a/attestation-service/attestation-service/src/lib.rs +++ b/attestation-service/attestation-service/src/lib.rs @@ -274,6 +274,17 @@ impl AttestationService { pub async fn register_reference_value(&mut self, message: &str) -> Result<()> { self.rvps.verify_and_extract(message).await } + + pub async fn generate_supplemental_challenge( + &self, + tee: Tee, + tee_parameters: String, + ) -> Result { + let verifier = verifier::to_verifier(&tee)?; + verifier + .generate_supplemental_challenge(tee_parameters) + .await + } } /// Get the expected init/runtime data and potential claims due to the given input diff --git a/attestation-service/docs/parsed_claims.md b/attestation-service/docs/parsed_claims.md index 4b2411cc8..be3ac34a1 100644 --- a/attestation-service/docs/parsed_claims.md +++ b/attestation-service/docs/parsed_claims.md @@ -92,3 +92,6 @@ The claim inherit the fields from the SEV-SNP claim with and additional `tpm` hi - `tpm.pcr{01,..,n}`: SHA256 PCR registers for the TEE's vTPM quote. Note: The TD Report and TD Quote are fetched during early boot in this TEE. Kernel, Initrd and rootfs are measured into the vTPM's registers. + +## IBM Secure Execution (SE) +TBD \ No newline at end of file diff --git a/attestation-service/protos/attestation.proto b/attestation-service/protos/attestation.proto index 405aad923..9c167b348 100644 --- a/attestation-service/protos/attestation.proto +++ b/attestation-service/protos/attestation.proto @@ -12,6 +12,7 @@ enum Tee { CSV = 6; CCA = 7; AzTdxVtpm = 8; + Se = 9; } message AttestationRequest { @@ -88,8 +89,17 @@ message SetPolicyRequest { } message SetPolicyResponse {} +message ChallengeRequest { + Tee tee = 1; + string tee_params = 2; +} +message ChallengeResponse { + string attestation_challenge = 1; +} + service AttestationService { rpc AttestationEvaluate(AttestationRequest) returns (AttestationResponse) {}; rpc SetAttestationPolicy(SetPolicyRequest) returns (SetPolicyResponse) {}; + rpc GetAttestationChallenge(ChallengeRequest) returns (ChallengeResponse) {}; // Get the GetPolicyRequest.user and GetPolicyRequest.tee specified Policy(.rego) } diff --git a/attestation-service/verifier/Cargo.toml b/attestation-service/verifier/Cargo.toml index ef0cd9df9..ec33b1350 100644 --- a/attestation-service/verifier/Cargo.toml +++ b/attestation-service/verifier/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [features] default = [ "all-verifier" ] -all-verifier = [ "tdx-verifier", "sgx-verifier", "snp-verifier", "az-snp-vtpm-verifier", "az-tdx-vtpm-verifier", "csv-verifier", "cca-verifier" ] +all-verifier = [ "tdx-verifier", "sgx-verifier", "snp-verifier", "az-snp-vtpm-verifier", "az-tdx-vtpm-verifier", "csv-verifier", "cca-verifier", "se-verifier" ] tdx-verifier = [ "eventlog-rs", "scroll", "sgx-dcap-quoteverify-rs" ] sgx-verifier = [ "scroll", "sgx-dcap-quoteverify-rs" ] az-snp-vtpm-verifier = [ "az-snp-vtpm", "sev", "snp-verifier" ] @@ -13,6 +13,7 @@ az-tdx-vtpm-verifier = [ "az-tdx-vtpm", "openssl", "tdx-verifier" ] snp-verifier = [ "asn1-rs", "openssl", "sev", "x509-parser" ] csv-verifier = [ "openssl", "csv-rs", "codicon" ] cca-verifier = [ "ear", "jsonwebtoken", "veraison-apiclient" ] +se-verifier = [ "openssl", "pv" ] [dependencies] anyhow.workspace = true @@ -35,9 +36,11 @@ jsonwebtoken = { workspace = true, default-features = false, optional = true } kbs-types.workspace = true log.workspace = true openssl = { version = "0.10.55", optional = true } +pv = { version = "0.10.0", package = "s390_pv" } scroll = { version = "0.11.0", default-features = false, features = ["derive"], optional = true } serde.workspace = true serde_json.workspace = true +serde_yaml = "0.9.0" sev = { version = "1.2.0", features = ["openssl", "snp"], optional = true } sgx-dcap-quoteverify-rs = { git = "https://github.com/intel/SGXDataCenterAttestationPrimitives", tag = "DCAP_1.16", optional = true } strum.workspace = true @@ -54,3 +57,7 @@ assert-json-diff.workspace = true rstest.workspace = true serial_test.workspace = true tokio.workspace = true + +[patch.crates-io] +s390_pv = { path = "/root/src/tmp_pv_crate/pv" } +s390_pv_core = { path = "/root/src/tmp_pv_crate/pv_core" } diff --git a/attestation-service/verifier/src/lib.rs b/attestation-service/verifier/src/lib.rs index 811e530aa..d0ada3d83 100644 --- a/attestation-service/verifier/src/lib.rs +++ b/attestation-service/verifier/src/lib.rs @@ -28,6 +28,9 @@ pub mod csv; #[cfg(feature = "cca-verifier")] pub mod cca; +#[cfg(feature = "se-verifier")] +pub mod se; + pub fn to_verifier(tee: &Tee) -> Result> { match tee { Tee::Sev => todo!(), @@ -99,6 +102,17 @@ pub fn to_verifier(tee: &Tee) -> Result> { } } } + + Tee::Se => { + cfg_if::cfg_if! { + if #[cfg(feature = "se-verifier")] { + Ok(Box::::default() as Box) + } else { + bail!("feature `se-verifier` is not enabled for `verifier` crate.") + } + } + } + } } @@ -152,6 +166,21 @@ pub trait Verifier { expected_report_data: &ReportData, expected_init_data_hash: &InitDataHash, ) -> Result; + + /// Generate the supplemental challenge + /// + /// Some TEE like IBM SE need a `challenge` generated on verifier side + /// and pass it to attester side. This challenge is used by attester to + /// generate the evidence + /// + /// A optional `tee_parameters` comes from the attester side as the input. + + async fn generate_supplemental_challenge( + &self, + _tee_parameters: String, + ) -> Result { + Ok(String::new()) + } } /// Padding or truncate the given data slice to the given `len` bytes. diff --git a/attestation-service/verifier/src/se/ibmse.rs b/attestation-service/verifier/src/se/ibmse.rs new file mode 100644 index 000000000..203fb50ff --- /dev/null +++ b/attestation-service/verifier/src/se/ibmse.rs @@ -0,0 +1,265 @@ +// Copyright (C) Copyright IBM Corp. 2024 +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::{anyhow, bail, Context, Result}; +use log::{debug, warn}; +use pv::{ + attest::{ + AdditionalData, AttestationFlags, AttestationItems, AttestationMeasAlg, + AttestationMeasurement, AttestationRequest, AttestationVersion, ExchangeFormatCtx, + ExchangeFormatVersion, + }, + misc::{CertificateOptions, open_file, read_exact_file, write_file, HexSlice}, + request::{openssl::pkey::PKey, BootHdrTags, Confidential, ReqEncrCtx, Request, SymKey, SymKeyType}, +}; +use serde::Serialize; +use serde_yaml; +use std::{fmt::Display, io::Read, process::ExitCode}; +use serde_with::base64::{Base64, Bcrypt, BinHex, Standard}; + +const EXIT_CODE_ATTESTATION_FAIL: u8 = 2; + +//TODO implement getters or an into function to convert into pv::AttestationCmd(better approach) +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +pub struct S390xAttestationRequest { + #[serde_as(as = "Base64")] + request_blob: Vec, + measurement_size: u32, + additional_size: u32, + //not the arpk but the real measuremtn key pv must provide a getter + #[serde_as(as = "Base64")] + encr_measurement_key: Vec, + //not the arpk but the request nonce pv must provide a getter + #[serde_as(as = "Base64")] + encr_request_nonce: Vec, + // IIRC the thing here should be stateless, therefore the request should contain the se header + // as well, right? To save bytes, only the really required bytes are included here. + // + // For this to work I need to add the AsRef<[u8]> impl for that type to pv crate, but that is + // very easy. as you see here: + // + // impl AsRef<[u8]> for BootHdrTags { + // fn as_ref(&self) -> &[u8] { + // self.as_bytes() + // } + // } + #[serde_as(as = "Base64")] + image_hdr_tags: BootHdrTags, +} + + +impl S390xAttestationRequest { + +// the caller of this fun then only has to serielize the request in (using serde-json) e.g. json and send it via the + // network + + +pub fn create(hkds: Vec, certs: Vec, crls: Vec, image_hdr_tags: BootHdrTags) -> Result { + let att_version = AttestationVersion::One; + let meas_alg = AttestationMeasAlg::HmacSha512; + + let mut att_flags = AttestationFlags::default(); + let mut arcb = AttestationRequest::new(att_version, meas_alg, att_flags)?; + + let verifier = CertVerifier::new(certs.as_slice(), crls.as_slice(), None, false)?; + + let mut arcb = AttestationRequest::new(att_version, meas_alg, att_flags)?; + for hkd in hkds { + let hk = read_file(hkd, "host-key document")?; + let certs = read_certs(&hk).map_err(|source| Error::HkdNotPemOrDer { + hkd: hkd.to_string(), + source, + })?; + if certs.is_empty() { + todo!(); + } + if certs.len() != 1 { + todo!(); + } + + // Panic: len is == 1 -> unwrap will succeed/not panic + let c = certs.first().unwrap(); + verifier.verify(c)?; + arcb.add_hostkey(c.public_key()?); + } + let encr_ctx = + ReqEncrCtx::random(SymKeyType::Aes256)?; + let request_blob = arcb.encrypt(&encr_ctx)?; + + let encr_measurement_key = /* TODO encrypt data I need to provide a getter for this in the pv crate*/; + let encr_request_nonce = /* TODO encrypt data I need to provide a getter for this in the pv crate*/; + +Self{ +request_blob, + measurement_size: meas_alg.expected_size(), +additional_size: arcb.flags().expected_additional_size(), +encr_measurement_key, +encr_request_nonce, +image_hdr_tags + } + + } +} +/// the caller of this fn has to deserialize the request first and serialize the result + + +//TODO insert user data +fn poc_calc_measurement(req: &S390xAttestationRequest) -> Result { + + let mut uvc: AttestationCmd = req.into(); //TODO impl Into in this crate + let uv = Uvdevice::open()?; + uv.send_cmd(&mut uvc)?; + + let res = uvc.into(); //TODO impl Into in this crate +Ok(res) + +} + + + +//TODO implement getters or an into function to convert from pv::AttestationCmd(better approach) +//and into +#[derive(Debug, Serialize, Deserialize)] +pub struct S390xAttestationResponse { + #[serde_as(as = "Base64")] + measurement: Vec, + #[serde_as(as = "Base64")] + additional_data: Vec, + #[serde_as(as = "Base64")] + user_data: Vec, + #[serde_as(as = "Base64")] + cuid: ConfigUid, +#[serde_as(as = "Base64")] + encr_measurement_key: Vec, + #[serde_as(as = "Base64")] + encr_request_nonce: Vec, + + #[serde_as(as = "Base64")] + image_hdr_tags: BootHdrTags, +} + +impl S390xAttestationResponse { +fn verify(&self) -> Result { + let meas_key = ...; + let nonce = ...; + + + let meas_key = PKey::hmac(conf.meas_key())?; +let items = AttestationItems::new(self.image_hdr_tags, ....)? + + + let measurement = + AttestationMeasurement::calculate(items, AttestationMeasAlg::HmacSha512, &meas_key)?; + +//todo check measuremt + // do something with additonal data, user_data, .... + + } + + + +} + + + + + + +pub fn verify(input: &mut dyn Read, hdr_file: String, arpk_file: String, output: &mut Vec, user_data: &mut Vec) -> Result { + let mut img = open_file(&hdr_file)?; + let arpk = SymKey::Aes256( + read_exact_file(&arpk_file, "Attestation request protection key").map(Confidential::new)?, + ); + let hdr = BootHdrTags::from_se_image(&mut img)?; + let ctx = ExchangeFormatCtx::read(mut input, true)?; + + let (auth, conf) = AttestationRequest::decrypt_bin(ctx.arcb(), &arpk)?; + let meas_key = PKey::hmac(conf.measurement_key())?; + let items = AttestationItems::from_exchange(&ctx, &hdr, conf.nonce())?; + + let measurement = + AttestationMeasurement::calculate(items, AttestationMeasAlg::HmacSha512, &meas_key)?; + + let uv_meas = ctx.measurement().ok_or(anyhow!( + "The input is missing the measurement. It is probably no attestation response" + ))?; + if !measurement.eq_secure(uv_meas) { + debug!("Measurement values:"); + debug!("Recieved: {}", HexSlice::from(uv_meas)); + debug!("Calculated: {}", HexSlice::from(measurement.as_ref())); + warn!("Attestation measurement verification failed. Calculated and received attestation measurement are not equal."); + return Ok(ExitCode::from(EXIT_CODE_ATTESTATION_FAIL)); + } + warn!("Attestation measurement verified"); + // Error impossible CUID is present Attestation verified + let pr_data = VerifyOutput::from_exchange(&ctx, auth.flags())?; + + warn!("{pr_data}"); + serde_yaml::to_writer(output, &pr_data)?; + + // How to get user_data, what's data here??? + if let Some(user_data) = &user_data { + match ctx.user() { + Some(data) => write_file(user_data, data, "user-data")?, + None => { + warn!("Location for `user-data` specified, but respose does not contain any user-data") + } + } + }; + + Ok(ExitCode::SUCCESS) +} + +pub fn create(host_key_documents: Vec, certs: Vec, crls: Vec, arpk_file: String, root_ca: Option) -> Result> { + let att_version = AttestationVersion::One; + let meas_alg = AttestationMeasAlg::HmacSha512; + + let mut att_flags = AttestationFlags::default(); + att_flags.set_image_phkh(); + + let mut arcb = AttestationRequest::new(att_version, meas_alg, att_flags)?; + debug!("Generated Attestation request"); + + let certificate_args = CertificateOptions { + host_key_documents, + no_verify: true, + certs, + crls, + offline: true, + root_ca, + }; + // Add host-key documents + certificate_args + .get_verified_hkds("attestation request")? + .into_iter() + .for_each(|k| arcb.add_hostkey(k)); + debug!("Added all host-keys"); + + let encr_ctx = + ReqEncrCtx::random(SymKeyType::Aes256).context("Failed to generate random input")?; + let ser_arcb = arcb.encrypt(&encr_ctx)?; + warn!("Successfully generated the request"); + + let output: Vec; + let exch_ctx = ExchangeFormatCtx::new_request( + ser_arcb, + meas_alg.exp_size(), + arcb.flags().expected_additional_size().into(), + )?; + exch_ctx.write(&mut output, ExchangeFormatVersion::One)?; + + let arpk = match encr_ctx.prot_key() { + SymKey::Aes256(k) => k, + _ => bail!("Unexpected key type"), + }; + write_file( + &arpk_file, + arpk.value(), + "Attestation request Protection Key", + )?; + + Result::Ok(output) +} diff --git a/attestation-service/verifier/src/se/mod.rs b/attestation-service/verifier/src/se/mod.rs new file mode 100644 index 000000000..b9f99da13 --- /dev/null +++ b/attestation-service/verifier/src/se/mod.rs @@ -0,0 +1,66 @@ +// Copyright (C) Copyright IBM Corp. 2024 +// +// SPDX-License-Identifier: Apache-2.0 +// + +use super::*; +use async_trait::async_trait; +use base64::prelude::*; +use serde_json::json; +use std::io::Cursor; +use pv::misc::CertificateOptions; +use crate::{InitDataHash, ReportData}; + +pub mod ibmse; + +#[derive(Debug, Default)] +pub struct SeVerifier {} + +#[async_trait] +impl Verifier for SeVerifier { + async fn evaluate( + &self, + evidence: &[u8], + expected_report_data: &ReportData, + expected_init_data_hash: &InitDataHash, + ) -> Result { + verify_evidence(evidence, expected_report_data, expected_init_data_hash).await + } + + async fn generate_supplemental_challenge( + &self, + _tee_parameters: String, + ) -> Result { + let hkds: Vec = vec![String::new(); 2]; + let certs: Vec = vec![String::new(); 2]; + let crls: Vec = vec![]; + let arpk = String::from("arpk_file_path"); + let root_ca = Option::Some(String::from("root_ca")); + + let challenge = ibmse::create(hkds, certs, crls, arpk, root_ca)?; + + Ok(BASE64_STANDARD.encode(challenge)) + } +} + +async fn verify_evidence( + evidence: &[u8], + _expected_report_data: &ReportData<'_>, + _expected_init_data_hash: &InitDataHash<'_>, +) -> Result { + let arpk = String::from("arpk_file_path"); + let hdr = String::from("hdr_file_path"); + + let mut cursor = Cursor::new(evidence); + let mut output: Vec = vec![]; + let mut userdata: Vec = vec![]; + let _res = ibmse::verify(&mut cursor, hdr, arpk, &mut output, &mut userdata); + + let claims_map = json!({ + "serial_number": format!("{}", "SE-ID"), + "measurement": format!("{}", BASE64_STANDARD.encode(output.clone())), + "report_data": format!("{}", BASE64_STANDARD.encode(userdata.clone())), + }); + + Ok(claims_map as TeeEvidenceParsedClaim) +} diff --git a/kbs/src/api/src/attestation/coco/builtin.rs b/kbs/src/api/src/attestation/coco/builtin.rs index 3f8eb4bac..477b06500 100644 --- a/kbs/src/api/src/attestation/coco/builtin.rs +++ b/kbs/src/api/src/attestation/coco/builtin.rs @@ -45,6 +45,18 @@ impl Attest for BuiltInCoCoAs { ) .await } + + async fn generate_supplemental_challenge( + &self, + tee: Tee, + tee_parameters: String, + ) -> Result { + self.inner + .read() + .await + .generate_supplemental_challenge(tee, tee_parameters) + .await + } } impl BuiltInCoCoAs { diff --git a/kbs/src/api/src/attestation/coco/grpc.rs b/kbs/src/api/src/attestation/coco/grpc.rs index 174d76b49..b60b8f7a8 100644 --- a/kbs/src/api/src/attestation/coco/grpc.rs +++ b/kbs/src/api/src/attestation/coco/grpc.rs @@ -16,7 +16,7 @@ use tonic::transport::Channel; use self::attestation::{ attestation_request::RuntimeData, attestation_service_client::AttestationServiceClient, - AttestationRequest, SetPolicyRequest, Tee as GrpcTee, + AttestationRequest, ChallengeRequest, SetPolicyRequest, Tee as GrpcTee, }; mod attestation { @@ -39,6 +39,7 @@ fn to_grpc_tee(tee: Tee) -> GrpcTee { Tee::Sgx => GrpcTee::Sgx, Tee::Snp => GrpcTee::Snp, Tee::Tdx => GrpcTee::Tdx, + Tee::Se => GrpcTee::Se, } } @@ -125,6 +126,27 @@ impl Attest for GrpcClientPool { Ok(token) } + + async fn generate_supplemental_challenge( + &self, + tee: Tee, + tee_parameters: String, + ) -> Result { + let req = tonic::Request::new(ChallengeRequest { + tee: to_grpc_tee(tee).into(), + tee_params: tee_parameters, + }); + + let mut client = { self.pool.lock().await.get().await? }; + + let challenge = client + .get_attestation_challenge(req) + .await? + .into_inner() + .attestation_challenge; + + Ok(challenge) + } } pub struct GrpcManager { diff --git a/kbs/src/api/src/attestation/intel_trust_authority/mod.rs b/kbs/src/api/src/attestation/intel_trust_authority/mod.rs index f77d51a0a..a9664ddaf 100644 --- a/kbs/src/api/src/attestation/intel_trust_authority/mod.rs +++ b/kbs/src/api/src/attestation/intel_trust_authority/mod.rs @@ -122,6 +122,14 @@ impl Attest for IntelTrustAuthority { Ok(resp_data.token.clone()) } + + async fn generate_supplemental_challenge( + &self, + _tee: Tee, + _tee_parameters: String, + ) -> Result { + Ok(String::new()) + } } impl IntelTrustAuthority { diff --git a/kbs/src/api/src/attestation/mod.rs b/kbs/src/api/src/attestation/mod.rs index db7fc4926..3db42837d 100644 --- a/kbs/src/api/src/attestation/mod.rs +++ b/kbs/src/api/src/attestation/mod.rs @@ -32,6 +32,13 @@ pub trait Attest: Send + Sync { /// Verify Attestation Evidence /// Return Attestation Results Token async fn verify(&self, tee: Tee, nonce: &str, attestation: &str) -> Result; + + /// generate the challenge payload to pass to attester based on Tee and nonce + async fn generate_supplemental_challenge( + &self, + tee: Tee, + tee_parameters: String, + ) -> Result; } /// Attestation Service @@ -89,4 +96,31 @@ impl AttestationService { AttestationService::IntelTA(inner) => inner.set_policy(input).await, } } + + pub async fn generate_supplemental_challenge( + &self, + tee: Tee, + tee_parameters: String, + ) -> Result { + match self { + #[cfg(feature = "coco-as-grpc")] + AttestationService::CoCoASgRPC(inner) => { + inner + .generate_supplemental_challenge(tee, tee_parameters) + .await + } + #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] + AttestationService::CoCoASBuiltIn(inner) => { + inner + .generate_supplemental_challenge(tee, tee_parameters) + .await + } + #[cfg(feature = "intel-trust-authority-as")] + AttestationService::IntelTA(inner) => { + inner + .generate_supplemental_challenge(tee, tee_parameters) + .await + } + } + } } diff --git a/kbs/src/api/src/http/attest.rs b/kbs/src/api/src/http/attest.rs index 0484336e7..95e6aae8e 100644 --- a/kbs/src/api/src/http/attest.rs +++ b/kbs/src/api/src/http/attest.rs @@ -7,8 +7,9 @@ use crate::{raise_error, session::SessionStatus}; use super::*; use anyhow::anyhow; -use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}; use base64::Engine; +use kbs_types::Challenge; use log::{debug, error, info}; use serde_json::json; @@ -17,11 +18,17 @@ pub(crate) async fn auth( request: web::Json, map: web::Data, timeout: web::Data, + attestation_service: web::Data>, ) -> Result { info!("Auth API called."); debug!("Auth Request: {:?}", &request); - let session = SessionStatus::auth(request.0, **timeout) + let extra_params = attestation_service + .generate_supplemental_challenge(request.tee, request.extra_params.clone()) + .await + .unwrap(); + + let session = SessionStatus::auth(request.0, **timeout, extra_params) .map_err(|e| Error::FailedAuthentication(format!("Session: {e}")))?; let response = HttpResponse::Ok() diff --git a/kbs/src/api/src/session.rs b/kbs/src/api/src/session.rs index 67943a75d..41c1997df 100644 --- a/kbs/src/api/src/session.rs +++ b/kbs/src/api/src/session.rs @@ -63,7 +63,7 @@ macro_rules! impl_member { } impl SessionStatus { - pub fn auth(request: Request, timeout: i64) -> Result { + pub fn auth(request: Request, timeout: i64, extra_params: String) -> Result { let version = Version::parse(&request.version).map_err(anyhow::Error::from)?; if !crate::VERSION_REQ.matches(&version) { bail!("Invalid Request version {}", request.version); @@ -76,7 +76,7 @@ impl SessionStatus { request, challenge: Challenge { nonce: nonce()?, - extra_params: String::new(), + extra_params, }, id, timeout,