rust/pv_core: Attestation support

Add functionality for:

* sending attestation requests to the uvdevice and retrieve the
  response
* create/read the attestation exchange format file format

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-02-23 16:31:55 +01:00
parent e152b554f5
commit b7765993e2
6 changed files with 356 additions and 12 deletions

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
/// Result type for this crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -49,6 +49,18 @@ pub enum Error {
#[error("Input contains unsupported user-data type: {0:#06x}")]
UnsupportedUserData(u16),
#[error("The input has not the correct format: {field} is too large. Maximal size {max_size}")]
AttDataSizeLarge { field: &'static str, max_size: u32 },
#[error("The input has not the correct format: {field} is too small. Minimal size {min_size}")]
AttDataSizeSmall { field: &'static str, min_size: u32 },
#[error("The attestation request has an unknown algorithm type (.0)")]
BinArcbInvAlgorithm(u32),
#[error("The attestation request does not specify a measurement size or measurement data.")]
BinArcbNoMeasurement,
// errors from other crates
#[error(transparent)]
Io(#[from] std::io::Error),

View File

@@ -6,11 +6,17 @@
mod error;
mod macros;
mod utils;
mod uvattest;
mod uvdevice;
mod uvsecret;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
/// Functionalities for reading attestation requests
pub mod attest {
pub use crate::uvattest::{AttestationMagic, AttestationMeasAlg};
}
/// Miscellaneous functions and definitions
pub mod misc {
pub use crate::utils::pv_guest_bit_set;
@@ -25,6 +31,7 @@ pub mod misc {
/// For detailed Information on how to send Ultravisor Commands see [`crate::uv::UvDevice`] and
/// [`crate::uv::UvCmd`]
pub mod uv {
pub use crate::uvdevice::attest::AttestationCmd;
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess};

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use crate::{request::MagicValue, Error};
use byteorder::{BigEndian, ByteOrder};
use zerocopy::U32;
/// The magic value used to identify an attestation request
///
/// The magic value is ASCII:
/// ```rust
/// # use pv_core::attest::AttestationMagic;
/// # use pv_core::request::MagicValue;
/// # fn main() {
/// # let magic = &
/// [0u8; 8]
/// # ;
/// # assert!(AttestationMagic::starts_with_magic(magic));
/// # }
/// ```
#[derive(Debug)]
pub struct AttestationMagic;
impl MagicValue<8> for AttestationMagic {
const MAGIC: [u8; 8] = [0; 8];
}
/// Identifier for the used measurement algorithm
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttestationMeasAlg {
/// Use HMAC with SHA512 as measurement algorithm
HmacSha512 = 1,
}
impl AttestationMeasAlg {
/// Report the expected size for a given measurement algorithm
pub const fn exp_size(&self) -> u32 {
match self {
AttestationMeasAlg::HmacSha512 => 64,
}
}
}
impl<E: ByteOrder> TryFrom<U32<E>> for AttestationMeasAlg {
type Error = Error;
fn try_from(value: U32<E>) -> Result<Self, Self::Error> {
if value.get() == AttestationMeasAlg::HmacSha512 as u32 {
Ok(Self::HmacSha512)
} else {
Err(Error::BinArcbInvAlgorithm(value.get()))
}
}
}
impl From<AttestationMeasAlg> for U32<BigEndian> {
fn from(value: AttestationMeasAlg) -> Self {
(value as u32).into()
}
}

View File

@@ -24,11 +24,13 @@ mod ffi;
mod info;
mod test;
pub(crate) use ffi::uv_ioctl;
pub mod attest;
pub mod secret;
pub mod secret_list;
pub use info::UvDeviceInfo;
#[allow(dead_code)] //TODO rm when pv learns attestation
/// User data for the attestation UVC
pub type AttestationUserData = [u8; ffi::UVIO_ATT_USER_DATA_LEN];
///Configuration Unique Id of the Secure Execution guest

View File

@@ -0,0 +1,269 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use super::{ffi, AttestationUserData, ConfigUid, UvCmd};
use crate::{Error, Result};
use std::ptr;
use zerocopy::{AsBytes, FromZeroes};
/// _Retrieve Attestation Measurement_ UVC
///
/// The Attestation Request has two input and three outputs.
/// ARCB and User Data are inputs for the UV.
/// Measurement, Additional Data, and the Configuration Unique ID are outputs generated by UV.
///
/// The Attestation Request Control Block (ARCB) is a cryptographically verified
/// and secured request to UV and User Data is some plaintext data which is
/// going to be included in the Attestation Measurement calculation.
///
/// Measurement is a cryptographic measurement of the callers properties,
/// optional data configured by the ARCB and the user data. If specified by the
/// ARCB, UV will add some Additional Data to the measurement calculation.
/// This Additional Data is then returned as well.
///
/// If the Retrieve Attestation Measurement UV facility is not present,
/// UV will return invalid command rc.
///
/// # Example
///
/// ```rust,no_run
/// # use pv_core::uv::UvDevice;
/// # use pv_core::uv::AttestationCmd;
/// # fn main() -> pv_core::Result<()> {
/// let arcb = std::fs::read("arcb")?.into();
/// let user_data = vec![0, 1, 2, 3];
/// // Hard-coded example
/// let mut cmd = AttestationCmd::new_request(arcb, Some(user_data), 64, 0)?;
/// let uv = UvDevice::open()?;
/// # uv.send_cmd(&mut cmd)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct AttestationCmd {
// all sizes are guaranteed to fit in the exchange format/UV-Call at any time
// attestation data, these must not changed by this tooling, this is an invariant of this
// struct, so that the raw pointer stay valid through the lifetime of this struct.
// no mutable references are ever passed from this struct
arcb: Box<[u8]>,
measurement: Vec<u8>,
additional: Option<Vec<u8>>,
// raw IOCTL struct
uvio_attest: ffi::uvio_attest,
}
impl AttestationCmd {
/// Maximum size for Additional-data
pub const ADDITIONAL_MAX_SIZE: u32 = ffi::UVIO_ATT_ADDITIONAL_MAX_LEN as u32;
/// Maximum size for Attestation Request Control Block
pub const ARCB_MAX_SIZE: u32 = ffi::UVIO_ATT_ARCB_MAX_LEN as u32;
/// Maximum size for Configuration UID
pub const CUID_SIZE: u32 = ffi::UVIO_ATT_UID_LEN as u32;
/// Maximum size for Measurement-data
pub const MEASUREMENT_MAX_SIZE: u32 = ffi::UVIO_ATT_MEASUREMENT_MAX_LEN as u32;
/// Maximum size for user-data
pub const USER_MAX_SIZE: u32 = ffi::UVIO_ATT_USER_DATA_LEN as u32;
fn verify_size(size: u32, min_size: u32, max_size: u32, field: &'static str) -> Result<()> {
if size < min_size {
return Err(Error::AttDataSizeSmall { field, min_size });
}
if size > max_size {
return Err(Error::AttDataSizeLarge { field, max_size });
}
Ok(())
}
fn verify_slice(val: &[u8], max_size: u32, field: &'static str) -> Result<()> {
if val.len() > max_size as usize {
Err(Error::AttDataSizeLarge { field, max_size })
} else {
Ok(())
}
}
/// Creates a new [`AttestationCmd`]
///
/// * `arcb` - binary attestation request
/// * `user_data` - optional, up to 256 bytes of arbitrary data
/// * `exp_measurement` - expected size the Attestation measurement requires
/// * `exp_additional` - expected size of the additional data.
///
/// Creates a new Retrieve Attestation Measurement UVC.
pub fn new_request(
arcb: Box<[u8]>,
user_data: Option<Vec<u8>>,
exp_measurement: u32,
exp_additional: u32,
) -> Result<Self> {
Self::verify_size(
exp_measurement,
1,
AttestationCmd::MEASUREMENT_MAX_SIZE,
"Expected measurement size",
)?;
Self::verify_size(
exp_additional,
0,
AttestationCmd::ADDITIONAL_MAX_SIZE,
"Expected additional data size",
)?;
Self::verify_slice(&arcb, AttestationCmd::ARCB_MAX_SIZE, "Attestation request")?;
if let Some(ref data) = user_data {
Self::verify_slice(data, AttestationCmd::USER_MAX_SIZE, "User data")?;
}
let (user_len, user_data) = match user_data {
// enforced by tests before invariants
Some(user) => (Some(user.len() as u16), {
let mut user_data = AttestationUserData::new_zeroed();
user_data[0..user.len()].clone_from_slice(&user);
Some(user_data)
}),
None => (None, None),
};
let mut additional = match exp_additional {
0 => None,
size => Some(vec![0u8; size as usize]),
};
let mut measurement = vec![0u8; exp_measurement as usize];
let uvio_attest = unsafe {
ffi::uvio_attest::new(
&arcb,
&mut measurement,
additional.as_deref_mut(),
user_data,
user_len,
)
};
Ok(Self {
arcb,
measurement,
additional,
uvio_attest,
})
}
/// Provides the additional data calculated by UV after a successful UVC
///
/// Truncates the additional data to the correct length in place.
/// If called before a successful attestation the data in this buffer is undefined.
pub fn additional(&mut self) -> Option<&[u8]> {
// truncate the add size to the UV reported size
match &mut self.additional {
Some(ref mut a) => a.truncate(self.uvio_attest.add_data_len as usize),
None => (),
}
self.additional.as_deref()
}
/// Copies the additional data calculated by UV after a successful UVC into a Vec
///
/// Truncates the additional data to the correct length.
/// If called before a successful attestation the data in this buffer is undefined.
pub fn additional_owned(&self) -> Option<Vec<u8>> {
let mut additional = self.additional.clone()?;
additional.truncate(self.uvio_attest.add_data_len as usize);
Some(additional)
}
/// Provides the Configuration Unique Identifier received from UV after a successful UVC
///
/// If called before a successful attestation the data in this buffer is undefined.
pub fn cuid(&self) -> &ConfigUid {
&self.uvio_attest.config_uid
}
/// Provides the attestation measurement calculated by UV after a successful UVC
///
/// If called before a successful attestation the data in this buffer is undefined.
pub fn measurement(&self) -> &[u8] {
&self.measurement
}
/// Returns a reference to the request of this [`AttestationCmd`].
pub fn arcb(&self) -> &[u8] {
self.arcb.as_ref()
}
}
impl UvCmd for AttestationCmd {
const UV_IOCTL_NR: u8 = ffi::UVIO_IOCTL_ATT_NR;
fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> {
match rc {
// should not happen, uvdevice local value
0x0101 => Some("Invalid continuation token specified"),
0x010a => Some("Unsupported plaintext attestation flag set"),
0x010b => Some("Unsupported measurement algorithm specified."),
0x010c => Some("Unable to decrypt attestation request control block. Probably no valid host-key was provided"),
0x0106 => Some("Unsupported attestation request version"),
// should not happen, protected by AttestationCmd constructors
0x0102 => Some("User data length is greater than 256"),
// should not happen, uvdevice ensures this
0x0103 => Some("Access exception recognized when accessing the attestation request control block"),
// should not happen, uvdevice ensures this
0x0104 => Some("Access exception recognized when accessing the measurement data area"),
// should not happen, uvdevice ensures this
0x0105 => Some("Access exception recognized when accessing the additional data area"),
// should not happen, ensured by Attestation Request builder
0x0107 => Some("Invalid attestation request length for the specified attestation request version"),
// 0 case should not happen, ensured by Attestation Request builder
0x0108 => Some("Number of key slots is either equal to 0 or greater than the maximum number supported by the specified attestation request version"),
// should not happen, ensured by Attestation Request builder
0x0109 => Some("Size of encrypted area does not match measurement length plus any optional items"),
// should not happen, ensured by Attestation Request builder
0x010d => Some("Measurement data length is not large enough to store measurement"),
// should not happen, ensured by Attestation Request builder
0x010e => Some("Additional data length not large enough to hold all requested additional data"),
_ => None,
}
}
fn data(&mut self) -> Option<&mut [u8]> {
Some(self.uvio_attest.as_bytes_mut())
}
}
fn opt_to_mut_ptr_u64(opt: &mut Option<&mut [u8]>) -> u64 {
(match opt {
Some(v) => v.as_mut_ptr(),
None => ptr::null_mut(),
}) as u64
}
impl ffi::uvio_attest {
/// Create a new attestation IOCTL control block
///
/// Happily converts slice lengths into u32/u16 without verifying.
/// Therefore marked as unsafe.
///
/// # SAFETY
/// It is safe to call this function iff:
/// - arcb.len() < u32::MAX
/// - additional.len() < u32::MAX
/// - user_len() <= 256
/// - pointyer fit into an u64
unsafe fn new(
arcb: &[u8],
measurement: &mut [u8],
mut additional: Option<&mut [u8]>,
user: Option<AttestationUserData>,
user_len: Option<u16>,
) -> Self {
Self {
arcb_addr: arcb.as_ptr() as u64,
meas_addr: measurement.as_ptr() as u64,
add_data_addr: opt_to_mut_ptr_u64(&mut additional),
user_data: user.unwrap_or([0; 256]),
config_uid: [0; 16],
arcb_len: arcb.len() as u32,
meas_len: measurement.len() as u32,
add_data_len: additional.unwrap_or_default().len() as u32,
user_data_len: user_len.unwrap_or_default(),
reserved136: 0,
}
}
}

View File

@@ -74,13 +74,13 @@ pub const UVIO_ATT_UID_LEN: usize = 0x10;
/// Measurement and Additional Data are outputs generated by UV.
///
/// The Attestation Request Control Block (ARCB) is a cryptographically verified
/// and secured request to UV and User Data is some plaintext data which is
/// and secured request to UV and user-data is some plaintext data which is
/// going to be included in the Attestation Measurement calculation.
///
/// Measurement is a cryptographic measurement of the callers properties,
/// optional data configured by the ARCB and the user data. If specified by the
/// ARCB, UV will add some Additional Data to the measurement calculation.
/// This Additional Data is then returned as well.
/// ARCB, UV will add some additional-data to the measurement calculation.
/// This additional-data is then returned as well.
///
/// If the Retrieve Attestation Measurement UV facility is not present,
/// UV will return invalid command rc.
@@ -101,13 +101,6 @@ pub struct uvio_attest {
}
assert_size!(uvio_attest, 0x138);
#[allow(dead_code)] //TODO rm when pv learns attestation
impl uvio_attest {
pub const ARCB_MAX_LEN: usize = UVIO_ATT_ARCB_MAX_LEN;
pub const MEASUREMENT_MAX_LEN: usize = UVIO_ATT_MEASUREMENT_MAX_LEN;
pub const ADDITIONAL_MAX_LEN: usize = UVIO_ATT_ADDITIONAL_MAX_LEN;
}
/// corresponds to the UV_IOCTL macro
pub(crate) const fn uv_ioctl(nr: u8) -> u64 {
iowr(UVIO_TYPE_UVC, nr, std::mem::size_of::<uvio_ioctl_cb>())