diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3a6c4238..76fdffb4 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -774,10 +774,9 @@ dependencies = [ [[package]] name = "pv" -version = "0.9.0" +version = "0.9.1" dependencies = [ "byteorder", - "cfg-if", "clap", "curl", "lazy_static", @@ -786,9 +785,27 @@ dependencies = [ "mockito", "openssl", "openssl_extensions", + "pv_core", "serde", "serde_test", "thiserror", + "utils", + "zerocopy", +] + +[[package]] +name = "pv_core" +version = "0.9.1" +dependencies = [ + "byteorder", + "lazy_static", + "libc", + "log", + "mockito", + "serde", + "serde_test", + "thiserror", + "utils", "zerocopy", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index f358dbd9..22b3d448 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "pv", + "pv_core", "pvapconfig", "pvsecret", "utils", diff --git a/rust/README.md b/rust/README.md index 16603bf5..96128424 100644 --- a/rust/README.md +++ b/rust/README.md @@ -25,16 +25,18 @@ Tip: You can use `make version` to get the version string. ## Internal Libraries * __utils__ _Library for rust tools that bundles common stuff for the 390-tools_ - * currently only provides a macro to get the `S390_TOOLS_RELEASE` string + * provides a macro to get the `S390_TOOLS_RELEASE` string + * provides macros for compile time assertions + +* __pv_core__ _Library for pv tools, providing uvdevice access and utilities to send, receive and interpret various UV-calls._ * __pv__ _Library for pv tools, providing uvdevice access, encryption utilities, and utilities for generating UV-request_ - * requires openssl and libcurl for the feature `request`; use `HAVE_=0` to - disable build that use pv with the request feature. - + * requires openssl and libcurl + * reexports ann symbols from __pv_core__ + * if no encryption utilities required, use __pv_core__ ## Tools * __pvsecret__ _Manage secrets for IBM Secure Execution guests_ - * requires pv with the `request` feature ## Writing new tools We encourage to use Rust for new tools. However, for some use cases it makes diff --git a/rust/pv/Cargo.toml b/rust/pv/Cargo.toml index ff845263..df31e681 100644 --- a/rust/pv/Cargo.toml +++ b/rust/pv/Cargo.toml @@ -1,32 +1,25 @@ [package] name = "pv" -version = "0.9.0" +version = "0.9.1" edition.workspace = true license.workspace = true [dependencies] +byteorder = "1.3" +clap = { version ="4", features = ["derive", "wrap_help"] } +curl = "0.4.7" libc = "0.2.49" log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } +openssl = "0.10.49" +serde = { version = "1.0.139", features = ["derive"] } thiserror = "1.0.33" +utils = {path = "../utils"} zerocopy = "0.6" -cfg-if = "1.0.0" -# dependencies for request feature -clap = { version ="4", features = ["derive", "wrap_help"], optional = true } -curl = { version ="0.4.7", optional = true } -openssl = {version = "0.10.49", optional = true } -openssl_extensions = { path = "openssl_extensions", optional = true } -serde = { version = "1.0.139", features = ["derive"], optional = true } - -# misc optional dependencies -byteorder = {version = "1.3", optional = true } +openssl_extensions = { path = "openssl_extensions" } +pv_core = { path = "../pv_core" } [dev-dependencies] mockito = {version = "1", default-features = false } serde_test = "1" lazy_static = "1.1" - -[features] -default = [] -request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"] -uvsecret = ["dep:byteorder", "dep:serde"] diff --git a/rust/pv/src/brcb.rs b/rust/pv/src/brcb.rs index 9ef58e2d..8f4bba98 100644 --- a/rust/pv/src/brcb.rs +++ b/rust/pv/src/brcb.rs @@ -8,10 +8,9 @@ use std::{ }; // (SE) boot request control block aka SE header -use crate::{ - assert_size, request::MagicValue, requires_feat, static_assert, Error, Result, PAGESIZE, -}; +use crate::{assert_size, static_assert, Error, Result, PAGESIZE}; use log::debug; +use pv_core::request::MagicValue; use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64}; /// Struct containing all SE-header tags. @@ -22,7 +21,6 @@ use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64}; /// Tweak List Digest (tld) /// SE Header Tag (seht) /// -#[doc = requires_feat!(request)] #[repr(C)] #[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)] pub struct BootHdrTags { diff --git a/rust/pv/src/cli.rs b/rust/pv/src/cli.rs index 6c673338..daed1c11 100644 --- a/rust/pv/src/cli.rs +++ b/rust/pv/src/cli.rs @@ -2,13 +2,12 @@ // // Copyright IBM Corp. 2023 +use crate::misc::{create_file, open_file}; use crate::Result; use clap::{ArgGroup, Args, ValueHint}; use std::io::{Read, Write}; /// CLI Argument collection for handling certificates. -/// -#[doc = requires_feat!(request)] #[derive(Args, Debug, PartialEq, Eq, Default)] #[command( group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])), @@ -103,37 +102,25 @@ impl CertificateOptions { } /// stdout -#[cfg(feature = "request")] pub const STDOUT: &str = "-"; /// stdin -#[cfg(feature = "request")] pub const STDIN: &str = "-"; /// Converts an argument value into a Writer. -/// -/// # Errors -/// No Error will occur but function must match a signature -/// -#[cfg(feature = "request")] pub fn get_writer_from_cli_file_arg(path: &str) -> Result> { if path == STDOUT { Ok(Box::new(std::io::stdout())) } else { - Ok(Box::new(crate::misc::create_file(path)?)) + Ok(Box::new(create_file(path)?)) } } /// Converts an argument value into a Reader. -/// -/// # Errors -/// No Error will occur but function must match a signature -/// -#[cfg(feature = "request")] pub fn get_reader_from_cli_file_arg(path: &str) -> Result> { if path == STDIN { Ok(Box::new(std::io::stdin())) } else { - Ok(Box::new(crate::misc::open_file(path)?)) + Ok(Box::new(open_file(path)?)) } } diff --git a/rust/pv/src/crypto.rs b/rust/pv/src/crypto.rs index 8132e9de..fa4bfc23 100644 --- a/rust/pv/src/crypto.rs +++ b/rust/pv/src/crypto.rs @@ -2,7 +2,6 @@ // // Copyright IBM Corp. 2023 -use crate::requires_feat; use crate::{error::Result, secret::Secret, Error}; use openssl::rand::rand_bytes; use openssl::{ @@ -19,12 +18,10 @@ use std::convert::TryInto; /// An AES256-key that will purge itself out of the memory when going out of scope /// -#[doc = requires_feat!(request)] pub type Aes256Key = Secret<[u8; 32]>; /// Types of symmetric keys, to specify during construction. /// -#[doc = requires_feat!(request)] #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SymKeyType { @@ -34,7 +31,6 @@ pub enum SymKeyType { /// Types of symmetric keys /// -#[doc = requires_feat!(request)] #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] pub enum SymKey { @@ -88,7 +84,6 @@ impl From for SymKey { /// Performs an hkdf according to RFC 5869. /// See [`OpenSSL HKDF`]() /// -#[doc = requires_feat!(request)] /// # Errors /// /// This function will return an OpenSSL error if the key could not be generated. @@ -113,7 +108,6 @@ pub fn hkdf_rfc_5869( /// Derive a symmetric key from a private and a public key. /// -#[doc = requires_feat!(request)] /// # Errors /// /// This function will return an error if something went bad in OpenSSL. @@ -132,7 +126,6 @@ pub fn derive_key(k1: &PKey, k2: &PKey) -> Result { /// Generate a random array. /// -#[doc = requires_feat!(request)] /// # Errors /// /// This function will return an error if the entropy source fails or is not available. @@ -144,7 +137,6 @@ pub fn random_array() -> Result<[u8; COUNT]> { /// Generate a new random EC-SECP521R1 key. /// -#[doc = requires_feat!(request)] /// # Errors /// /// This function will return an error if the key could not be generated by OpenSSL. @@ -160,7 +152,6 @@ pub fn gen_ec_key() -> Result> { /// * `iv` - initialisation vector /// * `conf` - data to be encrypted /// -#[doc = requires_feat!(request)] /// # Errors /// /// This function will return an error if the data could not be encrypted by OpenSSL. @@ -179,7 +170,6 @@ pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result> { /// * `aad` - additional authentic data /// * `conf` - data to be encrypted /// -#[doc = requires_feat!(request)] /// # Returns /// [`Vec`] with the following content: /// 1. `aad` @@ -212,7 +202,6 @@ pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Resu /// Calculate the hash of a slice. /// -#[doc = requires_feat!(request)] /// # Errors /// /// This function will return an error if OpenSSL could not compute the hash. diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs index 97043b1b..f2bd963f 100644 --- a/rust/pv/src/error.rs +++ b/rust/pv/src/error.rs @@ -10,49 +10,19 @@ pub type Result = std::result::Result; #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { - #[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))] - #[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))] - Uv { - rc: u16, - rrc: u16, - msg: &'static str, - }, - #[error("Invalid SE header provided")] - #[cfg(feature = "request")] InvBootHdr, - #[error("{0}")] - Specification(String), - - #[error("Cannot {ty} {ctx} at `{path}`")] - FileIo { - ty: FileIoErrorType, - ctx: String, - path: String, - source: std::io::Error, - }, - #[error("Cannot {ty} `{path}`")] - FileAccess { - ty: FileAccessErrorType, - path: String, - source: std::io::Error, - }, - #[error("Host-key verification failed: {0}")] - #[cfg(feature = "request")] HkdVerify(HkdVerifyErrorType), #[error("No host-key provided")] - #[cfg(feature = "request")] NoHostkey, #[error("To many host-keys provided")] - #[cfg(feature = "request")] ManyHostkeys, #[error("Cannot load {ty} from {path}")] - #[cfg(feature = "request")] X509Load { path: String, ty: &'static str, @@ -60,38 +30,16 @@ pub enum Error { }, #[error("Internal (unexpected) error: {0}, caused by {1}")] - #[cfg(feature = "request")] InternalSsl(&'static str, #[source] openssl::error::ErrorStack), - #[error("No Config UID found: {0}")] - NoCuid(String), - // errors from request types - #[cfg(feature = "uvsecret")] - #[error("Customer Communication Key must be 32 bytes long")] - CckSize, - - #[cfg(feature = "uvsecret")] - #[error("Cannot encode secrets (Too many secrets)")] - ManySecrets, - - #[cfg(feature = "uvsecret")] - #[error("Cannot decode secret list")] - InvSecretList(#[source] std::io::Error), - - #[cfg(feature = "uvsecret")] - #[error("Input does not contain an add-secret request")] - NoAsrcb, - // errors from other crates #[error(transparent)] + PvCore(#[from] pv_core::Error), + #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] - #[cfg(feature = "request")] Crypto(#[from] openssl::error::ErrorStack), #[error(transparent)] - ParseInt(#[from] std::num::ParseIntError), - #[cfg(feature = "request")] - #[error(transparent)] Curl(#[from] curl::Error), } @@ -102,35 +50,11 @@ impl Error { pub const CERT: &str = "certificate"; } -/// Error cases for I/O operations -#[allow(missing_docs)] -#[derive(thiserror::Error, Debug)] -#[non_exhaustive] -pub enum FileIoErrorType { - #[error("read")] - Read, - #[error("write")] - Write, -} - -/// Error cases for accessing files -#[allow(missing_docs)] -#[derive(thiserror::Error, Debug)] -#[non_exhaustive] -pub enum FileAccessErrorType { - #[error("open")] - Open, - #[error("create")] - Create, -} - /// Error cases for verifying host-key documents /// -#[doc = crate::requires_feat!(request)] #[allow(missing_docs)] #[derive(thiserror::Error, Debug, PartialEq, Eq)] #[non_exhaustive] -#[cfg(feature = "request")] pub enum HkdVerifyErrorType { #[error("Signature verification failed")] Signature, @@ -160,37 +84,9 @@ pub enum HkdVerifyErrorType { IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32), } -macro_rules! path_to_str { - ($path: expr) => { - $path.as_ref().to_str().unwrap_or("no UTF-8 path") - }; -} -pub(crate) use path_to_str; - -macro_rules! file_error { - ($ty: tt, $ctx: expr, $path:expr, $src: expr) => { - $crate::Error::FileIo { - ty: $crate::FileIoErrorType::$ty, - ctx: $ctx.to_string(), - path: $path.to_string(), - source: $src, - } - }; -} -pub(crate) use file_error; - -#[cfg(feature = "request")] macro_rules! bail_hkd_verify { ($var: tt) => { return Err($crate::Error::HkdVerify($crate::HkdVerifyErrorType::$var)) }; } -#[cfg(feature = "request")] pub(crate) use bail_hkd_verify; - -macro_rules! bail_spec { - ($str: expr) => { - return Err($crate::Error::Specification($str.to_string())) - }; -} -pub(crate) use bail_spec; diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs index 0fd2a861..78569712 100644 --- a/rust/pv/src/lib.rs +++ b/rust/pv/src/lib.rs @@ -2,7 +2,6 @@ // // Copyright IBM Corp. 2023 -#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)] #![deny(missing_docs)] //! pv - library for pv-tools //! @@ -11,156 +10,83 @@ //! `pv` provides abstraction layers for encryption, secure memory management, //! logging, and accessing the uvdevice. //! -//! ## Feature Flags -//! The following feature flags are available: -//! - `request` -//! - optional -//! - Enables generation of UV requests -//! - `uvsecret` -//! - optional -//! - Enables support for the UV Secret API. +//! If you do not need any OpenSSL features use `pv_core`. +//! This crate reexports all symbols from `pv_core` +mod brcb; +mod cli; +mod crypto; mod error; -mod log; +mod req; +mod secret; mod utils; -mod uvdevice; +mod uvsecret; +mod verify; -/// Internal macro to conveninetly document required features on items -// #[macro_export] +/// utility functions for writing TESTS!!! +//hide any test helpers on docs! #[doc(hidden)] -macro_rules! requires_feat { - (request) => { - " Requires the feature `request`" - }; - (uvsecret) => { - " Requires the feature `uvsecret`" - }; - (reqsecret) => { - "Requires the features `request` & `uvsecret`" - }; -} -#[allow(unused_imports)] -use requires_feat; - -//only some features need this #[allow(dead_code)] +pub mod test_utils; + +pub use ::utils::assert_size; +pub use ::utils::static_assert; + const PAGESIZE: usize = 0x1000; -cfg_if::cfg_if! { - if #[cfg(feature = "request")] { - mod brcb; - mod cli; - mod crypto; - mod req; - mod secret; - mod uvsecret; - mod verify; - - /// utility functions for writing TESTS!!! - #[allow(dead_code)] - //hide any test helpers on docs! - #[doc(hidden)] - pub mod test_utils; - - } -} /// Definitions and functions for interacting with the Ultravisor pub mod uv { - pub use crate::uvdevice::{ + pub use pv_core::uv::{ uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess, }; - #[cfg(feature = "uvsecret")] - pub use crate::uvsecret::{ - secret_list::{ListableSecretType, SecretEntry, SecretList}, - uvc::{AddCmd, ListCmd, LockCmd}, - }; + pub use pv_core::uv::{AddCmd, ListCmd, LockCmd}; + pub use pv_core::uv::{ListableSecretType, SecretEntry, SecretList}; } /// Miscellaneous functions and definitions pub mod misc { - - #[cfg(feature = "request")] pub use crate::cli::{ get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, CertificateOptions, STDIN, STDOUT, }; - pub use crate::log::PvLogger; - pub use crate::utils::{ - create_file, memeq, open_file, parse_hex, pv_guest_bit_set, read, read_exact_file, - read_file, to_u16, to_u32, try_parse_u128, try_parse_u64, write, write_file, Flags, - Lsb0Flags64, Msb0Flags64, - }; - #[cfg(feature = "request")] pub use crate::utils::{read_certs, read_crls}; + pub use pv_core::misc::*; + pub use pv_core::PvLogger; } -#[cfg(feature = "request")] pub use crate::error::HkdVerifyErrorType; -pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result}; +pub use error::{Error, Result}; /// Functionalities to build UV requests -#[doc = requires_feat!(request)] pub mod request { + pub use crate::brcb::{BootHdrMagic, BootHdrTags}; + pub use crate::crypto::derive_key; + pub use crate::crypto::random_array; + pub use crate::crypto::{encrypt_aes, encrypt_aes_gcm, gen_ec_key}; + pub use crate::crypto::{hash, hkdf_rfc_5869}; + pub use crate::crypto::{Aes256Key, SymKey, SymKeyType}; + pub use crate::req::{Aad, Encrypt, Keyslot, ReqEncrCtx, Request}; + pub use crate::secret::{Secret, Zeroize}; + pub use crate::verify::HkdVerifier; - cfg_if::cfg_if! { - if #[cfg(feature = "request")] { - pub use crate::brcb::{BootHdrTags, BootHdrMagic}; - pub use crate::crypto::{ - derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, hash, hkdf_rfc_5869, - random_array, Aes256Key, SymKey, SymKeyType, - }; - pub use crate::req::{Aad, Encrypt, Keyslot, ReqEncrCtx, Request}; - pub use crate::secret::{Secret, Zeroize}; - pub use crate::verify::HkdVerifier; - - /// Reexports some useful OpenSSL symbols - /// - #[doc = requires_feat!(request)] - pub mod openssl { - pub use openssl::error::ErrorStack; - pub use openssl::hash::MessageDigest; - pub use openssl::md::Md; - pub use openssl::pkey; - } - - } + /// Reexports some useful OpenSSL symbols + pub mod openssl { + pub use openssl::error::ErrorStack; + pub use openssl::hash::MessageDigest; + pub use openssl::md::Md; + pub use openssl::pkey; } - cfg_if::cfg_if! { - if #[cfg(feature = "uvsecret")] { - /// Functionalities for creating add-secret requests - pub mod uvsecret { - #[cfg(feature = "request")] - pub use crate::uvsecret::{ - asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion,}, - ext_secret::ExtSecret, - guest_secret::GuestSecret, - }; - pub use crate::uvsecret::AddSecretMagic; - pub use crate::uvsecret::UserDataType; - } - } - } - - /// Version number of the request in system-endian - pub type RequestVersion = u32; - /// Request magic value - /// - /// The first 8 byte of a request providing an identifier of the request type - /// for programs - pub type RequestMagic = [u8; 8]; - /// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified - /// (binary) data. - pub trait MagicValue { - /// Magic value as byte array - const MAGIC: [u8; N]; - /// Test whether the given slice starts with the magic value. - fn starts_with_magic(v: &[u8]) -> bool { - if v.len() < Self::MAGIC.len() { - return false; - } - crate::misc::memeq(&v[..Self::MAGIC.len()], &Self::MAGIC) - } + /// Functionalities for creating add-secret requests + pub mod uvsecret { + pub use crate::uvsecret::{ + asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion}, + ext_secret::ExtSecret, + guest_secret::GuestSecret, + }; + pub use pv_core::request::uvsecret::AddSecretMagic; + pub use pv_core::request::uvsecret::UserDataType; } + pub use pv_core::request::RequestMagic; } /// Provides cargo version Info about this crate. diff --git a/rust/pv/src/req.rs b/rust/pv/src/req.rs index b9acd732..f54b9a5c 100644 --- a/rust/pv/src/req.rs +++ b/rust/pv/src/req.rs @@ -4,8 +4,7 @@ use crate::misc::to_u32; use crate::request::{ - derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, random_array, RequestMagic, - RequestVersion, SymKey, SymKeyType, + derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, random_array, SymKey, SymKeyType, }; use crate::{Error, Result}; use openssl::bn::{BigNum, BigNumContext}; @@ -13,6 +12,7 @@ use openssl::ec::{EcGroupRef, EcPointRef}; use openssl::error::ErrorStack; use openssl::hash::{hash, MessageDigest}; use openssl::pkey::{PKey, PKeyRef, Private, Public}; +use pv_core::request::{RequestMagic, RequestVersion}; use std::convert::TryInto; use zerocopy::{AsBytes, BigEndian, FromBytes, U32}; @@ -215,10 +215,11 @@ impl ReqEncrCtx { } } - let rql = to_u32(auth_data.len() + encr_size + 16) - .ok_or_else(|| Error::Specification("Configured request size to large".to_string()))?; + let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| { + pv_core::Error::Specification("Configured request size to large".to_string()) + })?; let sea = to_u32(encr_size) - .ok_or_else(|| Error::Specification("Encrypted size to large".to_string()))?; + .ok_or_else(|| pv_core::Error::Specification("Encrypted size to large".to_string()))?; let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic); // copy request header to the start of the request diff --git a/rust/pv/src/utils.rs b/rust/pv/src/utils.rs index 84277c51..db948330 100644 --- a/rust/pv/src/utils.rs +++ b/rust/pv/src/utils.rs @@ -1,355 +1,9 @@ // SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2023 - -use crate::{ - error::{bail_spec, file_error, path_to_str, FileAccessErrorType}, - Error, FileIoErrorType, Result, -}; - -#[cfg(feature = "request")] +use crate::{Error, Result}; use openssl::x509::X509Crl; -#[cfg(feature = "request")] use openssl::x509::X509; -use std::{ - fs::File, - io::{Read, Write}, - path::Path, -}; -use zerocopy::{AsBytes, BigEndian, FromBytes, U64}; - -/// Asserts a constant expression evaluates to `true`. -/// -/// If the expression is not evaluated to `true` the compilation will fail. -#[macro_export] -macro_rules! static_assert { - ($condition:expr) => { - const _: () = core::assert!($condition); - }; -} - -/// Asserts that a type has a specific size. -/// -/// Useful to validate structs that are passed to C code. -/// If the expression is not evaluated to `true` the compilation will fail. -/// -/// # Example -/// ```rust -/// # use pv::assert_size; -/// # fn main() {} -/// #[repr(C)] -/// struct c_struct { -/// v: u64, -/// } -/// assert_size!(c_struct, 8); -/// // assert_size!(c_struct, 7);//won't compile -/// ``` -#[macro_export] -macro_rules! assert_size { - ($t:ty, $sz:expr ) => { - $crate::static_assert!(::std::mem::size_of::<$t>() == $sz); - }; -} - -/// Trait that describes bitflags, represented by `T`. -pub trait Flags: From + for<'a> From<&'a T> { - /// Set the specified bit to one. - /// # Panics - ///Panics if bit is >= 64 - fn set_bit(&mut self, bit: u8); - /// Set the specified bit to zero. - /// # Panics - ///Panics if bit is >= 64 - fn unset_bit(&mut self, bit: u8); - /// Test if the specified bit is set. - /// # Panics - ///Panics if bit is >= 64 - fn is_set(&self, bit: u8) -> bool; -} - -/// Bitflags in MSB0 ordering -/// -/// Wraps an u64 to set/get individual bits -#[repr(C)] -#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)] -pub struct Msb0Flags64(U64); -impl Flags for Msb0Flags64 { - #[track_caller] - fn set_bit(&mut self, bit: u8) { - assert!(bit < 64, "Flag bit set to greater than 63"); - let mut v = self.0.get(); - v |= 1 << (63 - bit); - self.0.set(v) - } - - #[track_caller] - fn unset_bit(&mut self, bit: u8) { - assert!(bit < 64, "Flag bit set to greater than 63"); - let mut v = self.0.get(); - v &= !(1 << (63 - bit)); - self.0.set(v) - } - - #[track_caller] - fn is_set(&self, bit: u8) -> bool { - assert!(bit < 64, "Flag bit set to greater than 63"); - self.0.get() & (1 << (63 - bit)) > 0 - } -} - -impl From for Msb0Flags64 { - fn from(value: u64) -> Self { - Self(value.into()) - } -} - -impl From<&u64> for Msb0Flags64 { - fn from(value: &u64) -> Self { - (*value).into() - } -} - -/// Bitflags in LSB0 ordering -/// -/// Wraps an u64 to set/get individual bits -#[repr(C)] -#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)] -pub struct Lsb0Flags64(U64); -impl Flags for Lsb0Flags64 { - #[track_caller] - fn set_bit(&mut self, bit: u8) { - assert!(bit < 64, "Flag bit set to greater than 63"); - let mut v = self.0.get(); - v |= 1 << bit; - self.0.set(v) - } - - #[track_caller] - fn unset_bit(&mut self, bit: u8) { - assert!(bit < 64, "Flag bit set to greater than 63"); - let mut v = self.0.get(); - v &= !(1 << bit); - self.0.set(v) - } - - #[track_caller] - fn is_set(&self, bit: u8) -> bool { - assert!(bit < 64, "Flag bit set to greater than 63"); - self.0.get() & (1 << bit) > 0 - } -} - -impl From for Lsb0Flags64 { - fn from(value: u64) -> Self { - Self(value.into()) - } -} - -impl From<&u64> for Lsb0Flags64 { - fn from(value: &u64) -> Self { - (*value).into() - } -} - -/// Tries to convert a BE hex string into a 128 unsigned integer -/// The hexstring must contain 32chars of hexdigits -/// -/// * `hex_str` - string to convert can be prepended with "0x" -/// * `ctx` - Error context string in case of an error -/// ```rust -/// # use std::error::Error; -/// # use pv::misc::try_parse_u128; -/// # fn main() -> Result<(), Box> { -/// let hex = "11223344556677889900aabbccddeeff"; -/// try_parse_u128(&hex, "The test")?; -/// # Ok(()) -/// # } -/// ``` -/// -/// # Errors -/// If `hex_string` is not a 32 byte hex string an Error appears -pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> { - let hex_str = if hex_str.starts_with("0x") { - hex_str.split_at(2).1 - } else { - hex_str - }; - if hex_str.len() != 32 { - bail_spec!(format!( - "{ctx} hexstring must be 32chars long to cover all 16 bytes" - )); - } - parse_hex(hex_str).try_into().map_err(|_| { - Error::Specification(format!( - "{ctx} hexstring must be 32chars long to cover all 16 bytes" - )) - }) -} - -/// Tries to convert a BE hex string into a 64 unsigned integer -/// The hexstring must *NOT* contain 16 chars of hexdigits, but -/// 16 chars at most. -/// -/// * `hex_str` - string to convert can be prepended with "0x" -/// * `ctx` - Error context string in case of an error -/// ```rust -/// # use std::error::Error; -/// # use pv::misc::try_parse_u64; -/// # fn main() -> Result<(), Box> { -/// let hex = "1234567890abcdef"; -/// try_parse_u64(&hex, "The test")?; -/// # Ok(()) -/// # } -/// ``` -/// -/// # Errors -/// If `hex_string` is not a 32 byte hex string an Error appears -pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result { - let hex_str = if hex_str.starts_with("0x") { - hex_str.split_at(2).1 - } else { - hex_str - }; - if hex_str.len() > 16 { - bail_spec!(format!( - "{ctx} hexstring {hex_str} must be max 16 chars long" - )); - } - Ok(u64::from_str_radix(hex_str, 16)?) -} - -/// Open a file. -/// -/// Wraps [`File::open`] -/// -/// * `path` - Path to file -pub fn open_file>(path: P) -> Result { - File::open(&path).map_err(|e| Error::FileAccess { - ty: FileAccessErrorType::Open, - path: path_to_str!(path).to_string(), - source: e, - }) -} - -/// Create a file. -/// -/// Wraps [`File::create`] -/// -/// * `path` - Path to file -pub fn create_file>(path: P) -> Result { - File::create(&path).map_err(|e| Error::FileAccess { - ty: FileAccessErrorType::Create, - path: path_to_str!(path).to_string(), - source: e, - }) -} - -/// Read exactly COUNT bytes into the buffer. -/// -/// * `path` - Path to file -/// * `ctx` - Error context string in case of an error -/// -/// # Errors -/// If this function encounters an "end of file" before completely filling -/// the buffer, it returns an error. The contents of `buf` are unspecified in this case. -/// -/// If any other read error is encountered then this function immediately -/// returns. The contents of `buf` are unspecified in this case. -/// -/// If this function returns an error, it is unspecified how many bytes it -/// has read, but it will never read more than would be necessary to -/// completely fill the buffer. -pub fn read_exact_file, const COUNT: usize>( - path: P, - ctx: &str, -) -> Result<[u8; COUNT]> { - let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess { - ty: crate::FileAccessErrorType::Open, - path: path_to_str!(path).to_string(), - source: e, - })?; - - if f.metadata()?.len() as usize != COUNT { - bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long")); - } - - let mut buf = [0; COUNT]; - f.read_exact(&mut buf) - .map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?; - Ok(buf) -} - -/// Read content from a file and add context in case of an error -/// -/// * `path` - Path to file -/// * `ctx` - Error context string in case of an error -/// -/// -/// # Errors -/// Passes through any kind of error `std::fs::read` produces -pub fn read_file>(path: P, ctx: &str) -> Result> { - std::fs::read(&path).map_err(|e| { - file_error!( - Read, - ctx, - path.as_ref().to_str().unwrap_or("no UTF-8 path"), - e - ) - }) -} - -/// Reads all content from a [`std::io::Read`] and add context in case of an error -/// -/// * `path` - Path to file -/// * `ctx` - Error context string in case of an error -/// -/// -/// # Errors -/// Passes through any kind of error `std::fs::write` produces -pub fn read(rd: &mut R, path: &str, ctx: &str) -> Result> { - let mut buf = vec![]; - rd.read_to_end(&mut buf).map_err(|e| Error::FileIo { - ty: FileIoErrorType::Write, - ctx: ctx.to_string(), - path: path.to_string(), - source: e, - })?; - Ok(buf) -} - -/// write content to a file and add context in case of an error -/// -/// * `path` - Path to file -/// * `ctx` - Error context string in case of an error -/// -/// -/// # Errors -/// Passes through any kind of error `std::fs::write` produces -pub fn write_file>(path: &str, data: D, ctx: &str) -> Result<()> { - std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo { - ty: FileIoErrorType::Write, - ctx: ctx.to_string(), - path: path.to_string(), - source: e, - }) -} - -/// Write content to a [`std::io::Write`] and add context in case of an error -/// -/// * `path` - Path to file -/// * `ctx` - Error context string in case of an error -/// -/// -/// # Errors -/// Passes through any kind of error `std::fs::write` produces -pub fn write, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> { - wr.write_all(data.as_ref()).map_err(|e| Error::FileIo { - ty: FileIoErrorType::Write, - ctx: ctx.to_string(), - path: path.to_string(), - source: e, - }) -} /// Read all CRLs from the buffer and parse them into a vector. /// @@ -357,9 +11,6 @@ pub fn write, W: Write>(wr: &mut W, data: D, path: &str, ctx: &st /// /// This function will return an error if the underlying openssl implementation cannot parse `buf` /// as `DER` or `PEM`. -/// -/// Requires the `request` feature. -#[cfg(feature = "request")] pub fn read_crls(buf: &[u8]) -> Result> { use openssl_extensions::crl::StackableX509Crl; X509Crl::from_der(buf) @@ -374,9 +25,6 @@ pub fn read_crls(buf: &[u8]) -> Result> { /// /// This function will return an error if the underlying openssl implementation cannot parse `buf` /// as `DER` or `PEM`. -/// -/// Requires the `request` feature. -#[cfg(feature = "request")] pub fn read_certs(buf: &[u8]) -> Result> { X509::from_der(buf) .map(|crt| vec![crt]) @@ -384,191 +32,11 @@ pub fn read_certs(buf: &[u8]) -> Result> { .map_err(Error::Crypto) } -macro_rules! usize_to_ui { - ($(#[$attr:meta])* => $t: ident, $name:ident) => { - ///Converts an [`usize`] to an [` - $(#[$attr])* - ///`] if possible - pub fn $name(u: usize) -> Option<$t> { - if u > $t::MAX as usize { - None - } else { - Some(u as $t) - } - } - - } -} - -usize_to_ui! { -#[doc = r"u32"] -=> u32, to_u32} -usize_to_ui! { -#[doc = r"u16"] -=> u16, to_u16} - -/// Test if both slices contain the exact same bytes. -/// -/// Do not use this to compare cryptographic values (i.e. hashes) -pub fn memeq(lhs: &[u8], rhs: &[u8]) -> bool { - let size = lhs.len(); - - size == rhs.len() - && unsafe { - let l = lhs as *const _ as _; - let r = rhs as *const _ as _; - (l as usize) == (r as usize) || libc::memcmp(l, r, size) == 0 - } -} - -/// Converts the hexstring into a byte vector. -/// -/// Stops if the end or until a non hex chat is found -pub fn parse_hex(hex_str: &str) -> Vec { - let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b { - b'0'..=b'9' => Some(b - b'0'), - b'a'..=b'f' => Some(b - b'a' + 10), - b'A'..=b'F' => Some(b - b'A' + 10), - _ => None, - }); - - let mut bytes = Vec::new(); - while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) { - bytes.push(h << 4 | l) - } - bytes -} -/// Report if the `prot_virt_guest` sysfs entry is one. -/// -/// If the entry does not exist returns false. -/// -/// for non-s390-architectures: -/// Returns always false -/// A non-s390 system cannot be a secure execution guest. -#[allow(unreachable_code)] -pub fn pv_guest_bit_set() -> bool { - #[cfg(not(target_arch = "s390x"))] - return false; - //s390 branch - let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]); - let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0); - v == 1 -} - #[cfg(test)] mod tests { - use std::usize; - - use super::*; - #[cfg(feature = "request")] use crate::test_utils::*; #[test] - fn msb_flags() { - let v = 17; - let v_flag: Msb0Flags64 = v.into(); - assert_eq!(v, v_flag.0.get()); - - let mut v: Msb0Flags64 = 4.into(); - v.unset_bit(61); - assert_eq!(v.0.get(), 0); - v.set_bit(61); - assert_eq!(4, v.0.get()); - - let mut v = Msb0Flags64::default(); - v.set_bit(0); - assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); - v.set_bit(0); - assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); - v.set_bit(1); - assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); - v.set_bit(2); - assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); - v.set_bit(3); - assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); - - v.unset_bit(3); - assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); - v.unset_bit(3); - assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); - - v.set_bit(16); - assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes()); - } - - #[test] - #[should_panic] - fn msb_flags_set_panic() { - Msb0Flags64::default().set_bit(64) - } - - #[test] - #[should_panic] - fn msb_flags_unset_panic() { - Msb0Flags64::default().unset_bit(64) - } - - #[test] - fn lsb_flags() { - let v = 17; - let v_flag: Lsb0Flags64 = v.into(); - assert_eq!(v, v_flag.0.get()); - - let mut v: Lsb0Flags64 = 4.into(); - v.unset_bit(2); - assert_eq!(v.0.get(), 0); - v.set_bit(2); - assert_eq!(4, v.0.get()); - - let mut v = Lsb0Flags64::default(); - v.set_bit(0); - assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes()); - v.set_bit(0); - assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes()); - v.set_bit(1); - assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes()); - v.set_bit(2); - assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); - v.set_bit(3); - assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes()); - - v.unset_bit(3); - assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); - v.unset_bit(3); - assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); - - v.set_bit(16); - assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes()); - } - - #[test] - #[should_panic] - fn lsb_flags_set_panic() { - Lsb0Flags64::default().set_bit(64) - } - - #[test] - #[should_panic] - fn lsb_flags_unset_panic() { - Lsb0Flags64::default().unset_bit(64) - } - #[test] - fn parse_hex() { - let s = "123456acbef0"; - let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; - assert_eq!(super::parse_hex(s), exp); - - let s = "00123456acbef0"; - let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; - assert_eq!(super::parse_hex(s), exp); - - let s = "00123456acbef0ii90"; - let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; - assert_eq!(super::parse_hex(s), exp); - } - - #[test] - #[cfg(feature = "request")] fn read_crls() { let crl = get_cert_asset("ibm.crl"); let crl_der = get_cert_asset("der.crl"); @@ -579,7 +47,6 @@ mod tests { } #[test] - #[cfg(feature = "request")] fn read_certs() { let crt = get_cert_asset("ibm.crt"); let crt_der = get_cert_asset("der.crt"); @@ -588,100 +55,4 @@ mod tests { assert_eq!(super::read_certs(&crt_der).unwrap().len(), 1); assert_eq!(super::read_certs(&fail).unwrap().len(), 0); } - - #[test] - fn to_u32() { - assert_eq!(Some(17), super::to_u32(17)); - assert_eq!(Some(0), super::to_u32(0)); - assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize)); - assert_eq!(None, super::to_u32(u32::MAX as usize + 1)); - assert_eq!(None, super::to_u32(usize::MAX)); - } - - #[test] - fn parse_u128() { - assert!(matches!( - try_parse_u128("123456", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("-1234", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("0011223344556677889900aabbccddeeff", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("dd11223344556677889900aabbccddeeff", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("-1223344556677889900aabbccddeeff", ""), - Err(Error::Specification(_)) - )); - - assert!(matches!( - try_parse_u128("0x123456", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("-0x1234", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("0x0011223344556677889900aabbccddeeff", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""), - Err(Error::Specification(_)) - )); - assert!(matches!( - try_parse_u128("0x-1223344556677889900aabbccddeeff", ""), - Err(Error::Specification(_)) - )); - - assert_eq!( - [ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, - 0xee, 0xff - ], - try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap() - ); - assert_eq!( - [ - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, - 0xee, 0xff - ], - try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap() - ); - assert_eq!( - [ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, - 0xee, 0xff - ], - try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap() - ); - assert_eq!( - [ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, - 0xee, 0xff - ], - try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap() - ); - } - - #[test] - fn memeq() { - let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; - let b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1]; - let c = [0, 0, 1, 2, 3, 4]; - - assert!(super::memeq(&a, &a)); - assert!(super::memeq(&a, &a.clone())); - assert!(!super::memeq(&b, &a)); - assert!(!super::memeq(&b, &c)); - assert!(!super::memeq(&b, &[])); - } } diff --git a/rust/pv/src/uvsecret.rs b/rust/pv/src/uvsecret.rs index 64702eca..0e9b3e44 100644 --- a/rust/pv/src/uvsecret.rs +++ b/rust/pv/src/uvsecret.rs @@ -2,79 +2,11 @@ // // Copyright IBM Corp. 2023 -#![cfg(feature = "uvsecret")] //! Provides functionality to manage the UV secret store. //! //! Provides functionality to build `add-secret` requests. //! Also provides interfaces, to dispatch `Add Secret`, `Lock Secret Store`, //! and `List Secrets` requests, -#[cfg(feature = "request")] pub mod asrcb; -#[cfg(feature = "request")] pub mod ext_secret; -#[cfg(feature = "request")] pub mod guest_secret; -pub mod secret_list; -pub mod uvc; - -use crate::request::MagicValue; -use crate::requires_feat; - -#[allow(unused_imports)] //used for more convenient docstring -use asrcb::AddSecretRequest; -/// Types of (non architectured) user data for [`AddSecretRequest`] -/// -#[doc = requires_feat!(uvsecret)] -#[repr(u16)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, zerocopy::AsBytes)] -pub enum UserDataType { - /// Marker that the request does not contain any user data - Null = 0x0000, -} - -/// The magic value used to identify an [`AddSecretRequest`] -/// -/// The magic value is ASCII: -/// ```rust -/// # use pv::request::uvsecret::AddSecretMagic; -/// # use pv::request::MagicValue; -/// # fn main() { -/// # let magic = -/// # b"asrcbM" -/// # ; -/// # assert!(AddSecretMagic::starts_with_magic(magic)); -/// # } -///``` -/// -#[doc = requires_feat!(uvsecret)] -#[repr(C)] -#[derive(Debug, Clone, Copy, zerocopy::AsBytes)] -pub struct AddSecretMagic { - magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D] - tp: UserDataType, -} - -impl MagicValue<6> for AddSecretMagic { - // "asrcbM" - const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]; -} - -impl From for AddSecretMagic { - fn from(tp: UserDataType) -> Self { - Self { - magic: Self::MAGIC, - tp, - } - } -} - -const SECRET_ID_SIZE: usize = 32; -fn ser_gsid(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result -where - S: serde::Serializer, -{ - let mut s = String::with_capacity(32 * 2 + 2); - s.push_str("0x"); - let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}")); - ser.serialize_str(&s) -} diff --git a/rust/pv/src/uvsecret/asrcb.rs b/rust/pv/src/uvsecret/asrcb.rs index a38e6d8f..212fb978 100644 --- a/rust/pv/src/uvsecret/asrcb.rs +++ b/rust/pv/src/uvsecret/asrcb.rs @@ -2,8 +2,6 @@ // // Copyright IBM Corp. 2023 -use super::{AddSecretMagic, UserDataType}; -use crate::requires_feat; use crate::{ assert_size, misc::Flags, @@ -14,11 +12,15 @@ use crate::{ Md, }, uvsecret::{ExtSecret, GuestSecret}, - Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, RequestVersion, Secret, + Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, Secret, }, uv::{ConfigUid, UvFlags}, Result, }; +use pv_core::request::{ + uvsecret::{AddSecretMagic, UserDataType}, + RequestVersion, +}; use zerocopy::AsBytes; /// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be @@ -96,8 +98,6 @@ impl ReqConfData { } /// Flags for [`AddSecretRequest`] -/// -#[doc = requires_feat!(reqsecret)] #[derive(Default, Clone, Copy, Debug)] pub struct AddSecretFlags(UvFlags); impl AddSecretFlags { @@ -123,8 +123,6 @@ impl From for UvFlags { } /// Versions for [`AddSecretRequest`] -/// -#[doc = requires_feat!(reqsecret)] #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AddSecretVersion { @@ -143,12 +141,6 @@ impl From for RequestVersion { } } -impl AddSecretMagic { - fn get(&self) -> crate::request::RequestMagic { - self.as_bytes().try_into().unwrap() - } -} - /// Add-secret request Control Block /// /// An ASRCB wraps a secret to transport it securely to the Ultravisor. @@ -173,8 +165,6 @@ impl AddSecretMagic { /// | AES GCM Tag (16) | /// |_____________________________________________________________| ///``` -/// -#[doc = requires_feat!(reqsecret)] #[derive(Clone, Debug)] pub struct AddSecretRequest { magic: AddSecretMagic, @@ -249,7 +239,11 @@ impl AddSecretRequest { self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k))); aad.push(Aad::Plain(&secr_auth)); - ctx.build_aad(self.version.into(), &aad, conf_len, self.magic.get()) + ctx.build_aad(self.version.into(), &aad, conf_len, self.magic()) + } + + fn magic(&self) -> crate::request::RequestMagic { + self.magic.as_bytes().try_into().unwrap() } #[doc(hidden)] diff --git a/rust/pv/src/uvsecret/ext_secret.rs b/rust/pv/src/uvsecret/ext_secret.rs index 38c4e8c8..0c9410d1 100644 --- a/rust/pv/src/uvsecret/ext_secret.rs +++ b/rust/pv/src/uvsecret/ext_secret.rs @@ -2,11 +2,9 @@ // // Copyright IBM Corp. 2023 -use crate::{request::Secret, requires_feat}; +use crate::request::Secret; /// Extension Secret for [`crate::request::uvsecret::AddSecretRequest`] -/// -#[doc = requires_feat!(reqsecret)] #[derive(Debug, Clone)] pub enum ExtSecret { /// A bytepattern that must be equal for each request targeting the same SE-guest instance diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs index 8c444211..8e826a7c 100644 --- a/rust/pv/src/uvsecret/guest_secret.rs +++ b/rust/pv/src/uvsecret/guest_secret.rs @@ -4,18 +4,16 @@ #[allow(unused_imports)] //used for more convenient docstring use super::asrcb::AddSecretRequest; -use super::{ser_gsid, SECRET_ID_SIZE}; use crate::{ request::{hash, openssl::MessageDigest, random_array, Secret}, - requires_feat, Result, + Result, }; +use pv_core::for_pv::{ser_gsid, SECRET_ID_SIZE}; use serde::{Deserialize, Serialize}; use std::convert::TryInto; const SECRET_SIZE: usize = 32; /// A Secret to be added in [`AddSecretRequest`] -/// -#[doc = requires_feat!(reqsecret)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum GuestSecret { /// No guest secret diff --git a/rust/pv/src/uvsecret/uvc.rs b/rust/pv/src/uvsecret/uvc.rs index 4f172144..cdcd3579 100644 --- a/rust/pv/src/uvsecret/uvc.rs +++ b/rust/pv/src/uvsecret/uvc.rs @@ -2,13 +2,12 @@ // // Copyright IBM Corp. 2023 -use super::AddSecretMagic; use crate::{ - request::MagicValue, requires_feat, uv::{uv_ioctl, UvCmd, UvDevice}, Error, Result, PAGESIZE, }; +use pv_core::request::{uvsecret::AddSecretMagic, MagicValue}; use std::io::Read; use std::usize; diff --git a/rust/pv/tests/add_secret_request.rs b/rust/pv/tests/add_secret_request.rs index bf491c61..0621d01f 100644 --- a/rust/pv/tests/add_secret_request.rs +++ b/rust/pv/tests/add_secret_request.rs @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2023 - -#![cfg(all(feature = "request", feature = "uvsecret"))] use pv::{ get_test_asset, request::{ diff --git a/rust/pv_core/Cargo.toml b/rust/pv_core/Cargo.toml new file mode 100644 index 00000000..3d0efb10 --- /dev/null +++ b/rust/pv_core/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pv_core" +version = "0.9.1" +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2.49" +log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } +thiserror = "1.0.33" +utils = {path = "../utils"} +zerocopy = "0.6" +serde = { version = "1.0.139", features = ["derive"]} +byteorder = "1.3" + +[dev-dependencies] +serde_test = "1" +mockito = {version = "1", default-features = false } +lazy_static = "1.1" diff --git a/rust/pv_core/src/error.rs b/rust/pv_core/src/error.rs new file mode 100644 index 00000000..e2954eb7 --- /dev/null +++ b/rust/pv_core/src/error.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +/// Result type for this crate +pub type Result = std::result::Result; + +/// Error cases for this crate +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))] + #[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))] + Uv { + rc: u16, + rrc: u16, + msg: &'static str, + }, + + #[error("{0}")] + Specification(String), + + #[error("Cannot {ty} {ctx} at `{path}`")] + FileIo { + ty: FileIoErrorType, + ctx: String, + path: String, + source: std::io::Error, + }, + #[error("Cannot {ty} `{path}`")] + FileAccess { + ty: FileAccessErrorType, + path: String, + source: std::io::Error, + }, + + #[error("Cannot encode secrets (Too many secrets)")] + ManySecrets, + + #[error("Cannot decode secret list")] + InvSecretList(#[source] std::io::Error), + + #[error("Input does not contain an add-secret request")] + NoAsrcb, + + // errors from other crates + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + ParseInt(#[from] std::num::ParseIntError), +} + +/// Error cases for I/O operations +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum FileIoErrorType { + #[error("read")] + Read, + #[error("write")] + Write, +} + +/// Error cases for accessing files +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum FileAccessErrorType { + #[error("open")] + Open, + #[error("create")] + Create, +} diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs new file mode 100644 index 00000000..69f3bf21 --- /dev/null +++ b/rust/pv_core/src/lib.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 +#![deny(missing_docs)] +#![allow(unused)] +//! pv_core - basic library for pv-tools +//! +//! This library is intened to be used by tools and libraries that +//! are used for creating and managing IBM Secure Execution guests. +//! `pv_core` provides abstraction layers for secure memory management, +//! logging, and accessing the uvdevice. +//! +//! It does not provide any cryptographic operations through OpenSSL. +//! For this use `pv` which reexports all symbos from this crate. +mod error; +mod log; +mod macros; +mod utils; +mod uvdevice; +mod uvsecret; + +pub use crate::log::PvLogger; +pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result}; + +/// Miscellaneous functions and definitions +pub mod misc { + pub use crate::utils::pv_guest_bit_set; + pub use crate::utils::{create_file, open_file, read_exact_file, read_file, write_file}; + pub use crate::utils::{memeq, parse_hex, to_u16, to_u32, try_parse_u128, try_parse_u64}; + pub use crate::utils::{read, write}; + pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64}; +} + +/// Definitions and functions for interacting with the Ultravisor +pub mod uv { + pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd}; + pub use crate::uvdevice::secret::{ListableSecretType, SecretEntry, SecretList}; + pub use crate::uvdevice::{ + uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess, + }; +} + +/// Functionalities to verify UV requests +pub mod request { + /// Functionalities for reading add-secret requests + pub mod uvsecret { + pub use crate::uvsecret::AddSecretMagic; + pub use crate::uvsecret::UserDataType; + } + + /// Version number of the request in system-endian + pub type RequestVersion = u32; + /// Request magic value + /// + /// The first 8 byte of a request providing an identifier of the request type + /// for programs + pub type RequestMagic = [u8; 8]; + /// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified + /// (binary) data. + pub trait MagicValue { + /// Magic value as byte array + const MAGIC: [u8; N]; + /// Test whether the given slice starts with the magic value. + fn starts_with_magic(v: &[u8]) -> bool { + if v.len() < Self::MAGIC.len() { + return false; + } + crate::misc::memeq(&v[..Self::MAGIC.len()], &Self::MAGIC) + } + } +} + +/// Provides cargo version Info about this crate. +/// +/// Produces `pv_core-crate ` +pub const fn crate_info() -> &'static str { + concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION")) +} + +// Internal definitions/ imports +const PAGESIZE: usize = 0x1000; +use ::utils::assert_size; +use ::utils::static_assert; + +#[doc(hidden)] +/// stuff pv_core and pv share. Not intended for other users +pub mod for_pv { + pub use crate::uvdevice::secret::ser_gsid; + pub use crate::uvdevice::secret::SECRET_ID_SIZE; +} diff --git a/rust/pv/src/log.rs b/rust/pv_core/src/log.rs similarity index 100% rename from rust/pv/src/log.rs rename to rust/pv_core/src/log.rs diff --git a/rust/pv_core/src/macros.rs b/rust/pv_core/src/macros.rs new file mode 100644 index 00000000..019b5f3b --- /dev/null +++ b/rust/pv_core/src/macros.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +macro_rules! path_to_str { + ($path: expr) => { + $path.as_ref().to_str().unwrap_or("no UTF-8 path") + }; +} +pub(crate) use path_to_str; + +macro_rules! file_error { + ($ty: tt, $ctx: expr, $path:expr, $src: expr) => { + $crate::Error::FileIo { + ty: $crate::FileIoErrorType::$ty, + ctx: $ctx.to_string(), + path: $path.to_string(), + source: $src, + } + }; +} +pub(crate) use file_error; + +macro_rules! bail_spec { + ($str: expr) => { + return Err($crate::Error::Specification($str.to_string())) + }; +} +pub(crate) use bail_spec; + +#[doc(hidden)] +#[macro_export] +macro_rules! file_acc_error { + ($ty: tt, $path:expr, $src: expr) => { + $crate::Error::FileAccess { + ty: $crate::FileAccessErrorType::$ty, + path: $path.to_string(), + source: $src, + } + }; +} diff --git a/rust/pv_core/src/utils.rs b/rust/pv_core/src/utils.rs new file mode 100644 index 00000000..66edffd6 --- /dev/null +++ b/rust/pv_core/src/utils.rs @@ -0,0 +1,589 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 +use crate::{ + macros::{bail_spec, file_error, path_to_str}, + Error, FileAccessErrorType, FileIoErrorType, Result, +}; +use std::{ + fs::File, + io::{Read, Write}, + path::Path, +}; +use zerocopy::{AsBytes, BigEndian, FromBytes, U64}; + +/// Trait that describes bitflags, represented by `T`. +pub trait Flags: From + for<'a> From<&'a T> { + /// Set the specified bit to one. + /// # Panics + ///Panics if bit is >= 64 + fn set_bit(&mut self, bit: u8); + /// Set the specified bit to zero. + /// # Panics + ///Panics if bit is >= 64 + fn unset_bit(&mut self, bit: u8); + /// Test if the specified bit is set. + /// # Panics + ///Panics if bit is >= 64 + fn is_set(&self, bit: u8) -> bool; +} + +/// Bitflags in MSB0 ordering +/// +/// Wraps an u64 to set/get individual bits +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)] +pub struct Msb0Flags64(U64); +impl Flags for Msb0Flags64 { + #[track_caller] + fn set_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v |= 1 << (63 - bit); + self.0.set(v) + } + + #[track_caller] + fn unset_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v &= !(1 << (63 - bit)); + self.0.set(v) + } + + #[track_caller] + fn is_set(&self, bit: u8) -> bool { + assert!(bit < 64, "Flag bit set to greater than 63"); + self.0.get() & (1 << (63 - bit)) > 0 + } +} + +impl From for Msb0Flags64 { + fn from(value: u64) -> Self { + Self(value.into()) + } +} + +impl From<&u64> for Msb0Flags64 { + fn from(value: &u64) -> Self { + (*value).into() + } +} + +/// Bitflags in LSB0 ordering +/// +/// Wraps an u64 to set/get individual bits +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)] +pub struct Lsb0Flags64(U64); +impl Flags for Lsb0Flags64 { + #[track_caller] + fn set_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v |= 1 << bit; + self.0.set(v) + } + + #[track_caller] + fn unset_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v &= !(1 << bit); + self.0.set(v) + } + + #[track_caller] + fn is_set(&self, bit: u8) -> bool { + assert!(bit < 64, "Flag bit set to greater than 63"); + self.0.get() & (1 << bit) > 0 + } +} + +impl From for Lsb0Flags64 { + fn from(value: u64) -> Self { + Self(value.into()) + } +} + +impl From<&u64> for Lsb0Flags64 { + fn from(value: &u64) -> Self { + (*value).into() + } +} + +/// Tries to convert a BE hex string into a 128 unsigned integer +/// The hexstring must contain 32chars of hexdigits +/// +/// * `hex_str` - string to convert can be prepended with "0x" +/// * `ctx` - Error context string in case of an error +/// ```rust +/// # use std::error::Error; +/// # use pv_core::misc::try_parse_u128; +/// # fn main() -> Result<(), Box> { +/// let hex = "11223344556677889900aabbccddeeff"; +/// try_parse_u128(&hex, "The test")?; +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// If `hex_string` is not a 32 byte hex string an Error appears +pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> { + let hex_str = if hex_str.starts_with("0x") { + hex_str.split_at(2).1 + } else { + hex_str + }; + if hex_str.len() != 32 { + bail_spec!(format!( + "{ctx} hexstring must be 32chars long to cover all 16 bytes" + )); + } + parse_hex(hex_str).try_into().map_err(|_| { + Error::Specification(format!( + "{ctx} hexstring must be 32chars long to cover all 16 bytes" + )) + }) +} + +/// Tries to convert a BE hex string into a 64 unsigned integer +/// The hexstring must *NOT* contain 16 chars of hexdigits, but +/// 16 chars at most. +/// +/// * `hex_str` - string to convert can be prepended with "0x" +/// * `ctx` - Error context string in case of an error +/// ```rust +/// # use std::error::Error; +/// # use pv_core::misc::try_parse_u64; +/// # fn main() -> Result<(), Box> { +/// let hex = "1234567890abcdef"; +/// try_parse_u64(&hex, "The test")?; +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// If `hex_string` is not a 32 byte hex string an Error appears +pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result { + let hex_str = if hex_str.starts_with("0x") { + hex_str.split_at(2).1 + } else { + hex_str + }; + if hex_str.len() > 16 { + bail_spec!(format!( + "{ctx} hexstring {hex_str} must be max 16 chars long" + )); + } + Ok(u64::from_str_radix(hex_str, 16)?) +} + +/// Open a file. +/// +/// Wraps [`File::open`] +/// +/// * `path` - Path to file +pub fn open_file>(path: P) -> Result { + File::open(&path).map_err(|e| Error::FileAccess { + ty: FileAccessErrorType::Open, + path: path_to_str!(path).to_string(), + source: e, + }) +} + +/// Create a file. +/// +/// Wraps [`File::create`] +/// +/// * `path` - Path to file +pub fn create_file>(path: P) -> Result { + File::create(&path).map_err(|e| Error::FileAccess { + ty: FileAccessErrorType::Create, + path: path_to_str!(path).to_string(), + source: e, + }) +} + +/// Read exactly COUNT bytes into a buffer and return it. +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// # Errors +/// If this function encounters an "end of file" before completely filling +/// the buffer, it returns an error. The contents of `buf` are unspecified in this case. +/// +/// If aber so hast du mmmeny other read error is encountered then this function immediately +/// returns. The contents of `buf` are unspecified in this case. +/// +/// If this function returns an error, it is unspecified how many bytes it +/// has read, but it will never read more than would be necessary to +/// completely fill the buffer. +pub fn read_exact_file, const COUNT: usize>( + path: P, + ctx: &str, +) -> Result<[u8; COUNT]> { + let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess { + ty: crate::FileAccessErrorType::Open, + path: path_to_str!(path).to_string(), + source: e, + })?; + + if f.metadata()?.len() as usize != COUNT { + bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long")); + } + + let mut buf = [0; COUNT]; + f.read_exact(&mut buf) + .map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?; + Ok(buf) +} + +/// Read content from a file and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::read` produces +pub fn read_file>(path: P, ctx: &str) -> Result> { + std::fs::read(&path).map_err(|e| { + file_error!( + Read, + ctx, + path.as_ref().to_str().unwrap_or("no UTF-8 path"), + e + ) + }) +} + +/// Reads all content from a [`std::io::Read`] and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::read` produces +pub fn read(rd: &mut R, path: &str, ctx: &str) -> Result> { + let mut buf = vec![]; + rd.read_to_end(&mut buf).map_err(|e| Error::FileIo { + ty: FileIoErrorType::Write, + ctx: ctx.to_string(), + path: path.to_string(), + source: e, + })?; + Ok(buf) +} + +/// write content to a file and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::write` produces +pub fn write_file>(path: &str, data: D, ctx: &str) -> Result<()> { + std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo { + ty: FileIoErrorType::Write, + ctx: ctx.to_string(), + path: path.to_string(), + source: e, + }) +} + +/// Write content to a [`std::io::Write`] and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::write` produces +pub fn write, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> { + wr.write_all(data.as_ref()).map_err(|e| Error::FileIo { + ty: FileIoErrorType::Write, + ctx: ctx.to_string(), + path: path.to_string(), + source: e, + }) +} + +macro_rules! usize_to_ui { + ($(#[$attr:meta])* => $t: ident, $name:ident) => { + ///Converts an [`usize`] to an [` + $(#[$attr])* + ///`] if possible + pub fn $name(u: usize) -> Option<$t> { + if u > $t::MAX as usize { + None + } else { + Some(u as $t) + } + } + + } +} + +usize_to_ui! { +#[doc = r"u32"] +=> u32, to_u32} +usize_to_ui! { +#[doc = r"u16"] +=> u16, to_u16} + +/// Test if both slices contain the exact same bytes. +/// +/// Do not use this to compare cryptographic values (i.e. hashes) +pub fn memeq(lhs: &[u8], rhs: &[u8]) -> bool { + let size = lhs.len(); + + size == rhs.len() + && unsafe { + let l = lhs as *const _ as _; + let r = rhs as *const _ as _; + (l as usize) == (r as usize) || libc::memcmp(l, r, size) == 0 + } +} + +/// Converts the hexstring into a byte vector. +/// +/// Stops if the end or until a non hex chat is found +pub fn parse_hex(hex_str: &str) -> Vec { + let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b { + b'0'..=b'9' => Some(b - b'0'), + b'a'..=b'f' => Some(b - b'a' + 10), + b'A'..=b'F' => Some(b - b'A' + 10), + _ => None, + }); + + let mut bytes = Vec::new(); + while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) { + bytes.push(h << 4 | l) + } + bytes +} +/// Report if the `prot_virt_guest` sysfs entry is one. +/// +/// If the entry does not exist returns false. +/// +/// for non-s390-architectures: +/// Returns always false +/// A non-s390 system cannot be a secure execution guest. +#[allow(unreachable_code)] +pub fn pv_guest_bit_set() -> bool { + #[cfg(not(target_arch = "s390x"))] + return false; + //s390 branch + let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]); + let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0); + v == 1 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn msb_flags() { + let v = 17; + let v_flag: Msb0Flags64 = v.into(); + assert_eq!(v, v_flag.0.get()); + + let mut v: Msb0Flags64 = 4.into(); + v.unset_bit(61); + assert_eq!(v.0.get(), 0); + v.set_bit(61); + assert_eq!(4, v.0.get()); + + let mut v = Msb0Flags64::default(); + v.set_bit(0); + assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(0); + assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(1); + assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(2); + assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(3); + assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + + v.unset_bit(3); + assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.unset_bit(3); + assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + + v.set_bit(16); + assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes()); + } + + #[test] + #[should_panic] + fn msb_flags_set_panic() { + Msb0Flags64::default().set_bit(64) + } + + #[test] + #[should_panic] + fn msb_flags_unset_panic() { + Msb0Flags64::default().unset_bit(64) + } + + #[test] + fn lsb_flags() { + let v = 17; + let v_flag: Lsb0Flags64 = v.into(); + assert_eq!(v, v_flag.0.get()); + + let mut v: Lsb0Flags64 = 4.into(); + v.unset_bit(2); + assert_eq!(v.0.get(), 0); + v.set_bit(2); + assert_eq!(4, v.0.get()); + + let mut v = Lsb0Flags64::default(); + v.set_bit(0); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes()); + v.set_bit(0); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes()); + v.set_bit(1); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes()); + v.set_bit(2); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); + v.set_bit(3); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes()); + + v.unset_bit(3); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); + v.unset_bit(3); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); + + v.set_bit(16); + assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes()); + } + + #[test] + #[should_panic] + fn lsb_flags_set_panic() { + Lsb0Flags64::default().set_bit(64) + } + + #[test] + #[should_panic] + fn lsb_flags_unset_panic() { + Lsb0Flags64::default().unset_bit(64) + } + #[test] + fn parse_hex() { + let s = "123456acbef0"; + let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; + assert_eq!(super::parse_hex(s), exp); + + let s = "00123456acbef0"; + let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; + assert_eq!(super::parse_hex(s), exp); + + let s = "00123456acbef0ii90"; + let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; + assert_eq!(super::parse_hex(s), exp); + } + + #[test] + fn to_u32() { + assert_eq!(Some(17), super::to_u32(17)); + assert_eq!(Some(0), super::to_u32(0)); + assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize)); + assert_eq!(None, super::to_u32(u32::MAX as usize + 1)); + assert_eq!(None, super::to_u32(usize::MAX)); + } + + #[test] + fn parse_u128() { + assert!(matches!( + try_parse_u128("123456", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("-1234", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0011223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("dd11223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("-1223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + + assert!(matches!( + try_parse_u128("0x123456", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("-0x1234", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0x0011223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0x-1223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + + assert_eq!( + [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap() + ); + assert_eq!( + [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap() + ); + assert_eq!( + [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap() + ); + assert_eq!( + [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap() + ); + } + + #[test] + fn memeq() { + let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + let b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1]; + let c = [0, 0, 1, 2, 3, 4]; + + assert!(super::memeq(&a, &a)); + assert!(super::memeq(&a, &a.clone())); + assert!(!super::memeq(&b, &a)); + assert!(!super::memeq(&b, &c)); + assert!(!super::memeq(&b, &[])); + } +} diff --git a/rust/pv/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs similarity index 98% rename from rust/pv/src/uvdevice.rs rename to rust/pv_core/src/uvdevice.rs index 87cd6af6..53bae59e 100644 --- a/rust/pv/src/uvdevice.rs +++ b/rust/pv_core/src/uvdevice.rs @@ -7,9 +7,11 @@ use crate::FileAccessErrorType; use crate::{Error, Result}; use libc::c_ulong; use log::debug; -use std::convert::TryInto; -use std::fs::File; -use std::os::unix::prelude::{AsRawFd, RawFd}; +use std::{ + convert::TryInto, + fs::File, + os::unix::prelude::{AsRawFd, RawFd}, +}; #[cfg(not(test))] use ::libc::ioctl; @@ -22,6 +24,7 @@ mod ffi; mod info; mod test; pub use ffi::uv_ioctl; +pub mod secret; pub use info::UvDeviceInfo; #[allow(dead_code)] //TODO rm when pv learns attestation diff --git a/rust/pv/src/uvdevice/ffi.rs b/rust/pv_core/src/uvdevice/ffi.rs similarity index 100% rename from rust/pv/src/uvdevice/ffi.rs rename to rust/pv_core/src/uvdevice/ffi.rs diff --git a/rust/pv/src/uvdevice/info.rs b/rust/pv_core/src/uvdevice/info.rs similarity index 100% rename from rust/pv/src/uvdevice/info.rs rename to rust/pv_core/src/uvdevice/info.rs diff --git a/rust/pv/src/uvsecret/secret_list.rs b/rust/pv_core/src/uvdevice/secret.rs similarity index 69% rename from rust/pv/src/uvsecret/secret_list.rs rename to rust/pv_core/src/uvdevice/secret.rs index c60faa7e..4ec3c20b 100644 --- a/rust/pv/src/uvsecret/secret_list.rs +++ b/rust/pv_core/src/uvdevice/secret.rs @@ -2,7 +2,13 @@ // // Copyright IBM Corp. 2023 -use crate::{assert_size, misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result}; +use crate::{ + assert_size, + misc::to_u16, + request::{uvsecret::AddSecretMagic, MagicValue}, + uv::{uv_ioctl, UvCmd, UvDevice}, + Error, Result, PAGESIZE, +}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use serde::{Serialize, Serializer}; use std::{ @@ -13,11 +19,119 @@ use std::{ }; use zerocopy::{AsBytes, FromBytes, U16, U32}; -use super::ser_gsid; - -/// List of secrets used to parse the [`crate::uv::ListCmd`] result +/// _List Secrets_ Ultravisor command. /// -/// Requires the `uvsecret` feature. +/// The List Secrets Ultravisor call is used to list the +/// secrets that are in the secret store for the current SE-guest. +pub struct ListCmd(Vec); +impl ListCmd { + fn with_size(size: usize) -> Self { + Self(vec![0; size]) + } + + /// Create a new list secrets command with a one page capacity + pub fn new() -> Self { + Self::with_size(PAGESIZE) + } +} + +impl Default for ListCmd { + fn default() -> Self { + Self::new() + } +} + +impl UvCmd for ListCmd { + fn data(&mut self) -> Option<&mut [u8]> { + Some(self.0.as_mut_slice()) + } + + fn cmd(&self) -> u64 { + uv_ioctl(UvDevice::LIST_SECRET_NR) + } + + fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> { + None + } +} + +/// _Add Secret_ Ultravisor command. +/// +/// The Add Secret Ultravisor-call is used to add a secret +/// to the secret store for the current SE-guest. +pub struct AddCmd(Vec); + +impl AddCmd { + /// Create a new Add Secret command using the provided data. + /// + /// # Errors + /// + /// This function will return an error if the provided data does not start with the + /// ['crate::AddSecretRequest'] magic Value. + pub fn new(bin_add_secret_req: &mut R) -> Result { + let mut data = Vec::with_capacity(PAGESIZE); + bin_add_secret_req.read_to_end(&mut data)?; + + if !AddSecretMagic::starts_with_magic(&data[..6]) { + return Err(Error::NoAsrcb); + } + Ok(Self(data)) + } +} + +impl UvCmd for AddCmd { + fn data(&mut self) -> Option<&mut [u8]> { + Some(&mut self.0) + } + + fn cmd(&self) -> u64 { + uv_ioctl(UvDevice::ADD_SECRET_NR) + } + + fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> { + match rc { + 0x0101 => Some("not allowed to modify the secret store"), + 0x0102 => Some("secret store locked"), + 0x0103 => Some("access exception when accessing request control block"), + 0x0104 => Some("unsupported add secret version"), + 0x0105 => Some("invalid request size"), + 0x0106 => Some("invalid number of host-keys"), + 0x0107 => Some("unsupported flags specified"), + 0x0108 => Some("unable to decrypt the request"), + 0x0109 => Some("unsupported secret provided"), + 0x010a => Some("invalid length for the specified secret"), + 0x010b => Some("secret store full"), + 0x010c => Some("unable to add secret"), + 0x010d => Some("dump in progress, try again later"), + _ => None, + } + } +} + +/// _Lock Secret Store_ Ultravisor command. +/// +/// The Lock Secret Store Ultravisor-call is used to block +/// all changes to the secret store. Upon successful +/// completion of a Lock Secret Store Ultravisor-call, any +/// request to modify the secret store will fail. +pub struct LockCmd; +impl UvCmd for LockCmd { + fn cmd(&self) -> u64 { + uv_ioctl(UvDevice::LOCK_SECRET_NR) + } + + fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> { + match rc { + 0x0101 => Some("not allowed to modify the secret store"), + 0x0102 => Some("secret store already locked"), + _ => None, + } + } +} + +/// List of secrets used to parse the [`crate::uv::ListCmd`] result. +/// +/// The list should not hold more than 0xffffffff elements #[derive(Debug, PartialEq, Eq, Serialize)] pub struct SecretList { total_num_secrets: usize, @@ -51,8 +165,10 @@ impl FromIterator for SecretList { } impl SecretList { - #[doc(hidden)] - /// For testing purposes. + /// Creates a new SecretList. + /// + /// The content of this list will very liekly not represent the status of the guest in the + /// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged. pub fn new(total_num_secrets: u16, secrets: Vec) -> Self { Self { total_num_secrets: total_num_secrets as usize, @@ -72,12 +188,12 @@ impl SecretList { self.secrets.len() } - /// Check for is_empty of this [`SecretList`]. + /// Returns `true` if the [`SecretList`] contains no [`SecretEntry`]. pub fn is_empty(&self) -> bool { self.secrets.is_empty() } - /// Reports the number of secrets stored in UV + /// Reports the number of secrets stored in UV. /// /// This number may be not equal to the provided number of [`SecretEntry`] pub fn total_num_secrets(&self) -> usize { @@ -199,6 +315,20 @@ impl From for U16 { } } +#[doc(hidden)] +pub const SECRET_ID_SIZE: usize = 32; + +#[doc(hidden)] +pub fn ser_gsid(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result +where + S: serde::Serializer, +{ + let mut s = String::with_capacity(32 * 2 + 2); + s.push_str("0x"); + let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}")); + ser.serialize_str(&s) +} + /// A secret in a [`SecretList`] #[repr(C)] #[derive(Debug, PartialEq, Eq, AsBytes, FromBytes, Serialize)] @@ -212,15 +342,17 @@ pub struct SecretEntry { #[serde(skip)] res_8: u64, #[serde(serialize_with = "ser_gsid")] - id: [u8; 32], + id: [u8; SECRET_ID_SIZE], } assert_size!(SecretEntry, SecretEntry::STRUCT_SIZE); impl SecretEntry { const STRUCT_SIZE: usize = 0x30; - #[doc(hidden)] - /// For testing purposes. + /// Create a new entry for a [`SecretList`]. + /// + /// The content of this entry will very liekly not represent the status of the guest in the + /// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged. pub fn new(index: u16, stype: ListableSecretType, id: [u8; 32], secret_len: u32) -> Self { Self { index: index.into(), @@ -261,6 +393,7 @@ impl Display for SecretEntry { #[cfg(test)] mod test { + use super::*; use std::io::{BufReader, BufWriter, Cursor}; diff --git a/rust/pv/src/uvdevice/test.rs b/rust/pv_core/src/uvdevice/test.rs similarity index 100% rename from rust/pv/src/uvdevice/test.rs rename to rust/pv_core/src/uvdevice/test.rs diff --git a/rust/pv_core/src/uvsecret.rs b/rust/pv_core/src/uvsecret.rs new file mode 100644 index 00000000..3fbac315 --- /dev/null +++ b/rust/pv_core/src/uvsecret.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::{ + misc::to_u16, + request::MagicValue, + uv::{ListCmd, UvCmd}, + Error, Result, +}; +use std::{ + fmt::Display, + io::{Cursor, Read, Seek, Write}, +}; +use zerocopy::{AsBytes, FromBytes, U16, U32}; + +/// The magic value used to identify an add-secret request`] +/// +/// The magic value is ASCII: +/// ```rust +/// # use pv_core::request::uvsecret::AddSecretMagic; +/// # use pv_core::request::MagicValue; +/// # fn main() { +/// # let magic = +/// b"asrcbM" +/// # ; +/// # assert!(AddSecretMagic::starts_with_magic(magic)); +/// # } +///``` +/// +#[repr(C)] +#[derive(Debug, Clone, Copy, AsBytes)] +pub struct AddSecretMagic { + magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D] + tp: UserDataType, +} + +impl MagicValue<6> for AddSecretMagic { + // "asrcbM" + const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]; +} + +/// Types of (non architectured) user data for an add-secret request +#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, zerocopy::AsBytes)] +pub enum UserDataType { + /// Marker that the request does not contain any user data + Null = 0x0000, +} + +impl From for AddSecretMagic { + fn from(tp: UserDataType) -> Self { + Self { + magic: Self::MAGIC, + tp, + } + } +} diff --git a/rust/pvapconfig/Cargo.toml b/rust/pvapconfig/Cargo.toml index 2a69468c..1d617821 100644 --- a/rust/pvapconfig/Cargo.toml +++ b/rust/pvapconfig/Cargo.toml @@ -11,7 +11,7 @@ clap = { version ="4.1", features = ["derive", "wrap_help"]} lazy_static = "1.1" openssl = { version = "0.10" } openssl-sys = { version = "0.9" } -pv = { path = "../pv", features = ["uvsecret", "request"] } +pv = { path = "../pv" } rand = "0.8" regex = "1" serde = { version = "1.0", features = ["derive"] } diff --git a/rust/pvsecret/Cargo.toml b/rust/pvsecret/Cargo.toml index e236c005..5b0823fc 100644 --- a/rust/pvsecret/Cargo.toml +++ b/rust/pvsecret/Cargo.toml @@ -10,5 +10,5 @@ clap = { version ="4", features = ["derive", "wrap_help"]} log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } serde_yaml = "0.9" -pv = { path = "../pv", features = ["uvsecret", "request"] } +pv = { path = "../pv" } utils = { path = "../utils" } diff --git a/rust/utils/src/lib.rs b/rust/utils/src/lib.rs index e519f6c1..c8a29940 100644 --- a/rust/utils/src/lib.rs +++ b/rust/utils/src/lib.rs @@ -27,3 +27,36 @@ macro_rules! release_string { env!("S390_TOOLS_RELEASE", "env 'S390_TOOLS_RELEASE' must be set for release builds. Trigger build using the s390-tools build system or export the variable yourself") }}; } + +/// Asserts a constant expression evaluates to `true`. +/// +/// If the expression is not evaluated to `true` the compilation will fail. +#[macro_export] +macro_rules! static_assert { + ($condition:expr) => { + const _: () = core::assert!($condition); + }; +} + +/// Asserts that a type has a specific size. +/// +/// Useful to validate structs that are passed to C code. +/// If the size has not the expected value the compilation will fail. +/// +/// # Example +/// ```rust +/// # use utils::assert_size; +/// # fn main() {} +/// #[repr(C)] +/// struct c_struct { +/// v: u64, +/// } +/// assert_size!(c_struct, 8); +/// // assert_size!(c_struct, 7);//won't compile +/// ``` +#[macro_export] +macro_rules! assert_size { + ($t:ty, $sz:expr ) => { + $crate::static_assert!(::std::mem::size_of::<$t>() == $sz); + }; +}