rust: pvattest-Rust

Add a CLI compatible Rust implementation of pvattest-C.
 - All (non-experimental) options are supported and work exactly as in
   the C implementation. For some options/parameters new variants are
   available.
 - `perform` now also accepts positional arguments, while keep accepting
   -i and -o  that was mandatory in the C implementation.
 - `version` may also be a command instead of an option now.
 - -V is deprecated
 - -v increases verbosity instead of showing the version
 - all experimental options are dropped

Acked-by: Qi Feng Huo <huoqif@cn.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Steffen Eiden
2024-04-05 13:28:04 +02:00
parent 4d57ff046d
commit 16610a211f
24 changed files with 2208 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use crate::{
cli::{AttAddFlags, CreateAttOpt},
exchange::{ExchangeFormatRequest, ExchangeFormatVersion},
};
use anyhow::{bail, Context, Result};
use log::{debug, warn};
use pv::{
attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion},
misc::{create_file, write_file},
request::{ReqEncrCtx, Request, SymKey, SymKeyType},
};
use std::process::ExitCode;
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
let mut att_flags = AttestationFlags::default();
for flag in cli_flags {
match flag {
AttAddFlags::PhkhImg => att_flags.set_image_phkh(),
AttAddFlags::PhkhAtt => att_flags.set_attest_phkh(),
}
}
att_flags
}
pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
let att_version = AttestationVersion::One;
let meas_alg = AttestationMeasAlg::HmacSha512;
let mut arcb = AttestationRequest::new(att_version, meas_alg, flags(&opt.add_data))?;
debug!("Generated Attestation request");
// Add host-key documents
opt.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 mut output = create_file(&opt.output)?;
let exch_ctx = ExchangeFormatRequest::new(
ser_arcb,
meas_alg.exp_size(),
arcb.flags().expected_additional_size(),
)?;
exch_ctx.write(&mut output, ExchangeFormatVersion::One)?;
let arpk = match encr_ctx.prot_key() {
SymKey::Aes256(k) => k,
_ => bail!("Unexpected key type"),
};
write_file(
&opt.arpk,
arpk.value(),
"Attestation request Protection Key",
)?;
Ok(ExitCode::SUCCESS)
}
+54
View File
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use crate::{
cli::PerformAttOptComb,
exchange::{ExchangeFormatRequest, ExchangeFormatResponse, ExchangeFormatVersion},
};
use anyhow::Result;
use pv::{
misc::{create_file, open_file, read_file},
uv::{AttestationCmd, UvDevice},
};
use std::process::ExitCode;
pub fn perform<'a, P>(opt: P) -> Result<ExitCode>
where
P: Into<PerformAttOptComb<'a>>,
{
let opt = opt.into();
let mut input = open_file(opt.input)?;
let mut output = create_file(opt.output)?;
let uvdevice = UvDevice::open()?;
let ex_in = ExchangeFormatRequest::read(&mut input)?;
let user_data = opt
.user_data
.map(|u| read_file(u, "user-data"))
.transpose()?;
let mut cmd = AttestationCmd::new_request(
ex_in.arcb.clone().into(),
user_data.clone(),
ex_in.exp_measurement,
ex_in.exp_additional,
)?;
uvdevice.send_cmd(&mut cmd)?;
let measurement = cmd.measurement();
let additional = cmd.additional_owned();
let cuid = cmd.cuid();
let ex_out = ExchangeFormatResponse::new(
ex_in.arcb,
measurement.to_owned(),
additional,
user_data,
cuid.to_owned(),
)?;
ex_out.write(&mut output, ExchangeFormatVersion::One)?;
Ok(ExitCode::SUCCESS)
}
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use anyhow::Result;
use log::{debug, warn};
use pv::{
attest::{
AdditionalData, AttestationFlags, AttestationItems, AttestationMeasurement,
AttestationRequest,
},
misc::{create_file, open_file, read_exact_file, write_file},
request::{openssl::pkey::PKey, BootHdrTags, Confidential, SymKey},
};
use serde::Serialize;
use std::{fmt::Display, process::ExitCode};
use utils::HexSlice;
use crate::{
cli::{VerifyOpt, VerifyOutputType},
exchange::ExchangeFormatResponse,
EXIT_CODE_ATTESTATION_FAIL,
};
#[derive(Serialize)]
struct VerifyOutput<'a> {
cuid: HexSlice<'a>,
#[serde(skip_serializing_if = "Option::is_none")]
add: Option<HexSlice<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
add_fields: Option<AdditionalData<HexSlice<'a>>>,
#[serde(skip_serializing_if = "Option::is_none")]
user_data: Option<HexSlice<'a>>,
}
impl<'a> VerifyOutput<'a> {
fn from_exchange(resp: &'a ExchangeFormatResponse, flags: &AttestationFlags) -> Result<Self> {
let additional_data_fields = resp
.additional()
.map(|a| AdditionalData::from_slice(a, flags))
.transpose()?;
let user_data = resp.user().map(|u| u.into());
Ok(Self {
cuid: resp.config_uid().into(),
add: resp.additional().map(|a| a.into()),
add_fields: additional_data_fields.map(AdditionalData::from_other),
user_data,
})
}
}
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.add {
writeln!(f, "Additional-data:")?;
writeln!(f, "{:#}", data)?;
}
if let Some(data) = &self.add_fields {
writeln!(f, "Additional-data content:")?;
writeln!(f, "{:#}", data)?;
}
if let Some(data) = &self.user_data {
writeln!(f, "user-data:")?;
writeln!(f, "{:#}", data)?;
}
Ok(())
}
}
pub fn verify(opt: &VerifyOpt) -> Result<ExitCode> {
let mut input = open_file(&opt.input)?;
let mut img = open_file(&opt.hdr)?;
let output = opt.output.as_ref().map(create_file).transpose()?;
let arpk = SymKey::Aes256(
read_exact_file(&opt.arpk, "Attestation request protection key").map(Confidential::new)?,
);
let tags = BootHdrTags::from_se_image(&mut img)?;
let exchange = ExchangeFormatResponse::read(&mut input)?;
let (auth, conf) = AttestationRequest::decrypt_bin(exchange.arcb(), &arpk)?;
let meas_key = PKey::hmac(conf.measurement_key())?;
let items = AttestationItems::new(
&tags,
exchange.config_uid(),
exchange.user(),
conf.nonce().as_ref().map(|v| v.value()),
exchange.additional(),
);
let measurement = AttestationMeasurement::calculate(items, auth.mai(), &meas_key)?;
let uv_meas = exchange.measurement();
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(&exchange, auth.flags())?;
warn!("{pr_data}");
if let Some(mut output) = output {
match opt.format {
VerifyOutputType::Yaml => serde_yaml::to_writer(&mut output, &pr_data)?,
};
}
if let Some(user_data) = &opt.user_data {
match exchange.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)
}