Skip to content

Commit

Permalink
Verifier: IBM SE implementation PoC
Browse files Browse the repository at this point in the history
Signed-off-by: Qi Feng Huo <[email protected]>
  • Loading branch information
Qi Feng Huo committed May 16, 2024
1 parent 7c10203 commit f2e9626
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 149 deletions.
8 changes: 7 additions & 1 deletion attestation-service/docs/parsed_claims.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,10 @@ The claim inherit the fields from the SEV-SNP claim with and additional `tpm` hi
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
- `se.version`: The version this quote structure.
- `se.cuid`: The id of the tee.
- `se.additional_data`: Data provided by tee hardware.
- `se.image_hash`: The hash of the se image.
- `se.user_data`: Custom attestation key owner data.
- `se.report_data`: Data provided by the user.

321 changes: 190 additions & 131 deletions attestation-service/verifier/src/se/ibmse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

use anyhow::{anyhow, bail, Context, Result};
use log::{debug, warn};
use openssl::{
pkey::{PKey},
rand::rand_bytes,
rsa::{Rsa, Padding},
symm::{encrypt, Cipher},
};
use pv::{
attest::{
AdditionalData, AttestationFlags, AttestationItems, AttestationMeasAlg,
Expand All @@ -17,149 +23,202 @@ use pv::{
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;

#[derive(Serialize)]
struct VerifyOutput<'a> {
cuid: HexSlice<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
additional_data: Option<HexSlice<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
additional_data_fields: Option<AdditionalData<HexSlice<'a>>>,
#[serde(skip_serializing_if = "Option::is_none")]
user_data: Option<HexSlice<'a>>,
#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
pub struct S390xAttestationRequest {
#[serde_as(as = "Base64")]
request_blob: Vec<u8>,
measurement_size: u32,
additional_size: u32,
#[serde_as(as = "Base64")]
encr_measurement_key: Vec<u8>,
#[serde_as(as = "Base64")]
encr_request_nonce: Vec<u8>,
#[serde_as(as = "Base64")]
image_hdr_tags: BootHdrTags,
}

impl<'a> VerifyOutput<'a> {
fn from_exchange(ctx: &'a ExchangeFormatCtx, flags: &AttestationFlags) -> Result<Self> {
let additional_data_fields = ctx
.additional()
.map(|a| AdditionalData::from_slice(a, flags))
.transpose()?;
let user_data = ctx.user().map(|u| u.into());

Ok(Self {
cuid: ctx.config_uid()?.into(),
additional_data: ctx.additional().map(|a| a.into()),
additional_data_fields,
user_data,
})
impl S390xAttestationRequest {
pub fn create(hkds: Vec<String>, certs: Vec<String>, crls: Vec<String>, image_hdr_tags: BootHdrTags) -> Result<Self> {
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)?;

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
}
}
}

impl<'a> Display for VerifyOutput<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Config UID:")?;
writeln!(f, "{:#}", self.cuid)?;
if let Some(data) = &self.additional_data {
writeln!(f, "Additional-data:")?;
writeln!(f, "{:#}", data)?;
}
if let Some(data) = &self.additional_data_fields {
writeln!(f, "Additional-data content:")?;
writeln!(f, "{:#}", data)?;
}
if let Some(data) = &self.user_data {
writeln!(f, "user-data:")?;
writeln!(f, "{:#}", data)?;
}
Ok(())
}
fn generate_nonce() -> Vec<u8> {
let mut key = [0u8; 64];
rand_bytes(&mut key).unwrap()
}

fn generate_hmac_sha512_key() -> Vec<u8> {
let mut key = [0u8; 64];
rand_bytes(&mut key).unwrap()
}

fn generate_measurement_key() -> Vec<u8> {
let hmac_sha512_key = generate_hmac_sha512_key();
PKey::hmac(&hmac_sha512_key).unwrap()
}

fn encr_measurement_key(hmac_sha512_key: Vec<u8>, pub_key_file: String) -> Vec<u8> {
let mut file = File::open(pub_key_file)?;
let mut contents = vec![];
file.read_to_end(&mut contents)?;

let rsa = Rsa::public_key_from_pem(&contents)?;
let rsa_public_key = PKey::from_rsa(rsa)?;

let mut encryptor = rsa_public_key.encryptor().unwrap();
let encrypted_hmac_key = encrypt(&mut encryptor, &hmac_sha512_key, Padding::PKCS1).unwrap()
}

fn decr_measurement_key(measurement_key: Vec<u8>, priv_key_file: String) -> Vec<u8> {
let mut file = File::open(priv_key_file)?;
let mut contents = vec![];
file.read_to_end(&mut contents)?;

let rsa = Rsa::private_key_from_pem(&contents)?;
let rsa_private_key = PKey::from_rsa(rsa)?;

let mut decryptor = rsa_private_key.private_decryptor().unwrap();
let decrypted_hmac_key = decryptor.decrypt(&encrypted_hmac_key).unwrap()
}

pub fn verify(input: &mut dyn Read, hdr_file: String, arpk_file: String, output: &mut Vec<u8>, user_data: &mut Vec<u8>) -> Result<ExitCode> {
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));
pub fn create(host_key_documents: Vec<String>, certs: Vec<String>, crls: Vec<String>, se_img_hdr: String, pub_key_file: String) -> Result<Vec<u8>, serde_json::Error> {
let mut request_blob: Vec<u8> = vec![];
let measurement_size : u32 = 0;
let additional_size : u32 = 0;
let encr_measurement_key = encr_measurement_key(generate_measurement_key()?, pub_key_file)?;
let encr_request_nonce = generate_nonce()?;
let mut hdr_file = open_file(se_img_hdr)?;
let image_hdr_tags = BootHdrTags::from_se_image(&mut hdr_file)).unwrap();
let mut se_request : S390xAttestationRequest = S390xAttestationRequest {
request_blob,
measurement_size,
additional_size,
encr_measurement_key,
encr_request_nonce,
image_hdr_tags,
}
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")
}
}
};
se_request = se_request.create(hkds, certs, crls, image_hdr_tags);
serde_json::to_vec(se_request)
}

