rust: Refactoring and reduce API surface

Prepare pv & pv_core crates to be released on crates.io:
* Remove any unused API to stay flexible
* Remove utils dependency
* Move cli, tmpfile and version utilities to local utils crate
* Use the new utilities in the pv tools
* Rename Secret into Confidential to avoid confusion of Secret (now
  Confidential) and AddSecret requests.
* Move the uvsecret module out of the request module and change the name
  to secret.
* Cleanup dependencies
* Precise and correct minimal dependency versions
* Inline `Aes256Key::from_digest`

The cleanup ensures that the code also compiles with the dependencies
resolved to their minimal versions using:

$ cargo +nightly -Z minimal-versions update
$ cargo build

For more information refer to this blog post:
https://users.rust-lang.org/t/psa-please-specify-precise-dependency-versions-in-cargo-toml/71277/8

Signed-off-by: Marc Hartmayer <mhartmay@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Steffen Eiden
2024-05-21 16:26:51 +02:00
parent 5648b924d6
commit 381fecfc44
44 changed files with 499 additions and 490 deletions

10
rust/Cargo.lock generated
View File

@@ -414,7 +414,6 @@ name = "pv"
version = "1.0.0"
dependencies = [
"byteorder",
"clap",
"curl",
"log",
"openssl",
@@ -423,7 +422,6 @@ dependencies = [
"serde",
"serde_test",
"thiserror",
"utils",
"zerocopy",
]
@@ -438,7 +436,6 @@ dependencies = [
"serde",
"serde_test",
"thiserror",
"utils",
"zerocopy",
]
@@ -449,7 +446,6 @@ dependencies = [
"clap",
"lazy_static",
"openssl",
"openssl-sys",
"pv_core",
"rand",
"regex",
@@ -687,6 +683,12 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "utils"
version = "0.1.0"
dependencies = [
"clap",
"libc",
"log",
"pv",
]
[[package]]
name = "vcpkg"

View File

@@ -6,17 +6,15 @@ license.workspace = true
[dependencies]
byteorder = "1.3"
clap = { version ="4", features = ["derive", "wrap_help"] }
curl = "0.4.7"
curl = "0.4.44"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
openssl = "0.10.49"
openssl = "0.10.57"
serde = { version = "1.0.139", features = ["derive"] }
thiserror = "1.0.33"
utils = {path = "../utils"}
zerocopy = { version="0.7", features = ["derive"] }
openssl_extensions = { path = "openssl_extensions" }
pv_core = { path = "../pv_core" }
[dev-dependencies]
serde_test = "1"
serde_test = "1.0.139"

View File

@@ -8,5 +8,5 @@ license.workspace = true
foreign-types = "0.3.1"
libc = {version = "0.2.49", features = [ "extra_traits"] }
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
openssl = "0.10.49"
openssl-sys = "0.9.85"
openssl = "0.10.57"
openssl-sys = "0.9.92"

View File

@@ -8,9 +8,8 @@ use std::{
};
// (SE) boot request control block aka SE header
use crate::{assert_size, static_assert, Error, Result, PAGESIZE};
use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGESIZE};
use log::debug;
use pv_core::request::MagicValue;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
/// Struct containing all SE-header tags.
@@ -19,15 +18,14 @@ use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
/// Page List Digest (pld)
/// Address List Digest (ald)
/// Tweak List Digest (tld)
/// SE Header Tag (seht)
///
/// SE Header Tag (tag)
#[repr(C)]
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq)]
pub struct BootHdrTags {
pld: [u8; BootHdrHead::DIGEST_SIZE],
ald: [u8; BootHdrHead::DIGEST_SIZE],
tld: [u8; BootHdrHead::DIGEST_SIZE],
seht: [u8; BootHdrHead::SEHT_SIZE],
tag: [u8; BootHdrHead::TAG_SIZE],
}
/// Magiv value for a SE-(boot)header
@@ -38,19 +36,14 @@ impl MagicValue<8> for BootHdrMagic {
impl BootHdrTags {
/// Returns a reference to the SE-hdr tag of this [`BootHdrTags`].
pub fn seht(&self) -> &[u8; 16] {
&self.seht
pub fn tag(&self) -> &[u8; 16] {
&self.tag
}
/// Creates a new [`BootHdrTags`]. Useful for writing tests.
#[doc(hidden)]
pub const fn new(pld: [u8; 64], ald: [u8; 64], tld: [u8; 64], seht: [u8; 16]) -> Self {
Self {
ald,
tld,
pld,
seht,
}
pub const fn new(pld: [u8; 64], ald: [u8; 64], tld: [u8; 64], tag: [u8; 16]) -> Self {
Self { ald, tld, pld, tag }
}
/// returns false if no hdr found, true otherwise
@@ -93,8 +86,8 @@ impl BootHdrTags {
///
/// # Errors
///
/// This function will return an error if `hdr` is not at least as long as the header specifies
/// in bytes 12-15 or the first 8 bytes do not contain the magic value.
/// This function will return an error if the header could not be found in
/// `img` or is invalid.
pub fn from_se_image<R>(img: &mut R) -> Result<Self>
where
R: Read + Seek,
@@ -125,18 +118,18 @@ impl BootHdrTags {
img.seek(Current(
hdr_head.size.get() as i64
- size_of::<BootHdrHead>() as i64
- BootHdrHead::SEHT_SIZE as i64,
- BootHdrHead::TAG_SIZE as i64,
))?;
// read in the tag
let mut seht = [0u8; BootHdrHead::SEHT_SIZE];
img.read_exact(seht.as_mut_slice())?;
let mut tag = [0u8; BootHdrHead::TAG_SIZE];
img.read_exact(tag.as_mut_slice())?;
Ok(BootHdrTags {
pld: hdr_head.pld,
ald: hdr_head.ald,
tld: hdr_head.tld,
seht,
tag,
})
}
}
@@ -161,7 +154,7 @@ struct BootHdrHead {
assert_size!(BootHdrHead, 0x1A0);
impl BootHdrHead {
const DIGEST_SIZE: usize = 0x40;
const SEHT_SIZE: usize = 0x10;
const TAG_SIZE: usize = 0x10;
}
#[cfg(test)]
@@ -194,7 +187,7 @@ mod tests {
0x8f, 0x9b, 0xe0, 0xa5, 0x49, 0xd8, 0xd7, 0xa9, 0x4a, 0xe7, 0x20, 0xe5, 0xc0, 0x76,
0x0a, 0x82, 0x5d, 0x47, 0x9f, 0xe6, 0x7a, 0xf5,
],
seht: [
tag: [
0x92, 0x30, 0x9d, 0x45, 0x89, 0xb9, 0xa8, 0x5b, 0x42, 0x7f, 0x87, 0x53, 0x17, 0x1d,
0x15, 0x20,
],

View File

@@ -1,18 +1,18 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
use std::fmt::Debug;
/// Trait for securely zeroizing memory.
///
/// To be used with [`Secret`]
/// To be used with [`Confidential`]
pub trait Zeroize {
/// Reliably overwrites the given buffer with zeros,
fn zeroize(&mut self);
}
/* Automatically impl Zeroize for u8 arrays */
// Automatically impl Zeroize for u8 arrays
impl<const COUNT: usize> Zeroize for [u8; COUNT] {
/// Reliably overwrites the given buffer with zeros,
/// by performing a volatile write followed by a memory barrier
@@ -27,7 +27,7 @@ impl Zeroize for Vec<u8> {
/// Reliably overwrites the given buffer with zeros,
/// by overwriting the whole vector's capacity with zeros.
fn zeroize(&mut self) {
//TODO use `volatile_set_memory` when stabilized
// TODO use `volatile_set_memory` when stabilized
let mut dst = self.as_mut_ptr();
for _ in 0..self.capacity() {
// SAFETY:
@@ -44,38 +44,39 @@ impl Zeroize for Vec<u8> {
/// Thin wrapper around an type implementing Zeroize.
///
/// A `Secret` represents a confidential value that must be securely overwritten during drop.
/// A `Confidential` represents a confidential value that must be securely overwritten during drop.
/// Will never leak its wrapped value during [`Debug`]
///
/// ```rust
/// use pv::request::Secret;
/// fn foo(value: Secret<[u8; 2]>) {
/// use pv::request::Confidential;
/// fn foo(value: Confidential<[u8; 2]>) {
/// println!("value: {value:?}");
/// }
/// # fn main() {
/// foo([1,2].into());
/// foo([1, 2].into());
/// // prints:
/// // in debug builds:
/// // value: Secret([1, 2])
/// // value: Confidential([1, 2])
/// // in release builds:
/// // value: Secret(***)
/// // value: Confidential(***)
/// # }
/// ```
#[derive(Clone, PartialEq, Eq, Default)]
pub struct Secret<C: Zeroize>(C);
impl<C: Zeroize> Secret<C> {
pub struct Confidential<C: Zeroize>(C);
impl<C: Zeroize> Confidential<C> {
/// Convert a type into a self overwriting one.
///
/// Prefer using [`Into`]
pub fn new(v: C) -> Self {
Secret(v)
Confidential(v)
}
/// Get a reference to the contained value
pub fn value(&self) -> &C {
&self.0
}
/// Get a imutable reference to the contained value
/// Get an immutable reference to the contained value
///
/// NOTE that modifications to a mutable reference can trigger reallocation.
/// e.g. a [`Vec`] might expand if more space needed. -> preallocate enough space
@@ -85,32 +86,32 @@ impl<C: Zeroize> Secret<C> {
}
}
impl<C: Zeroize + Debug> Debug for Secret<C> {
impl<C: Zeroize + Debug> Debug for Confidential<C> {
#[allow(unreachable_code)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// do NOT leak secrets in production builds
#[cfg(not(debug_assertions))]
return write!(f, "Secret(***)");
return write!(f, "Confidential(***)");
let mut b = f.debug_tuple("Secret");
let mut b = f.debug_tuple("Confidential");
b.field(&self.0);
b.finish()
}
}
impl<C: Zeroize> From<C> for Secret<C> {
fn from(v: C) -> Secret<C> {
Secret(v)
impl<C: Zeroize> From<C> for Confidential<C> {
fn from(v: C) -> Confidential<C> {
Confidential(v)
}
}
impl<C: Zeroize> Zeroize for Secret<C> {
impl<C: Zeroize> Zeroize for Confidential<C> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<C: Zeroize> Drop for Secret<C> {
impl<C: Zeroize> Drop for Confidential<C> {
fn drop(&mut self) {
self.0.zeroize();
}

View File

@@ -2,7 +2,7 @@
//
// Copyright IBM Corp. 2023
use crate::{error::Result, secret::Secret, Error};
use crate::{confidential::Confidential, error::Result, Error};
use openssl::{
derive::Deriver,
ec::{EcGroup, EcKey},
@@ -20,7 +20,7 @@ use std::{convert::TryInto, ops::Range};
/// An AES256-key that will purge itself out of the memory when going out of scope
///
pub type Aes256Key = Secret<[u8; 32]>;
pub type Aes256Key = Confidential<[u8; 32]>;
pub(crate) const AES_256_GCM_TAG_SIZE: usize = 16;
/// Types of symmetric keys, to specify during construction.
@@ -63,21 +63,6 @@ impl SymKey {
}
}
impl Aes256Key {
/// Generates an AES256 key from an digest (hash).
///
/// # Panics
///
/// Panics if `digset` is not 32 bytes long.
fn from_digest(digest: DigestBytes) -> Self {
let key: [u8; 32] = digest
.as_ref()
.try_into()
.expect("Unexpected OpenSSl Error. Sha256 hash not 32 bytes long");
key.into()
}
}
impl From<Aes256Key> for SymKey {
fn from(value: Aes256Key) -> Self {
Self::Aes256(value)
@@ -90,7 +75,7 @@ impl From<Aes256Key> for SymKey {
/// # Errors
///
/// This function will return an OpenSSL error if the key could not be generated.
pub fn hkdf_rfc_5869<const COUNT: usize>(
pub(crate) fn hkdf_rfc_5869<const COUNT: usize>(
md: &MdRef,
ikm: &[u8],
salt: &[u8],
@@ -114,17 +99,20 @@ pub fn hkdf_rfc_5869<const COUNT: usize>(
/// # Errors
///
/// This function will return an error if something went bad in OpenSSL.
pub fn derive_key(k1: &PKey<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
pub(crate) fn derive_key(k1: &PKeyRef<Private>, k2: &PKeyRef<Public>) -> Result<Aes256Key> {
let mut der = Deriver::new(k1)?;
der.set_peer(k2)?;
let mut key = der.derive_to_vec()?;
key.extend([0, 0, 0, 1]);
let secr = Secret::new(key);
let secr = Confidential::new(key);
Ok(Aes256Key::from_digest(hash(
MessageDigest::sha256(),
secr.value(),
)?))
// Panic: does not panic as SHA256 digest is 32 bytes long
Ok(Aes256Key::new(
hash(MessageDigest::sha256(), secr.value())?
.as_ref()
.try_into()
.unwrap(),
))
}
/// Generate a random array.
@@ -132,7 +120,7 @@ pub fn derive_key(k1: &PKey<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
/// # Errors
///
/// This function will return an error if the entropy source fails or is not available.
pub fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
pub(crate) fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
let mut rand = [0; COUNT];
rand_bytes(&mut rand)?;
Ok(rand)
@@ -143,14 +131,15 @@ pub fn random_array<const COUNT: usize>() -> Result<[u8; COUNT]> {
/// # Errors
///
/// This function will return an error if the key could not be generated by OpenSSL.
pub fn gen_ec_key() -> Result<PKey<Private>> {
pub(crate) fn gen_ec_key() -> Result<PKey<Private>> {
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
let key: EcKey<Private> = EcKey::generate(&group)?;
PKey::from_ec_key(key).map_err(Error::Crypto)
}
/// Result type for [`encrypt_aes_gcm`].
pub struct AesGcmResult {
/// Result type for an AES encryption in GCM mode..
#[derive(Debug)]
pub(crate) struct AesGcmResult {
/// The result.
///
/// [`Vec<u8>`] with the following content:
@@ -163,12 +152,14 @@ pub struct AesGcmResult {
/// The position of the encrypted data in [`Self::buf`]
pub encr_range: Range<usize>,
/// The position of the tag in [`Self::buf`]
#[allow(unused)]
// here for completeness
pub tag_range: Range<usize>,
}
impl AesGcmResult {
/// Deconstruct the result to just the resulting data w/o ranges.
pub fn data(self) -> Vec<u8> {
pub(crate) fn data(self) -> Vec<u8> {
let Self { buf, .. } = self;
buf
}
@@ -184,7 +175,12 @@ impl AesGcmResult {
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub fn encrypt_aes_gcm(key: &SymKey, iv: &[u8], aad: &[u8], conf: &[u8]) -> Result<AesGcmResult> {
pub(crate) fn encrypt_aes_gcm(
key: &SymKey,
iv: &[u8],
aad: &[u8],
conf: &[u8],
) -> Result<AesGcmResult> {
let mut tag = vec![0xff; AES_256_GCM_TAG_SIZE];
let encr = match key {
SymKey::Aes256(key) => encrypt_aead(
@@ -239,7 +235,11 @@ pub fn hash(t: MessageDigest, data: &[u8]) -> Result<DigestBytes> {
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the signature.
pub fn sign_msg(skey: &PKeyRef<Private>, dgst: MessageDigest, msg: &[u8]) -> Result<Vec<u8>> {
pub(crate) fn sign_msg(
skey: &PKeyRef<Private>,
dgst: MessageDigest,
msg: &[u8],
) -> Result<Vec<u8>> {
match skey.id() {
Id::EC => {
let mut sgn = Signer::new(dgst, skey)?;
@@ -265,7 +265,7 @@ pub fn sign_msg(skey: &PKeyRef<Private>, dgst: MessageDigest, msg: &[u8]) -> Res
/// # Errors
///
/// This function will return an error if OpenSSL could not compute the signature.
pub fn verify_signature<T: HasPublic>(
pub(crate) fn verify_signature<T: HasPublic>(
skey: &PKeyRef<T>,
dgst: MessageDigest,
msg: &[u8],

View File

@@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
use crate::secret::UserDataType;
/// Result type for this crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -68,6 +70,15 @@ pub enum Error {
)]
AsrcbUserDataSgnFail,
#[error("The provided Host Key Document in '{hkd}' is not in PEM or DER format")]
HkdNotPemOrDer {
hkd: String,
source: openssl::error::ErrorStack,
},
#[error("The provided host key document in {0} contains no certificate!")]
NoHkdInFile(String),
// errors from other crates
#[error(transparent)]
PvCore(#[from] pv_core::Error),
@@ -97,7 +108,7 @@ pub enum HkdVerifyErrorType {
#[error("No valid CRL found")]
NoCrl,
#[error("Host-key document is revoked.")]
HdkRevoked,
HkdRevoked,
#[error("Not enough bits of security. ({0}, {1} expected)")]
SecurityBits(u32, u32),
#[error("Authority Key Id mismatch")]
@@ -126,5 +137,3 @@ macro_rules! bail_hkd_verify {
};
}
pub(crate) use bail_hkd_verify;
use crate::request::uvsecret::UserDataType;

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
#![deny(missing_docs)]
//! pv - library for pv-tools
@@ -8,28 +8,27 @@
//! This library is intened to be used by tools and libraries that
//! are used for creating and managing IBM Secure Execution guests.
//! `pv` provides abstraction layers for encryption, secure memory management,
//! logging, and accessing the uvdevice.
//! and accessing the uvdevice.
//!
//! If you do not need any OpenSSL features use `pv_core`.
//! This crate reexports all symbols from `pv_core`
mod brcb;
mod cli;
mod confidential;
mod crypto;
mod error;
mod req;
mod secret;
mod utils;
mod uvsecret;
mod verify;
/// utility functions for writing TESTS!!!
//hide any test helpers on docs!
// hide any test helpers on docs!
#[doc(hidden)]
#[allow(dead_code)]
pub mod test_utils;
pub use ::utils::assert_size;
pub use ::utils::static_assert;
pub use pv_core::assert_size;
pub use pv_core::static_assert;
const PAGESIZE: usize = 0x1000;
@@ -40,13 +39,8 @@ pub mod uv {
/// Miscellaneous functions and definitions
pub mod misc {
pub use crate::cli::{
get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, CertificateOptions, STDIN,
STDOUT,
};
pub use crate::utils::{read_certs, read_crls, read_private_key};
pub use crate::utils::read_certs;
pub use pv_core::misc::*;
pub use pv_core::PvLogger;
}
pub use crate::error::HkdVerifyErrorType;
@@ -54,42 +48,29 @@ pub use error::{Error, Result};
/// Functionalities to build UV requests
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_gcm, gen_ec_key, AesGcmResult};
pub use crate::crypto::{hash, hkdf_rfc_5869};
pub use crate::crypto::{sign_msg, verify_signature};
pub use crate::crypto::{Aes256Key, SymKey, SymKeyType};
pub use crate::req::{Aad, BinReqValues, Encrypt, Keyslot, ReqEncrCtx, Request};
pub use crate::secret::{Secret, Zeroize};
pub use crate::brcb::BootHdrTags;
pub use crate::confidential::{Confidential, Zeroize};
pub use crate::crypto::{SymKey, SymKeyType};
pub use crate::req::{Keyslot, ReqEncrCtx, Request};
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
/// 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;
pub use openssl::x509;
}
/// Functionalities for creating add-secret requests
pub mod uvsecret {
pub use crate::uvsecret::{
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
user_data::verify_asrcb_and_get_user_data,
};
pub use pv_core::request::uvsecret::AddSecretMagic;
pub use pv_core::request::uvsecret::UserDataType;
}
pub use pv_core::request::RequestMagic;
pub use pv_core::request::*;
}
/// Provides cargo version Info about this crate.
///
/// Produces `pv-crate <version>`
pub const fn crate_info() -> &'static str {
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
/// Functionalities for creating add-secret requests
pub mod secret {
pub use crate::uvsecret::{
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
user_data::verify_asrcb_and_get_user_data,
};
pub use pv_core::secret::*;
}

View File

@@ -2,9 +2,12 @@
//
// Copyright IBM Corp. 2023
use crate::crypto::{AesGcmResult, AES_256_GCM_TAG_SIZE};
use crate::assert_size;
use crate::crypto::{
derive_key, encrypt_aes_gcm, gen_ec_key, random_array, AesGcmResult, SymKey, SymKeyType,
AES_256_GCM_TAG_SIZE,
};
use crate::misc::to_u32;
use crate::request::{derive_key, encrypt_aes_gcm, gen_ec_key, random_array, SymKey, SymKeyType};
use crate::{Error, Result};
use openssl::bn::{BigNum, BigNumContext};
use openssl::ec::{EcGroupRef, EcPointRef};
@@ -14,7 +17,6 @@ use openssl::pkey::{PKey, PKeyRef, Private, Public};
use pv_core::request::{RequestMagic, RequestVersion};
use std::convert::TryInto;
use std::mem::size_of;
use utils::assert_size;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
/// Encrypt a _secret_ using self and a given private key.
@@ -27,7 +29,7 @@ pub trait Encrypt {
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt(&self, secret: &[u8], priv_key: &PKey<Private>) -> Result<Vec<u8>> {
fn encrypt(&self, secret: &[u8], priv_key: &PKeyRef<Private>) -> Result<Vec<u8>> {
let mut res = Vec::with_capacity(80);
self.encrypt_to(secret, priv_key, &mut res)?;
Ok(res)
@@ -42,7 +44,12 @@ pub trait Encrypt {
/// # Errors
///
/// This function will return an error if OpenSSL could not encrypt the secret.
fn encrypt_to(&self, secret: &[u8], priv_key: &PKey<Private>, to: &mut Vec<u8>) -> Result<()>;
fn encrypt_to(
&self,
secret: &[u8],
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()>;
}
/// Types of Authenticated Data
@@ -91,7 +98,7 @@ impl Encrypt for Keyslot {
fn encrypt_to(
&self,
prot_key: &[u8],
priv_key: &PKey<Private>,
priv_key: &PKeyRef<Private>,
to: &mut Vec<u8>,
) -> Result<()> {
let derived_key = derive_key(priv_key, &self.0)?;
@@ -106,7 +113,7 @@ impl Encrypt for Keyslot {
}
}
/// Context used to mange the encryption of requests.
/// Context used to manage the encryption of requests.
/// Intended to be used by [`Request`] implementations
#[derive(Debug)]
pub struct ReqEncrCtx {
@@ -155,12 +162,6 @@ impl ReqEncrCtx {
}
}
///Panics if data does not fit into bin_aad+offs
// #[track_caller]
// pub fn copy_to_bin_aad(_bin_aad: &mut [u8], _aad_offs: usize, _data: &[u8]) {
// todo!();
// }
/// Build the authenticated data for a request.
/// # Returns
/// ```none
@@ -252,7 +253,7 @@ impl ReqEncrCtx {
/// # Errors
///
/// This function will return an error if the data could not be encrypted by OpenSSL.
pub fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AesGcmResult> {
pub(crate) fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AesGcmResult> {
encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf)
}
}

View File

@@ -39,12 +39,6 @@ pub fn get_cert_asset_path<P: AsRef<Path>>(path: P) -> PathBuf {
p
}
pub fn get_cert_asset_path_string(path: &'static str) -> String {
get_cert_asset_path(path)
.into_os_string()
.into_string()
.unwrap()
}
/// TEST ONLY! Load an cert
///
/// panic on errors

View File

@@ -3,7 +3,7 @@
// Copyright IBM Corp. 2023
use crate::{Error, Result};
use openssl::{
pkey::{PKey, Private},
error::ErrorStack,
x509::{X509Crl, X509},
};
@@ -26,29 +26,15 @@ pub fn read_crls(buf: &[u8]) -> Result<Vec<X509Crl>> {
/// # Errors
///
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>> {
pub fn read_certs(buf: &[u8]) -> Result<Vec<X509>, ErrorStack> {
X509::from_der(buf)
.map(|crt| vec![crt])
.or_else(|_| X509::stack_from_pem(buf))
.map_err(Error::Crypto)
}
/// Read+parse the first key from the buffer.
///
/// # Errors
///
/// This function will return an error if the underlying OpenSSL implementation cannot parse `buf`
/// as `DER` or `PEM`.
pub fn read_private_key(buf: &[u8]) -> Result<PKey<Private>> {
PKey::private_key_from_der(buf)
.or_else(|_| PKey::private_key_from_pem(buf))
.map_err(Error::Crypto)
}
#[cfg(test)]
mod tests {
use crate::{get_test_asset, test_utils::*};
use crate::test_utils::*;
#[test]
fn read_crls() {
@@ -69,18 +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 read_private_key() {
let key = get_test_asset!("keys/rsa3072key.pem");
let key = super::read_private_key(key).unwrap();
assert_eq!(key.rsa().unwrap().size(), 384);
}
#[test]
fn read_private_key_fail() {
let key = get_test_asset!("exp/secure_guest.hdr");
let key = super::read_private_key(key);
assert!(key.is_err());
}
}

View File

@@ -5,20 +5,18 @@
use super::user_data::UserData;
use crate::{
assert_size,
crypto::AesGcmResult,
crypto::{hkdf_rfc_5869, AesGcmResult},
misc::Flags,
request::{
hkdf_rfc_5869,
openssl::{
pkey::{PKey, Private, Public},
Md,
},
uvsecret::{ExtSecret, GuestSecret},
Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, Secret,
},
req::{Aad, Keyslot, ReqEncrCtx},
request::{BootHdrTags, Confidential, Request},
secret::{ExtSecret, GuestSecret},
uv::{ConfigUid, UvFlags},
Result,
};
use openssl::{
md::Md,
pkey::{PKey, Private, Public},
};
use pv_core::request::RequestVersion;
use zerocopy::AsBytes;
@@ -47,11 +45,11 @@ impl ReqAuthData {
#[derive(Debug)]
struct ReqConfData {
secret: GuestSecret,
extension_secret: Secret<[u8; 32]>,
extension_secret: Confidential<[u8; 32]>,
}
impl ReqConfData {
fn to_bytes(&self) -> Secret<Vec<u8>> {
fn to_bytes(&self) -> Confidential<Vec<u8>> {
let secret = self.secret.confidential();
let mut v = vec![0; secret.len() + 32];
@@ -110,7 +108,7 @@ impl From<AddSecretVersion> for RequestVersion {
/// Add-secret request Control Block
///
/// An ASRCB wraps a secret to transport it securely to the Ultravisor.
/// An ASRCB wraps a secret to securely transport it to the Ultravisor.
///
/// Layout:
///```none
@@ -159,7 +157,7 @@ impl AddSecretRequest {
) -> Self {
AddSecretRequest {
conf: ReqConfData {
extension_secret: Secret::new([0; 32]),
extension_secret: Confidential::new([0; 32]),
secret,
},
aad: ReqAuthData::new(boot_tags, flags),
@@ -186,7 +184,7 @@ impl AddSecretRequest {
ExtSecret::Derived(cck) => hkdf_rfc_5869(
Md::sha512(),
cck.value(),
self.aad.boot_tags.seht(),
self.aad.boot_tags.tag(),
DER_EXT_SECRET_INFO,
)?
.into(),
@@ -210,8 +208,12 @@ impl AddSecretRequest {
/// - RSA 3072 bit (up to 128 byte message)
///
/// The signature can be verified during the verification of the secret-request on the target machine.
pub fn set_user_data(&mut self, msg: Vec<u8>, skey: Option<PKey<Private>>) -> Result<()> {
self.user_data = UserData::new(skey, msg)?;
pub fn set_user_data<T: Into<Vec<u8>>>(
&mut self,
msg: T,
skey: Option<PKey<Private>>,
) -> Result<()> {
self.user_data = UserData::new(skey, msg.into())?;
Ok(())
}

View File

@@ -2,13 +2,13 @@
//
// Copyright IBM Corp. 2023
use crate::request::Secret;
use crate::request::Confidential;
/// Extension Secret for [`crate::request::uvsecret::AddSecretRequest`]
/// Extension Secret for [`crate::secret::AddSecretRequest`]
#[derive(Debug, Clone)]
pub enum ExtSecret {
/// A bytepattern that must be equal for each request targeting the same SE-guest instance
Simple(Secret<[u8; 32]>), // contains the secret
Simple(Confidential<[u8; 32]>), // contains the secret
/// A secret that is derived from the Customer communication key from the SE-header
Derived(Secret<[u8; 32]>), // contains the cck
Derived(Confidential<[u8; 32]>), // contains the cck
}

View File

@@ -4,15 +4,17 @@
#[allow(unused_imports)] //used for more convenient docstring
use super::asrcb::AddSecretRequest;
use crate::assert_size;
use crate::{
request::{hash, openssl::MessageDigest, random_array, Secret},
crypto::{hash, random_array},
request::Confidential,
Result,
};
use byteorder::BigEndian;
use openssl::hash::MessageDigest;
use pv_core::uv::{ListableSecretType, SecretId};
use serde::{Deserialize, Serialize};
use std::{convert::TryInto, fmt::Display};
use utils::assert_size;
use zerocopy::{AsBytes, U16, U32};
const ASSOC_SECRET_SIZE: usize = 32;
@@ -32,7 +34,7 @@ pub enum GuestSecret {
id: SecretId,
/// Confidential actual association secret (32 bytes)
#[serde(skip)]
secret: Secret<[u8; ASSOC_SECRET_SIZE]>,
secret: Confidential<[u8; ASSOC_SECRET_SIZE]>,
},
}
@@ -166,7 +168,6 @@ mod test {
use super::*;
use serde_test::{assert_tokens, Token};
//todo test GuestSecret::association
#[test]
fn association() {
let secret_value = [0x11; 32];

View File

@@ -1,23 +1,16 @@
use crate::assert_size;
use crate::{
crypto::{sign_msg, verify_signature},
req::BinReqValues,
request::{
openssl::{
pkey::{PKey, Private},
MessageDigest,
},
uvsecret::{AddSecretRequest, AddSecretVersion},
openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public},
RequestMagic,
},
secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType},
Error, Result,
};
use openssl::{
nid::Nid,
pkey::{HasParams, HasPublic, Id, PKeyRef, Public},
};
use pv_core::request::uvsecret::AddSecretMagic;
use pv_core::request::uvsecret::UserDataType;
use utils::assert_size;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
/// User data.

View File

@@ -9,6 +9,7 @@ use openssl::stack::Stack;
use openssl::x509::store::X509Store;
use openssl::x509::{CrlStatus, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, X509};
use openssl_extensions::crl::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
use std::path::Path;
#[cfg(not(test))]
use helper::download_first_crl_from_x509;
@@ -90,7 +91,7 @@ impl HkdVerifier for CertVerifier {
for crl in verified_crls {
match crl.get_by_serial(hkd.serial_number()) {
CrlStatus::NotRevoked => (),
_ => bail_hkd_verify!(HdkRevoked),
_ => bail_hkd_verify!(HkdRevoked),
}
}
debug!("HKD: verified");
@@ -162,7 +163,7 @@ impl CertVerifier {
impl CertVerifier {
/// Create a `CertVerifier`.
///
/// * `cert_paths` - Paths to Cerificates for the chain of trust
/// * `cert_paths` - Paths to certificates for the chain of trust
/// * `crl_paths` - Paths to certificate revocation lists for the chain of trust
/// * `root_ca_path` - Path to the root of trust
/// * `offline` - if set to true the verification process will not try to download CRLs from the
@@ -171,9 +172,9 @@ impl CertVerifier {
///
/// This function will return an error if the chain of trust could not be established.
pub fn new(
cert_paths: &[String],
crl_paths: &[String],
root_ca_path: &Option<String>,
cert_paths: &[&Path],
crl_paths: &[&Path],
root_ca_path: Option<&Path>,
offline: bool,
) -> Result<Self> {
let mut store = helper::store_setup(root_ca_path, crl_paths, cert_paths)?;

View File

@@ -20,8 +20,9 @@ use openssl::{
},
};
use openssl_extensions::akid::{AkidCheckResult, AkidExtension};
use std::path::Path;
use std::str::from_utf8;
use std::{cmp::Ordering, ffi::c_int, usize};
use std::{cmp::Ordering, ffi::c_int};
/// Minimum security level for the keys/certificates used to establish a chain of
/// trust (see https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html
@@ -75,9 +76,9 @@ pub fn verify_crl(crl: &X509CrlRef, issuer: &X509Ref) -> Option<()> {
/// Setup the x509Store such that it can be used it for verifying certificates
pub fn store_setup(
root_ca_path: &Option<String>,
crl_paths: &[String],
cert_w_crl_paths: &[String],
root_ca_path: Option<&Path>,
crl_paths: &[&Path],
cert_w_crl_paths: &[&Path],
) -> Result<X509StoreBuilder> {
let mut x509store = X509StoreBuilder::new()?;
@@ -88,7 +89,7 @@ pub fn store_setup(
for crl in crl_paths {
load_crl_to_store(&mut x509store, crl, true).map_err(|source| Error::X509Load {
path: crl.to_owned(),
path: crl.display().to_string(),
ty: Error::CRL,
source,
})?;
@@ -96,7 +97,7 @@ pub fn store_setup(
for crl in cert_w_crl_paths {
load_crl_to_store(&mut x509store, crl, false).map_err(|source| Error::X509Load {
path: crl.to_owned(),
path: crl.display().to_string(),
ty: Error::CRL,
source,
})?;
@@ -235,7 +236,7 @@ fn get_ibm_z_sign_key(certs: &[X509]) -> Result<X509> {
}
}
fn load_root_ca(path: &str, x509_store: &mut X509StoreBuilder) -> Result<()> {
fn load_root_ca(path: &Path, x509_store: &mut X509StoreBuilder) -> Result<()> {
let lu = x509_store.add_lookup(X509Lookup::<File>::file())?;
// Try to load cert as PEM file
@@ -249,7 +250,7 @@ fn load_root_ca(path: &str, x509_store: &mut X509StoreBuilder) -> Result<()> {
.load_cert_file(path, SslFiletype::ASN1)
.map(|_| ())
.map_err(|source| Error::X509Load {
path: path.to_string(),
path: path.display().to_string(),
ty: Error::CERT,
source,
}),
@@ -258,7 +259,7 @@ fn load_root_ca(path: &str, x509_store: &mut X509StoreBuilder) -> Result<()> {
fn load_crl_to_store(
x509_store: &mut X509StoreBuilder,
path: &str,
path: &Path,
err_out_empty_crl: bool,
) -> std::result::Result<(), openssl::error::ErrorStack> {
let lu = x509_store.add_lookup(X509Lookup::<File>::file())?;
@@ -306,7 +307,7 @@ pub fn x509_dist_points(cert: &X509Ref) -> Vec<String> {
/// Other issues are mapped to Ok(None)
#[cfg(not(test))]
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<openssl::x509::X509Crl>>> {
use crate::misc::read_crls;
use crate::utils::read_crls;
use curl::easy::{Easy2, Handler, WriteError};
use std::time::Duration;
const CRL_TIMEOUT_MAX: Duration = Duration::from_secs(3);

View File

@@ -5,7 +5,7 @@
#![cfg(test)]
use super::{helper, helper::*, *};
use crate::{misc::read_crls, Error, HkdVerifyErrorType::*};
use crate::{utils::read_crls, Error, HkdVerifyErrorType::*};
use openssl::{stack::Stack, x509::X509Crl};
use std::path::Path;
@@ -33,31 +33,31 @@ pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<X509Crl
#[test]
fn store_setup() {
let ibm_str = get_cert_asset_path_string("ibm.crt");
let inter_str = get_cert_asset_path_string("inter.crt");
let ibm_path = get_cert_asset_path("ibm.crt");
let inter_path = get_cert_asset_path("inter.crt");
let store = helper::store_setup(&None, &[], &[ibm_str, inter_str]);
let store = helper::store_setup(None, &[], &[&ibm_path, &inter_path]);
assert!(store.is_ok());
}
#[test]
fn verify_chain_online() {
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let ibm_crt = get_cert_asset_path("ibm.crt");
let inter_crt = get_cert_asset_path("inter_ca.crt");
let root_crt = get_cert_asset_path("root_ca.chained.crt");
let ret = CertVerifier::new(&[ibm_crt, inter_crt], &[], &root_crt.into(), false);
let ret = CertVerifier::new(&[&ibm_crt, &inter_crt], &[], Some(&root_crt), false);
assert!(ret.is_ok(), "CertVerifier::new failed: {ret:?}");
}
#[test]
fn verify_chain_offline() {
let ibm_crt = load_gen_cert("ibm.crt");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let inter_crl = get_cert_asset_path("inter_ca.crl");
let inter_crt = load_gen_cert("inter_ca.crt");
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let root_crt = get_cert_asset_path("root_ca.chained.crt");
let store = helper::store_setup(&Some(root_crt), &[inter_crl], &[])
let store = helper::store_setup(Some(&root_crt), &[&inter_crl], &[])
.unwrap()
.build();
@@ -75,20 +75,20 @@ fn dist_points() {
}
fn verify(offline: bool, ibm_crt: &'static str, ibm_crl: &'static str, hkd: &'static str) {
let root_crt = get_cert_asset_path_string("root_ca.chained.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string(ibm_crt);
let ibm_crl = get_cert_asset_path_string(ibm_crl);
let root_crt = get_cert_asset_path("root_ca.chained.crt");
let inter_crt = get_cert_asset_path("inter_ca.crt");
let inter_crl = get_cert_asset_path("inter_ca.crl");
let ibm_crt = get_cert_asset_path(ibm_crt);
let ibm_crl = get_cert_asset_path(ibm_crl);
let hkd_revoked = load_gen_cert("host_rev.crt");
let hkd_exp = load_gen_cert("host_crt_expired.crt");
let hkd = load_gen_cert(hkd);
let crls = &[ibm_crl, inter_crl];
let crls = &[ibm_crl.as_path(), inter_crl.as_path()];
let verifier = CertVerifier::new(
&[ibm_crt, inter_crt],
&[&ibm_crt, &inter_crt],
if offline { crls } else { &[] },
&Some(root_crt),
Some(&root_crt),
offline,
)
.unwrap();
@@ -98,7 +98,7 @@ fn verify(offline: bool, ibm_crt: &'static str, ibm_crl: &'static str, hkd: &'st
assert!(matches!(
verifier.verify(&hkd_revoked),
Err(Error::HkdVerify(HdkRevoked))
Err(Error::HkdVerify(HkdRevoked))
));
assert!(matches!(

View File

@@ -10,12 +10,12 @@ use pv::{
get_test_asset,
request::{
openssl::pkey::{PKey, Public},
uvsecret::{
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
ExtSecret, GuestSecret,
},
BootHdrTags, ReqEncrCtx, Request, SymKey,
},
secret::{
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
ExtSecret, GuestSecret,
},
test_utils::get_test_keys,
uv::ConfigUid,
Result,

View File

@@ -30,90 +30,80 @@ fn verify_sign_error_slice(exp_raw: &[c_int], obs: Error) {
#[test]
fn verifier_new() {
let root_chn_crt = get_cert_asset_path_string("root_ca.chained.crt");
let root_crt = get_cert_asset_path_string("root_ca.crt");
let inter_crt = get_cert_asset_path_string("inter_ca.crt");
let inter_fake_crt = get_cert_asset_path_string("fake_inter_ca.crt");
let inter_fake_crl = get_cert_asset_path_string("fake_inter_ca.crl");
let inter_crl = get_cert_asset_path_string("inter_ca.crl");
let ibm_crt = get_cert_asset_path_string("ibm.crt");
let ibm_early_crt = get_cert_asset_path_string("ibm_outdated_early.crl");
let ibm_late_crt = get_cert_asset_path_string("ibm_outdated_late.crl");
let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt");
let root_chn_crt = get_cert_asset_path("root_ca.chained.crt");
let root_crt = get_cert_asset_path("root_ca.crt");
let inter_crt = get_cert_asset_path("inter_ca.crt");
let inter_fake_crt = get_cert_asset_path("fake_inter_ca.crt");
let inter_fake_crl = get_cert_asset_path("fake_inter_ca.crl");
let inter_crl = get_cert_asset_path("inter_ca.crl");
let ibm_crt = get_cert_asset_path("ibm.crt");
let ibm_early_crt = get_cert_asset_path("ibm_outdated_early.crl");
let ibm_late_crt = get_cert_asset_path("ibm_outdated_late.crl");
let ibm_rev_crt = get_cert_asset_path("ibm_rev.crt");
// Too many signing keys
let verifier = CertVerifier::new(&[ibm_crt.clone(), ibm_rev_crt.clone()], &[], &None, true);
let verifier = CertVerifier::new(&[&ibm_crt, &ibm_rev_crt], &[], None, true);
assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys))));
// No CRL for each X509
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_crt),
&[&inter_crt, &ibm_crt],
&[&inter_crl],
Some(&root_crt),
false,
);
verify_sign_error(3, verifier.unwrap_err());
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[],
&Some(root_chn_crt.clone()),
false,
);
let verifier = CertVerifier::new(&[&inter_crt, &ibm_crt], &[], Some(&root_chn_crt), false);
verify_sign_error(3, verifier.unwrap_err());
// Wrong intermediate (or ibm key)
let verifier = CertVerifier::new(
&[inter_fake_crt, ibm_crt.clone()],
&[inter_fake_crl],
&Some(root_chn_crt.clone()),
&[&inter_fake_crt, &ibm_crt],
&[&inter_fake_crl],
Some(&root_chn_crt),
true,
);
// Depending on the OpenSSL version different error codes can appear
verify_sign_error_slice(&[20, 30], verifier.unwrap_err());
// Wrong root ca
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&None,
true,
);
let verifier = CertVerifier::new(&[&inter_crt, &ibm_crt], &[&inter_crl], None, true);
verify_sign_error(20, verifier.unwrap_err());
// Correct signing key + intermediate cert
let _verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_crt.clone()],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
&[&inter_crt, &ibm_crt],
&[&inter_crl],
Some(&root_chn_crt),
false,
)
.unwrap();
// No intermediate key
let verifier = CertVerifier::new(&[ibm_crt], &[], &Some(root_chn_crt.clone()), false);
let verifier = CertVerifier::new(&[&ibm_crt], &[], Some(&root_chn_crt), false);
verify_sign_error(20, verifier.unwrap_err());
// IBM Sign outdated
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_early_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
&[&inter_crt, &ibm_early_crt],
&[&inter_crl],
Some(&root_chn_crt),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
let verifier = CertVerifier::new(
&[inter_crt.clone(), ibm_late_crt],
&[inter_crl.clone()],
&Some(root_chn_crt.clone()),
&[&inter_crt, &ibm_late_crt],
&[&inter_crl],
Some(&root_chn_crt),
false,
);
assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey))));
// Revoked
let verifier = CertVerifier::new(
&[inter_crt, ibm_rev_crt],
&[inter_crl],
&Some(root_chn_crt),
&[&inter_crt, &ibm_rev_crt],
&[&inter_crl],
Some(&root_chn_crt),
false,
);
verify_sign_error(23, verifier.unwrap_err());

View File

@@ -10,11 +10,10 @@ license.workspace = true
libc = "0.2.49"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
thiserror = "1.0.33"
utils = {path = "../utils"}
zerocopy = {version = "0.7", features = ["derive"]}
serde = { version = "1.0.139", features = ["derive"]}
byteorder = "1.3"
[dev-dependencies]
lazy_static = "1.4.0"
serde_test = "1"
serde_test = "1.0.139"
lazy_static = "1.1"

View File

@@ -8,19 +8,16 @@
//! 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.
//! 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 tmpfile;
mod utils;
mod uvdevice;
mod uvsecret;
pub use crate::log::PvLogger;
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
/// Miscellaneous functions and definitions
@@ -30,27 +27,17 @@ pub mod misc {
pub use crate::utils::{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};
pub use crate::tmpfile::TemporaryDirectory;
}
/// Definitions and functions for interacting with the Ultravisor
pub mod uv {
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
pub use crate::uvdevice::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
pub use crate::uvdevice::{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
@@ -73,14 +60,11 @@ pub mod request {
}
}
/// Provides cargo version Info about this crate.
///
/// Produces `pv_core-crate <version>`
pub const fn crate_info() -> &'static str {
concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION"))
/// Functionalities for reading add-secret requests
pub mod secret {
pub use crate::uvsecret::AddSecretMagic;
pub use crate::uvsecret::UserDataType;
}
// Internal definitions/ imports
const PAGESIZE: usize = 0x1000;
use ::utils::assert_size;
use ::utils::static_assert;

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
macro_rules! path_to_str {
($path: expr) => {
@@ -28,14 +28,35 @@ macro_rules! bail_spec {
}
pub(crate) use bail_spec;
#[doc(hidden)]
/// Asserts a constant expression evaluates to `true`.
///
/// If the expression is not evaluated to `true` the compilation will fail.
#[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,
}
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 pv_core::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);
};
}

View File

@@ -23,7 +23,7 @@ use test::mock_libc::ioctl;
mod ffi;
mod info;
mod test;
pub use ffi::uv_ioctl;
pub(crate) use ffi::uv_ioctl;
pub mod secret;
pub mod secret_list;
@@ -82,6 +82,11 @@ fn rc_fmt<C: UvCmd>(rc: u16, rrc: u16, cmd: &mut C) -> &'static str {
}
/// Ultravisor Command.
///
/// Implementers provide information on the specific Ultravisor command metadata and content.
/// API users do not need to interact directly with any functions provided by this trait and refer
/// to the specialized access and tweaking functionalities of the specivic command.
pub trait UvCmd {
/// The UV IOCTL number of the UV call
const UV_IOCTL_NR: u8;

View File

@@ -107,7 +107,7 @@ impl uvio_attest {
}
/// corresponds to the UV_IOCTL macro
pub const fn uv_ioctl(nr: u8) -> u64 {
pub(crate) const fn uv_ioctl(nr: u8) -> u64 {
iowr(UVIO_TYPE_UVC, nr, std::mem::size_of::<uvio_ioctl_cb>())
}
static_assert!(uv_ioctl(UVIO_IOCTL_ATT_NR) == 0xc0407501);

View File

@@ -5,7 +5,7 @@
use super::ffi::{self, uvio_uvdev_info};
use crate::{
misc::{Flags, Lsb0Flags64},
uv::{uv_ioctl, UvCmd, UvDevice},
uv::{UvCmd, UvDevice},
Result,
};
use std::fmt::Display;
@@ -22,8 +22,8 @@ use zerocopy::{AsBytes, FromZeroes};
/// If the bit is set in both, `supp_uvio_cmds` and `supp_uv_cmds`,
/// the uvdevice and the Ultravisor support that call.
///
/// Note that bit 0 ([`UvDevice::INFO_NR`]) is always zero for `supp_uv_cmds`
/// as there is no corresponding UV-call.
/// Note that bit 0 is always zero for `supp_uv_cmds`
/// as there is no corresponding Info UV-call.
///
#[derive(Debug)]
pub struct UvDeviceInfo {
@@ -38,7 +38,7 @@ impl UvDeviceInfo {
///
/// This function will return an error if the ioctl fails and the error code is not
/// [`libc::ENOTTY`].
/// `ENOTTY` is most likely because the uvdevice does not support the info IOCTL.
/// `ENOTTY` is most likely because older uvdevices does not support the info IOCTL.
/// In that case one can safely assume that the device only supports the Attestation IOCTL.
/// Therefore this is what this function returns IOCTL support for Attestation and _Data not
/// available_ for the UV Attestation facility.

View File

@@ -6,8 +6,9 @@ use super::ffi;
use crate::{
assert_size,
misc::to_u16,
request::{uvsecret::AddSecretMagic, MagicValue},
uv::{uv_ioctl, UvCmd, UvDevice},
request::MagicValue,
uv::{UvCmd, UvDevice},
uvsecret::AddSecretMagic,
Error, Result, PAGESIZE,
};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};

View File

@@ -2,6 +2,8 @@
//
// Copyright IBM Corp. 2024
use crate::assert_size;
use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Deserialize, Serialize, Serializer};
use std::{
@@ -10,11 +12,8 @@ use std::{
slice::Iter,
vec::IntoIter,
};
use utils::assert_size;
use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32};
use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
/// The 32 byte long ID of an UV secret
///
/// (de)serializes itself in/from a hex-string
@@ -94,7 +93,7 @@ impl SecretEntry {
/// Create a new entry for a [`SecretList`].
///
/// The content of this entry will very liekly not represent the status of the guest in the
/// The content of this entry will very likely 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: SecretId, secret_len: u32) -> Self {
Self {
@@ -180,7 +179,7 @@ impl FromIterator<SecretEntry> for SecretList {
impl SecretList {
/// Creates a new SecretList.
///
/// The content of this list will very liekly not represent the status of the guest in the
/// The content of this list will very likely 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<SecretEntry>) -> Self {
Self {

View File

@@ -2,6 +2,7 @@
//
// Copyright IBM Corp. 2023
use crate::{assert_size, static_assert};
use crate::{
misc::to_u16,
request::{MagicValue, RequestMagic},
@@ -14,14 +15,13 @@ use std::{
io::{Cursor, Read, Seek, Write},
mem::size_of,
};
use utils::{assert_size, static_assert};
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::secret::AddSecretMagic;
/// # use pv_core::request::MagicValue;
/// # fn main() {
/// # let magic =
@@ -56,7 +56,7 @@ impl AddSecretMagic {
/// Try to convert from a byte slice.
///
/// Retuns [`None`] if the byte slice does not contain a valid magic value variant.
/// Returns [`None`] if the byte slice does not contain a valid magic value variant.
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
if !Self::starts_with_magic(bytes) || bytes.len() < size_of::<AddSecretMagic>() {
return Err(Error::NoAsrcb);
@@ -70,8 +70,8 @@ impl AddSecretMagic {
/// Returns the [`UserDataType`] of this [`AddSecretMagic`].
pub fn kind(&self) -> UserDataType {
// Panic: Will never panic. The value is cheched during construcion of the object for
// beeing one of the enum values.
// Panic: Will never panic. The value is checked during construction of
// the object for being one of the enum values.
self.kind.get().try_into().unwrap()
}
}
@@ -153,10 +153,8 @@ impl From<UserDataType> for AddSecretMagic {
#[cfg(test)]
mod test {
use crate::{
request::{
uvsecret::{AddSecretMagic, UserDataType},
MagicValue,
},
request::MagicValue,
secret::{AddSecretMagic, UserDataType},
Error,
};

View File

@@ -10,10 +10,9 @@ license.workspace = true
clap = { version ="4.1", features = ["derive", "wrap_help"]}
lazy_static = "1.1"
openssl = { version = "0.10" }
openssl-sys = { version = "0.9" }
pv_core = { path = "../pv_core"}
rand = "0.8"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
regex = "1.7"
serde = { version = "1.0.139", features = ["derive"] }
serde_yaml = "0.9"
utils = { path = "../utils" }

View File

@@ -199,7 +199,7 @@ impl Drop for LockFile {
#[cfg(test)]
mod tests {
use super::*;
use pv_core::misc::TemporaryDirectory;
use utils::TemporaryDirectory;
// Only very simple tests

View File

@@ -18,7 +18,7 @@ use config::{ApConfigEntry, ApConfigList};
use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
use pv_core::uv::{ListableSecretType, SecretList};
use std::process::ExitCode;
use utils::release_string;
use utils::print_version;
/// Simple macro for
/// if Cli::verbose() {
@@ -58,11 +58,7 @@ macro_rules! on_error_print_and_exit {
fn main() -> ExitCode {
// handle version option
if cli::ARGS.version {
println!(
"{} version {}\nCopyright IBM Corp. 2023",
env!("CARGO_PKG_NAME"),
release_string!()
);
print_version!(0, "2023");
return ExitCode::SUCCESS;
}

View File

@@ -6,7 +6,7 @@ license.workspace = true
[dependencies]
anyhow = { version = "1.0.70", features = ["std"] }
clap = { version ="4", features = ["derive", "wrap_help"]}
clap = { version ="4.1", features = ["derive", "wrap_help"]}
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
serde_yaml = "0.9"

View File

@@ -3,8 +3,7 @@
// Copyright IBM Corp. 2023
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
use pv::misc::CertificateOptions;
use pv::misc::STDOUT;
use utils::{CertificateOptions, STDOUT};
/// Manage secrets for IBM Secure Execution guests.
///

View File

@@ -5,8 +5,8 @@
use crate::cli::AddSecretOpt;
use anyhow::{Context, Result};
use log::warn;
use pv::misc::get_reader_from_cli_file_arg;
use pv::uv::{AddCmd, UvDevice};
use utils::get_reader_from_cli_file_arg;
/// Do an Add Secret UVC
pub fn add(opt: &AddSecretOpt) -> Result<()> {

View File

@@ -1,23 +1,24 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt};
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{anyhow, bail, Context, Error, Result};
use log::{debug, info, trace, warn};
use pv::{
misc::{
get_writer_from_cli_file_arg, open_file, parse_hex, pv_guest_bit_set, read_certs,
read_exact_file, read_file, read_private_key, try_parse_u128, try_parse_u64, write,
open_file, parse_hex, pv_guest_bit_set, read_exact_file, read_file, try_parse_u128,
try_parse_u64, write,
},
request::{
openssl::pkey::{PKey, Public},
uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
BootHdrTags, HkdVerifier, ReqEncrCtx, Request, SymKeyType,
openssl::pkey::{PKey, Private},
BootHdrTags, ReqEncrCtx, Request, SymKeyType,
},
secret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
uv::ConfigUid,
};
use serde_yaml::Value;
use utils::get_writer_from_cli_file_arg;
fn write_out<D: AsRef<[u8]>>(path: &str, data: D, ctx: &str) -> pv::Result<()> {
let mut wr = get_writer_from_cli_file_arg(path)?;
@@ -40,8 +41,8 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> {
debug!("Generated Add-secret request");
// Add host-key documents
let verifier = opt.certificate_args.verifier()?;
read_and_verify_hkds(&opt.certificate_args.host_key_documents, verifier)?
opt.certificate_args
.get_verified_hkds("secret")?
.into_iter()
.for_each(|k| asrcb.add_hostkey(k));
@@ -57,6 +58,13 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> {
write_secret(&opt.secret, &asrcb)
}
/// Read+parse the first key from the buffer.
fn read_private_key(buf: &[u8]) -> Result<PKey<Private>> {
PKey::private_key_from_der(buf)
.or_else(|_| PKey::private_key_from_pem(buf))
.map_err(Error::new)
}
/// Set-up the `add-secret request` from command-line arguments
fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
debug!("Build add-secret request");
@@ -191,38 +199,6 @@ fn read_cuid(asrcb: &mut AddSecretRequest, opt: &CreateSecretOpt) -> Result<()>
Ok(())
}
/// reads HKDs into memory, verifies them with the provided HKD verifier.
/// returns list of public keys or Err
/// Aborts on first error
fn read_and_verify_hkds(
hkds: &Vec<String>,
verifier: Box<dyn HkdVerifier>,
) -> Result<Vec<PKey<Public>>> {
let mut res = Vec::with_capacity(hkds.len());
for hkd in hkds {
let hk = read_file(hkd, "host-key document")?;
let certs = read_certs(&hk).with_context(|| {
format!("The provided Host Key Document in '{hkd}' is not in PEM or DER format")
})?;
if certs.is_empty() {
let msg = format!(
"The provided host key document in {} contains no certificate!",
hkd
);
return Err(anyhow!(msg));
}
if certs.len() > 1 {
warn!("The host key document in '{hkd}' contains more than one certificate! Only the first certificate will be used.")
}
// len is >= 1 -> unwrap will succeed
let c = certs.first().unwrap();
verifier.verify(c)?;
res.push(c.public_key()?);
info!("Use host-key document at '{hkd}'");
}
Ok(res)
}
/// Write the generated secret (if any) to the specified output stream
fn write_secret(secret: &AddSecretType, asrcb: &AddSecretRequest) -> Result<()> {
if let AddSecretType::Association {
@@ -262,3 +238,21 @@ fn write_secret(secret: &AddSecretType, asrcb: &AddSecretRequest) -> Result<()>
};
Ok(())
}
#[cfg(test)]
mod test {
#[test]
fn read_private_key() {
let key = include_bytes!("../../../pv/tests/assets/keys/rsa3072key.pem");
let key = super::read_private_key(key).unwrap();
assert_eq!(key.rsa().unwrap().size(), 384);
}
#[test]
fn read_private_key_fail() {
let key = include_bytes!("create.rs");
let key = super::read_private_key(key);
assert!(key.is_err());
}
}

View File

@@ -5,10 +5,8 @@
use crate::cli::{ListSecretOpt, ListSecretOutputType};
use anyhow::{Context, Result};
use log::warn;
use pv::{
misc::{get_writer_from_cli_file_arg, STDOUT},
uv::{ListCmd, SecretList, UvDevice, UvcSuccess},
};
use pv::uv::{ListCmd, SecretList, UvDevice, UvcSuccess};
use utils::{get_writer_from_cli_file_arg, STDOUT};
/// Do a List Secrets UVC
pub fn list(opt: &ListSecretOpt) -> Result<()> {

View File

@@ -1,14 +1,12 @@
use crate::cli::VerifyOpt;
use anyhow::{anyhow, Context, Result};
use log::warn;
use pv::misc::{read_certs, read_file};
use pv::{
misc::{get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, read_certs, read_file},
request::{
openssl::pkey::{PKey, Public},
uvsecret::verify_asrcb_and_get_user_data,
},
request::openssl::pkey::{PKey, Public},
secret::verify_asrcb_and_get_user_data,
};
use crate::cli::VerifyOpt;
use utils::{get_reader_from_cli_file_arg, get_writer_from_cli_file_arg};
/// read the content of a DER or PEM x509 and return the public key
fn read_sgn_key(path: &str) -> Result<PKey<Public>> {

View File

@@ -1,17 +1,15 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
mod cli;
mod cmd;
use clap::CommandFactory;
use clap::Parser;
use clap::{CommandFactory, Parser};
use cli::{CliOptions, Command};
use log::trace;
use pv::misc::PvLogger;
use std::process::ExitCode;
use utils::release_string;
use utils::{print_cli_error, print_error, print_version, PvLogger};
use crate::cli::validate_cli;
@@ -19,44 +17,8 @@ static LOGGER: PvLogger = PvLogger;
static EXIT_LOGGER: u8 = 3;
const FEATURES: &[&[&str]] = &[cmd::CMD_FN, cmd::UV_CMD_FN];
fn print_error(e: anyhow::Error, verbosity: u8) -> ExitCode {
if verbosity > 0 {
// Debug formatter also prints the whole error stack
// So only print it when on verbose
eprintln!("error: {e:?}")
} else {
eprintln!("error: {e}")
};
ExitCode::FAILURE
}
fn print_cli_error(e: clap::Error) -> ExitCode {
let ret = if e.use_stderr() {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
};
//Ignore any errors during printing of the error
let _ = e.format(&mut CliOptions::command()).print();
ret
}
fn print_version(verbosity: u8) -> anyhow::Result<()> {
println!(
"{} version {}\nCopyright IBM Corp. 2023",
env!("CARGO_PKG_NAME"),
release_string!()
);
if verbosity > 0 {
FEATURES.concat().iter().for_each(|f| print!("{f} "));
println!("(compiled)");
println!(
"\n{}-crate {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
);
println!("{}", pv::crate_info());
}
print_version!(verbosity, "2024", FEATURES.concat());
Ok(())
}
@@ -64,9 +26,9 @@ fn main() -> ExitCode {
let cli: CliOptions = match CliOptions::try_parse() {
Ok(cli) => match validate_cli(&cli) {
Ok(_) => cli,
Err(e) => return print_cli_error(e),
Err(e) => return print_cli_error(e, CliOptions::command()),
},
Err(e) => return print_cli_error(e),
Err(e) => return print_cli_error(e, CliOptions::command()),
};
// set up logger/std(out,err)
@@ -97,6 +59,6 @@ fn main() -> ExitCode {
match res {
Ok(_) => ExitCode::SUCCESS,
Err(e) => print_error(e, cli.verbose),
Err(e) => print_error(&e, cli.verbose),
}
}

View File

@@ -3,3 +3,9 @@ name = "utils"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
clap = { version ="4.1", features = ["derive", "wrap_help"] }
libc = "0.2.49"
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
pv = { path = "../pv" }

View File

@@ -1,11 +1,21 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
// Copyright IBM Corp. 2023, 2024
use crate::misc::{create_file, open_file};
use crate::Result;
use clap::{ArgGroup, Args, ValueHint};
use clap::{ArgGroup, Args, Command, ValueHint};
use log::{info, warn};
use pv::misc::read_file;
use pv::{
misc::{create_file, open_file, read_certs},
request::{
openssl::pkey::{PKey, Public},
HkdVerifier,
},
Error, Result,
};
use std::io::{Read, Write};
use std::path::Path;
use std::process::ExitCode;
/// CLI Argument collection for handling certificates.
#[derive(Args, Debug, PartialEq, Eq, Default)]
@@ -34,7 +44,7 @@ pub struct CertificateOptions {
#[arg(long)]
pub no_verify: bool,
/// Use FILE as a certificate to verify the host key or keys.
/// Use FILE as a certificate to verify the host-key or keys.
///
/// The certificates are used to establish a chain of trust for the verification
/// of the host-key documents. Specify this option twice to specify the IBM Z signing key and
@@ -79,26 +89,63 @@ pub struct CertificateOptions {
impl CertificateOptions {
/// Returns the verifier of this [`CertificateOptions`] based on the given CLI options.
///
/// - `protectee`: what you want to create. e.g. add-secret request or SE-image
///
/// # Errors
///
/// This function will return an error if [`crate::request::HkdVerifier`] cannot be created.
pub fn verifier(&self) -> Result<Box<dyn crate::verify::HkdVerifier>> {
use crate::verify::{CertVerifier, NoVerifyHkd};
fn verifier(&self, protectee: &'static str) -> Result<Box<dyn HkdVerifier>> {
use pv::request::{CertVerifier, NoVerifyHkd};
match self.no_verify {
true => {
log::warn!(
"Host-key document verification is disabled. The secret may not be protected."
"Host-key document verification is disabled. The {protectee} may not be protected."
);
Ok(Box::new(NoVerifyHkd))
}
false => Ok(Box::new(CertVerifier::new(
&self.certs,
&self.crls,
&self.root_ca,
&self.certs.iter().map(Path::new).collect::<Vec<_>>(),
&self.crls.iter().map(Path::new).collect::<Vec<_>>(),
self.root_ca.as_ref().map(Path::new),
self.offline,
)?)),
}
}
/// Read the host-keys specified and verifies them if required
///
/// - `protectee`: what you want to create. e.g. add-secret request or SE-image
///
/// # Error
/// Returns an error if something went wrong during parsing the HKDs, the verification chain
/// could not built, or when the verification
/// failed.
pub fn get_verified_hkds(&self, protectee: &'static str) -> Result<Vec<PKey<Public>>> {
let hkds = &self.host_key_documents;
let verifier = self.verifier(protectee)?;
let mut res = Vec::with_capacity(hkds.len());
for hkd in hkds {
let hk = read_file(hkd, "host-key document")?;
let certs = read_certs(&hk).map_err(|source| Error::HkdNotPemOrDer {
hkd: hkd.to_string(),
source,
})?;
if certs.is_empty() {
return Err(Error::NoHkdInFile(hkd.to_string()));
}
if certs.len() != 1 {
warn!("The host-key document in '{hkd}' contains more than one certificate!")
}
// Panic: len is == 1 -> unwrap will succeed/not panic
let c = certs.first().unwrap();
verifier.verify(c)?;
res.push(c.public_key()?);
info!("Use host-key document at '{hkd}'");
}
Ok(res)
}
}
/// stdout
@@ -124,6 +171,34 @@ pub fn get_reader_from_cli_file_arg(path: &str) -> Result<Box<dyn Read>> {
}
}
/// Print an error that occured during CLI parsing
pub fn print_cli_error(e: clap::Error, mut cmd: Command) -> ExitCode {
let ret = if e.use_stderr() {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
};
// Ignore any errors during printing of the error
let _ = e.format(&mut cmd).print();
ret
}
/// Print an error to stderr
pub fn print_error<E>(e: &E, verbosity: u8) -> ExitCode
where
// Error trait is not required, but here to limit the usage to errors
E: AsRef<dyn std::error::Error> + std::fmt::Debug + std::fmt::Display,
{
if verbosity > 0 {
// Debug formatter also prints the whole error stack
// So only print it when on verbose
eprintln!("error: {e:?}")
} else {
eprintln!("error: {e}")
};
ExitCode::FAILURE
}
#[cfg(test)]
mod test {
use clap::Parser;

View File

@@ -2,7 +2,17 @@
//! Utils for s390-tools written in rust.
//! Not intened to be used outside of s390-tools.
//!
//! Copyright IBM Corp. 2023
//! Copyright IBM Corp. 2023, 2024
mod cli;
mod log;
mod tmpfile;
pub use crate::cli::CertificateOptions;
pub use crate::cli::{get_reader_from_cli_file_arg, get_writer_from_cli_file_arg};
pub use crate::cli::{print_cli_error, print_error};
pub use crate::cli::{STDIN, STDOUT};
pub use crate::log::PvLogger;
pub use crate::tmpfile::TemporaryDirectory;
/// Get the s390-tools release string
///
@@ -28,6 +38,33 @@ macro_rules! release_string {
}};
}
#[macro_export]
/// Print the version to stdout
///
/// verbosity: integer if >0 more and more details printed
/// feat: (optional) list of features
/// rel_str: a string containig the release name
macro_rules! print_version {
($verbosity: expr, $year: expr $( ,$feat: expr)?) => {{
println!(
"{} version {}\nCopyright IBM Corp. {}",
env!("CARGO_PKG_NAME"),
$crate::release_string!(),
$year,
);
if $verbosity > 0 {
$($feat.iter().for_each(|f| print!("{f} ")); println!("(compiled)");)?
}
if $verbosity > 1 {
println!(
"\n{}-crate {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
);
}
}};
}
/// Asserts a constant expression evaluates to `true`.
///
/// If the expression is not evaluated to `true` the compilation will fail.

View File

@@ -44,5 +44,6 @@ impl Log for PvLogger {
}
}
}
fn flush(&self) {}
}

View File

@@ -44,7 +44,7 @@ impl TemporaryDirectory {
/// An error is returned if the temporary directory could not be created.
pub fn new<P: AsRef<Path>>(prefix: P) -> Result<Self, std::io::Error> {
let mut template = prefix.as_ref().to_owned();
let mut template_os_string = template.as_mut_os_string();
let template_os_string = template.as_mut_os_string();
template_os_string.push("XXXXXX");
let temp_dir = mkdtemp(template_os_string)?;
@@ -64,7 +64,7 @@ impl TemporaryDirectory {
}
/// Removes the created temporary directory and it's contents.
pub fn close(mut self) -> std::io::Result<()> {
pub fn close(self) -> std::io::Result<()> {
let ret = std::fs::remove_dir_all(&self.path);
self.forget();
ret
@@ -85,8 +85,6 @@ impl Drop for TemporaryDirectory {
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{mkdtemp, TemporaryDirectory};
#[test]
@@ -97,15 +95,15 @@ mod tests {
let template = "yayXXXXXX";
let err = mkdtemp(template_inv_not_last_characters).expect_err("invalid template");
let err = mkdtemp(template_inv_too_less_x).expect_err("invalid template");
let err =
let _err = mkdtemp(template_inv_not_last_characters).expect_err("invalid template");
let _err = mkdtemp(template_inv_too_less_x).expect_err("invalid template");
let _err =
mkdtemp(template_inv_path_does_not_exist).expect_err("path does not exist template");
let path = mkdtemp(template).expect("mkdtemp should work");
assert!(path.exists());
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
std::fs::remove_dir(path);
std::fs::remove_dir(path).unwrap();
}
#[test]
@@ -115,7 +113,7 @@ mod tests {
assert!(path.exists());
// Test that close removes the directory
temp_dir.close();
temp_dir.close().unwrap();
assert!(!path.exists());
}
@@ -139,7 +137,7 @@ mod tests {
assert!(path.as_os_str().to_str().expect("works").starts_with("yay"));
// Test that close() removes the directory
temp_dir.close();
temp_dir.close().unwrap();
assert!(!path.exists());
}