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
+15 -22
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,
],
-166
View File
@@ -1,166 +0,0 @@
// SPDX-License-Identifier: MIT
//
// 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.
#[derive(Args, Debug, PartialEq, Eq, Default)]
#[command(
group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])),
)]
pub struct CertificateOptions {
/// Use FILE as a host-key document.
///
/// Can be specified multiple times and must be used at least once.
#[arg(
short = 'k',
long = "host-key-document",
value_name = "FILE",
required = true,
value_hint = ValueHint::FilePath,
use_value_delimiter = true,
value_delimiter = ',',
)]
pub host_key_documents: Vec<String>,
/// Disable the host-key document verification.
///
/// Does not require the host-key documents to be valid.
/// Do not use for a production request unless you verified the host-key document beforehand.
#[arg(long)]
pub no_verify: bool,
/// 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
/// the intermediate CA certificate (signed by the root CA).
#[arg(
short= 'C',
long = "cert",
value_name = "FILE",
alias("crt"),
value_hint = ValueHint::FilePath,
use_value_delimiter = true,
value_delimiter = ',',
)]
pub certs: Vec<String>,
/// Use FILE as a certificate revocation list.
///
/// The list is used to check whether a certificate of the chain of
/// trust is revoked. Specify this option multiple times to use multiple CRLs.
#[arg(
long = "crl",
requires("certs"),
value_name = "FILE",
value_hint = ValueHint::FilePath,
use_value_delimiter = true,
value_delimiter = ',',
)]
pub crls: Vec<String>,
/// Make no attempt to download CRLs.
#[arg(long, requires("certs"))]
pub offline: bool,
/// Use FILE as the root-CA certificate for the verification.
///
/// If omitted, the system wide-root CAs installed on the system are used.
/// Use this only if you trust the specified certificate.
#[arg(long, requires("certs"))]
pub root_ca: Option<String>,
}
impl CertificateOptions {
/// Returns the verifier of this [`CertificateOptions`] based on the given CLI options.
///
/// # 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};
match self.no_verify {
true => {
log::warn!(
"Host-key document verification is disabled. The secret may not be protected."
);
Ok(Box::new(NoVerifyHkd))
}
false => Ok(Box::new(CertVerifier::new(
&self.certs,
&self.crls,
&self.root_ca,
self.offline,
)?)),
}
}
}
/// stdout
pub const STDOUT: &str = "-";
/// stdin
pub const STDIN: &str = "-";
/// Converts an argument value into a Writer.
pub fn get_writer_from_cli_file_arg(path: &str) -> Result<Box<dyn Write>> {
if path == STDOUT {
Ok(Box::new(std::io::stdout()))
} else {
Ok(Box::new(create_file(path)?))
}
}
/// Converts an argument value into a Reader.
pub fn get_reader_from_cli_file_arg(path: &str) -> Result<Box<dyn Read>> {
if path == STDIN {
Ok(Box::new(std::io::stdin()))
} else {
Ok(Box::new(open_file(path)?))
}
}
#[cfg(test)]
mod test {
use clap::Parser;
use super::*;
#[test]
#[rustfmt::skip]
fn cli_args() {
//Verify only that some arguments are optional, we do not want to test clap, only the
//configuration
let valid_args = [vec!["pgr", "-k", "hkd.crt", "--no-verify"], vec!["pgr", "-k", "hkd.crt", "--crt", "abc.crt"]];
// Test for the minimal amount of flags to yield an invalid combination
let invalid_args = [
vec!["pgr", "-k", "hkd.crt"],
vec!["pgr", "--no-verify", "--crt", "abc.crt"],
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--offline"],
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--crl", "abc.crl"],
vec!["pgr", "--no-verify", "--crt", "abc.crt", "--root-ca", "root.crt"],
vec!["pgr", "--offline"],
vec!["pgr", "--crl", "abc.crl"],
vec!["pgr", "--root-ca", "root.crt"],
];
#[derive(Parser, Debug)]
struct TestParser {
#[command(flatten)]
pub verify_args: CertificateOptions,
}
for arg in valid_args {
let res = TestParser::try_parse_from(&arg);
assert!(res.is_ok());
}
for arg in invalid_args {
let res = TestParser::try_parse_from(&arg);
assert!(res.is_err());
}
}
}
@@ -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();
}
+32 -32
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],
+13 -4
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;
+22 -41
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::*;
}
+15 -14
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)
}
}
-6
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
+3 -31
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());
}
}
+19 -17
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(())
}
+4 -4
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
}
+5 -4
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];
+5 -12
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.
+6 -5
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)?;
+11 -10
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);
+20 -20
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!(