// called by attester
// TODO insert user data
fn perform(req: &S390xAttestationRequest) -> Result<S390xAttestationResponse> {
let mut uvc: AttestationCmd = req.into(); //TODO impl Into<AttestatioCmd> in this crate
let uv = Uvdevice::open()?;
uv.send_cmd(&mut uvc)?;

let res = uvc.into(); //TODO impl Into<S390xAttestationResponse> in this crate
Ok(res)
}

pub fn verify(response: Vec<u8>, enc_priv_key: String, userdata: Vec<u8>) -> Result<Vec<u8>, serde_json::Error> {
// Deserialize evidence to S390xAttestationResponse
// TODO implement S390xAttestationResponse::into()
let se_response = S390xAttestationResponse::into(response)
se_response.verify(enc_priv_key)
serde_json::to_vec(se_response)
}


Ok(ExitCode::SUCCESS)
//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<u8>,
#[serde_as(as = "Base64")]
additional_data: Vec<u8>,
#[serde_as(as = "Base64")]
user_data: Vec<u8>,
#[serde_as(as = "Base64")]
cuid: ConfigUid,
#[serde_as(as = "Base64")]
encr_measurement_key: Vec<u8>,
#[serde_as(as = "Base64")]
encr_request_nonce: Vec<u8>,
#[serde_as(as = "Base64")]
image_hdr_tags: BootHdrTags,
}

pub fn create(host_key_documents: Vec<String>, certs: Vec<String>, crls: Vec<String>, arpk_file: String, root_ca: Option<String>) -> Result<Vec<u8>> {
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<u8>;
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)
#[derive(Debug, Serialize, Deserialize)]
pub struct S390xAttestationClaims {
cuid,
init_data,
se_additional_data,
se_image_hash,
se_user_data,
report_data,
version,
}

impl S390xAttestationResponse {
pub fn verify(&self, priv_key_file: String) -> Result<S390xAttestationClaims> {
let meas_key = decr_measurement_key(self.encr_measurement_key, priv_key_file);
let nonce = self.nonce;

let meas_key = PKey::hmac(meas_key)?;
let items = AttestationItems::new(self.image_hdr_tags, self.cuid, self.user_data, self.nonce, self.additional_data)?

let measurement =
AttestationMeasurement::calculate(items, AttestationMeasAlg::HmacSha512, &meas_key)?;

if !measurement.eq_secure(self.measurement) {
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));
}
// TODO do something with additonal data, user_data, ....

// TODO, construct claims from S390xAttestationResponse
let claims = S390xAttestationClaims{}
Ok(claims)
}
}

Loading

0 comments on commit f2e9626

Please sign in to comment.