From b7765993e22624e865bf118fa06bdb873de676f2 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Fri, 23 Feb 2024 16:31:55 +0100 Subject: [PATCH] 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 Signed-off-by: Steffen Eiden --- rust/pv_core/src/error.rs | 14 +- rust/pv_core/src/lib.rs | 7 + rust/pv_core/src/uvattest.rs | 61 +++++++ rust/pv_core/src/uvdevice.rs | 4 +- rust/pv_core/src/uvdevice/attest.rs | 269 ++++++++++++++++++++++++++++ rust/pv_core/src/uvdevice/ffi.rs | 13 +- 6 files changed, 356 insertions(+), 12 deletions(-) create mode 100644 rust/pv_core/src/uvattest.rs create mode 100644 rust/pv_core/src/uvdevice/attest.rs diff --git a/rust/pv_core/src/error.rs b/rust/pv_core/src/error.rs index aefa241a..1a4c5b22 100644 --- a/rust/pv_core/src/error.rs +++ b/rust/pv_core/src/error.rs @@ -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 = std::result::Result; @@ -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), diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs index 1310f54e..4e2ac060 100644 --- a/rust/pv_core/src/lib.rs +++ b/rust/pv_core/src/lib.rs @@ -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}; diff --git a/rust/pv_core/src/uvattest.rs b/rust/pv_core/src/uvattest.rs new file mode 100644 index 00000000..f48bdf33 --- /dev/null +++ b/rust/pv_core/src/uvattest.rs @@ -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 TryFrom> for AttestationMeasAlg { + type Error = Error; + + fn try_from(value: U32) -> Result { + if value.get() == AttestationMeasAlg::HmacSha512 as u32 { + Ok(Self::HmacSha512) + } else { + Err(Error::BinArcbInvAlgorithm(value.get())) + } + } +} + +impl From for U32 { + fn from(value: AttestationMeasAlg) -> Self { + (value as u32).into() + } +} diff --git a/rust/pv_core/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs index 783872ad..5d3ecf55 100644 --- a/rust/pv_core/src/uvdevice.rs +++ b/rust/pv_core/src/uvdevice.rs @@ -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 diff --git a/rust/pv_core/src/uvdevice/attest.rs b/rust/pv_core/src/uvdevice/attest.rs new file mode 100644 index 00000000..697a8bca --- /dev/null +++ b/rust/pv_core/src/uvdevice/attest.rs @@ -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, + additional: Option>, + // 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>, + exp_measurement: u32, + exp_additional: u32, + ) -> Result { + 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> { + 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, + user_len: Option, + ) -> 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, + } + } +} diff --git a/rust/pv_core/src/uvdevice/ffi.rs b/rust/pv_core/src/uvdevice/ffi.rs index 81219e51..8b78c906 100644 --- a/rust/pv_core/src/uvdevice/ffi.rs +++ b/rust/pv_core/src/uvdevice/ffi.rs @@ -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::())