From c6f621d0dc8b7a8934d728df34239b959cabc718 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 4 May 2023 14:23:31 +0200 Subject: [PATCH] rust: Add library for pv tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `pv` crate that bundles useful functions and structs for creating requests like `Attestation`, `Add Secret`, or even `Boot` a.k.a. Secure Execution Image. Note pv includes a subcrate `openssl_extensions` that (temporarily) bundles some needed `openssl-rust` functionalities that are not upstream yet. The plan is to remove these, when they become upstream. The pv crate has multiple features: * request - code to generate requests * uvsecret - code to access the UV-secret api with request enabled also generating requests is possible Signed-off-by: Steffen Eiden Acked-by: Jan Höppner Acked-by: Marc Hartmayer Signed-off-by: Jan Höppner --- CHANGELOG.md | 1 + rust/pv/Cargo.toml | 36 + rust/pv/openssl_extensions/Cargo.toml | 12 + rust/pv/openssl_extensions/build.rs | 45 ++ rust/pv/openssl_extensions/src/akid.rs | 120 +++ rust/pv/openssl_extensions/src/crl.rs | 128 +++ rust/pv/openssl_extensions/src/lib.rs | 26 + .../openssl_extensions/src/stackable_crl.rs | 142 ++++ rust/pv/openssl_extensions/tests/assets | 1 + rust/pv/src/brcb.rs | 247 ++++++ rust/pv/src/cli.rs | 180 +++++ rust/pv/src/crypto.rs | 325 ++++++++ rust/pv/src/error.rs | 196 +++++ rust/pv/src/lib.rs | 206 +++++ rust/pv/src/log.rs | 48 ++ rust/pv/src/req.rs | 528 +++++++++++++ rust/pv/src/secret.rs | 117 +++ rust/pv/src/test_utils.rs | 120 +++ rust/pv/src/utils.rs | 658 ++++++++++++++++ rust/pv/src/uvdevice.rs | 208 +++++ rust/pv/src/uvdevice/ffi.rs | 128 +++ rust/pv/src/uvdevice/info.rs | 130 ++++ rust/pv/src/uvdevice/test.rs | 236 ++++++ rust/pv/src/uvsecret.rs | 80 ++ rust/pv/src/uvsecret/asrcb.rs | 314 ++++++++ rust/pv/src/uvsecret/ext_secret.rs | 16 + rust/pv/src/uvsecret/guest_secret.rs | 146 ++++ rust/pv/src/uvsecret/secret_list.rs | 224 ++++++ rust/pv/src/uvsecret/uvc.rs | 124 +++ rust/pv/src/verify.rs | 158 ++++ rust/pv/src/verify/helper.rs | 518 +++++++++++++ rust/pv/src/verify/test.rs | 282 +++++++ rust/pv/tests/add_secret_request.rs | 164 ++++ rust/pv/tests/assets/cert/der.crl | Bin 0 -> 859 bytes rust/pv/tests/assets/cert/der.crt | Bin 0 -> 1743 bytes rust/pv/tests/assets/cert/fake_inter_ca.crl | 19 + rust/pv/tests/assets/cert/fake_inter_ca.crt | 38 + rust/pv/tests/assets/cert/gen/.gitignore | 4 + rust/pv/tests/assets/cert/gen/Makefile | 7 + rust/pv/tests/assets/cert/gen/clean.sh | 2 + rust/pv/tests/assets/cert/gen/create_certs.py | 733 ++++++++++++++++++ rust/pv/tests/assets/cert/gen/create_certs.sh | 17 + .../pv/tests/assets/cert/gen/requirements.txt | 1 + rust/pv/tests/assets/cert/host.crt | 30 + .../pv/tests/assets/cert/host_crt_expired.crt | 30 + .../assets/cert/host_invalid_signing_key.crt | 30 + rust/pv/tests/assets/cert/host_rev.crt | 30 + rust/pv/tests/assets/cert/ibm.crl | 20 + rust/pv/tests/assets/cert/ibm.crt | 39 + .../tests/assets/cert/ibm_outdated_early.crl | 19 + .../tests/assets/cert/ibm_outdated_late.crl | 19 + rust/pv/tests/assets/cert/ibm_rev.crt | 39 + .../tests/assets/cert/ibm_wrong_subject.crt | 39 + rust/pv/tests/assets/cert/inter_ca.crl | 20 + rust/pv/tests/assets/cert/inter_ca.crt | 38 + rust/pv/tests/assets/cert/root_ca.chained.crt | 56 ++ rust/pv/tests/assets/cert/root_ca.crt | 37 + .../exp/asrcb/assoc_derived_default_cuid_one | Bin 0 -> 1416 bytes .../exp/asrcb/assoc_none_default_cuid_one | Bin 0 -> 1416 bytes .../exp/asrcb/assoc_simple_default_cuid_one | Bin 0 -> 1416 bytes .../exp/asrcb/null_derived_default_cuid_one | Bin 0 -> 1352 bytes .../exp/asrcb/null_none_default_cuid_one | Bin 0 -> 1352 bytes .../exp/asrcb/null_none_default_cuid_seven | Bin 0 -> 1832 bytes .../exp/asrcb/null_none_default_ncuid_one | Bin 0 -> 1352 bytes .../assets/exp/asrcb/null_none_dump_cuid_one | Bin 0 -> 1352 bytes .../exp/asrcb/null_simple_default_cuid_one | Bin 0 -> 1352 bytes rust/pv/tests/assets/exp/keyslot.bin | 1 + rust/pv/tests/assets/exp/secure_guest.hdr | Bin 0 -> 640 bytes rust/pv/tests/assets/keys/host.pem.crt | 30 + rust/pv/tests/assets/keys/private_cust.bin | Bin 0 -> 80 bytes rust/pv/tests/assets/keys/public_cust.bin | Bin 0 -> 160 bytes 71 files changed, 7162 insertions(+) create mode 100644 rust/pv/Cargo.toml create mode 100644 rust/pv/openssl_extensions/Cargo.toml create mode 100644 rust/pv/openssl_extensions/build.rs create mode 100644 rust/pv/openssl_extensions/src/akid.rs create mode 100644 rust/pv/openssl_extensions/src/crl.rs create mode 100644 rust/pv/openssl_extensions/src/lib.rs create mode 100644 rust/pv/openssl_extensions/src/stackable_crl.rs create mode 120000 rust/pv/openssl_extensions/tests/assets create mode 100644 rust/pv/src/brcb.rs create mode 100644 rust/pv/src/cli.rs create mode 100644 rust/pv/src/crypto.rs create mode 100644 rust/pv/src/error.rs create mode 100644 rust/pv/src/lib.rs create mode 100644 rust/pv/src/log.rs create mode 100644 rust/pv/src/req.rs create mode 100644 rust/pv/src/secret.rs create mode 100644 rust/pv/src/test_utils.rs create mode 100644 rust/pv/src/utils.rs create mode 100644 rust/pv/src/uvdevice.rs create mode 100644 rust/pv/src/uvdevice/ffi.rs create mode 100644 rust/pv/src/uvdevice/info.rs create mode 100644 rust/pv/src/uvdevice/test.rs create mode 100644 rust/pv/src/uvsecret.rs create mode 100644 rust/pv/src/uvsecret/asrcb.rs create mode 100644 rust/pv/src/uvsecret/ext_secret.rs create mode 100644 rust/pv/src/uvsecret/guest_secret.rs create mode 100644 rust/pv/src/uvsecret/secret_list.rs create mode 100644 rust/pv/src/uvsecret/uvc.rs create mode 100644 rust/pv/src/verify.rs create mode 100644 rust/pv/src/verify/helper.rs create mode 100644 rust/pv/src/verify/test.rs create mode 100644 rust/pv/tests/add_secret_request.rs create mode 100644 rust/pv/tests/assets/cert/der.crl create mode 100644 rust/pv/tests/assets/cert/der.crt create mode 100644 rust/pv/tests/assets/cert/fake_inter_ca.crl create mode 100644 rust/pv/tests/assets/cert/fake_inter_ca.crt create mode 100644 rust/pv/tests/assets/cert/gen/.gitignore create mode 100644 rust/pv/tests/assets/cert/gen/Makefile create mode 100755 rust/pv/tests/assets/cert/gen/clean.sh create mode 100755 rust/pv/tests/assets/cert/gen/create_certs.py create mode 100755 rust/pv/tests/assets/cert/gen/create_certs.sh create mode 100644 rust/pv/tests/assets/cert/gen/requirements.txt create mode 100644 rust/pv/tests/assets/cert/host.crt create mode 100644 rust/pv/tests/assets/cert/host_crt_expired.crt create mode 100644 rust/pv/tests/assets/cert/host_invalid_signing_key.crt create mode 100644 rust/pv/tests/assets/cert/host_rev.crt create mode 100644 rust/pv/tests/assets/cert/ibm.crl create mode 100644 rust/pv/tests/assets/cert/ibm.crt create mode 100644 rust/pv/tests/assets/cert/ibm_outdated_early.crl create mode 100644 rust/pv/tests/assets/cert/ibm_outdated_late.crl create mode 100644 rust/pv/tests/assets/cert/ibm_rev.crt create mode 100644 rust/pv/tests/assets/cert/ibm_wrong_subject.crt create mode 100644 rust/pv/tests/assets/cert/inter_ca.crl create mode 100644 rust/pv/tests/assets/cert/inter_ca.crt create mode 100644 rust/pv/tests/assets/cert/root_ca.chained.crt create mode 100644 rust/pv/tests/assets/cert/root_ca.crt create mode 100644 rust/pv/tests/assets/exp/asrcb/assoc_derived_default_cuid_one create mode 100644 rust/pv/tests/assets/exp/asrcb/assoc_none_default_cuid_one create mode 100644 rust/pv/tests/assets/exp/asrcb/assoc_simple_default_cuid_one create mode 100644 rust/pv/tests/assets/exp/asrcb/null_derived_default_cuid_one create mode 100644 rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_one create mode 100644 rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_seven create mode 100644 rust/pv/tests/assets/exp/asrcb/null_none_default_ncuid_one create mode 100644 rust/pv/tests/assets/exp/asrcb/null_none_dump_cuid_one create mode 100644 rust/pv/tests/assets/exp/asrcb/null_simple_default_cuid_one create mode 100644 rust/pv/tests/assets/exp/keyslot.bin create mode 100644 rust/pv/tests/assets/exp/secure_guest.hdr create mode 100644 rust/pv/tests/assets/keys/host.pem.crt create mode 100644 rust/pv/tests/assets/keys/private_cust.bin create mode 100644 rust/pv/tests/assets/keys/public_cust.bin diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b69f615..98a90ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Release history for s390-tools (MIT version) s390-tools now supports tools written in rust! Add new tools / libraries: + - rust/pv: Library for pv tools written in rust Changes of existing tools: - genprotimg: add support for add-secret requests diff --git a/rust/pv/Cargo.toml b/rust/pv/Cargo.toml new file mode 100644 index 00000000..689d6242 --- /dev/null +++ b/rust/pv/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pv" +version = "0.9.0" +edition = "2021" +license = "MIT" + +[dependencies] +libc = "0.2" +log = { version = "0.4", features = ["std", "release_max_level_debug"] } +thiserror = "1" +zerocopy = "0.6" +cfg-if = "1.0.0" + +# dependencies for request feature +clap = { version ="4", features = ["derive", "wrap_help"], optional = true } +curl = { version ="0.4", optional = true } +openssl = {version = "0.10", optional = true } +openssl_extensions = { path = "openssl_extensions", optional = true } +serde = { version = "1", features = ["derive"], optional = true } + +# misc optional dependencies +byteorder = {version = "1", optional = true } + +[dev-dependencies] +mockito = {version = "0.31", default-features = false } +serde_test = "1" +lazy_static = "1" + +[features] +default = [] +request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"] +uvsecret = ["dep:byteorder", "dep:serde"] + +[profile.release] +lto = true +panic = "abort" # release builds now do not clean up stack after panics. .1 Mb diff --git a/rust/pv/openssl_extensions/Cargo.toml b/rust/pv/openssl_extensions/Cargo.toml new file mode 100644 index 00000000..17d4c0e5 --- /dev/null +++ b/rust/pv/openssl_extensions/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "openssl_extensions" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +foreign-types = "0.3" +libc = {version = "0.2", features = [ "extra_traits"] } +log = { version = "0.4", features = ["std", "release_max_level_debug"] } +openssl = "0.10" +openssl-sys = "0.9" diff --git a/rust/pv/openssl_extensions/build.rs b/rust/pv/openssl_extensions/build.rs new file mode 100644 index 00000000..622298d2 --- /dev/null +++ b/rust/pv/openssl_extensions/build.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![allow( + clippy::inconsistent_digit_grouping, + clippy::uninlined_format_args, + clippy::unusual_byte_groupings +)] + +use std::env; + +fn main() { + if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { + for var in vars.split(',') { + println!("cargo:rustc-cfg=osslconf=\"{}\"", var); + } + } + + if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { + let version = u64::from_str_radix(&version, 16).unwrap(); + + if version >= 0x1_00_01_00_0 { + println!("cargo:rustc-cfg=ossl101"); + } + if version >= 0x1_00_02_00_0 { + println!("cargo:rustc-cfg=ossl102"); + } + if version >= 0x1_01_00_00_0 { + println!("cargo:rustc-cfg=ossl110"); + } + if version >= 0x1_01_00_07_0 { + println!("cargo:rustc-cfg=ossl110g"); + } + if version >= 0x1_01_00_08_0 { + println!("cargo:rustc-cfg=ossl110h"); + } + if version >= 0x1_01_01_00_0 { + println!("cargo:rustc-cfg=ossl111"); + } + if version >= 0x3_00_00_00_0 { + println!("cargo:rustc-cfg=ossl300"); + } + } +} diff --git a/rust/pv/openssl_extensions/src/akid.rs b/rust/pv/openssl_extensions/src/akid.rs new file mode 100644 index 00000000..fe3a8fb8 --- /dev/null +++ b/rust/pv/openssl_extensions/src/akid.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use std::fmt; + +use foreign_types::{foreign_type, ForeignType, ForeignTypeRef}; +use libc::c_int; +use openssl::x509::{X509CrlRef, X509Ref}; + +mod ffi { + extern "C" { + pub fn X509_check_akid( + issuer: *const openssl_sys::X509, + akid: *const openssl_sys::AUTHORITY_KEYID, + ) -> ::libc::c_int; + } +} + +foreign_type! { + type CType = openssl_sys::AUTHORITY_KEYID; + fn drop = openssl_sys::AUTHORITY_KEYID_free; + + /// An `Authority Key Identifier`. + pub struct Akid; + /// Reference to `Akid` + pub struct AkidRef; +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct AkidCheckResult(c_int); + +impl fmt::Debug for AkidCheckResult { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("AkidCheckResult") + .field("code", &self.0) + .finish() + } +} + +impl AkidCheckResult { + /// Creates an `AkidCheckResult` from a raw error number. + unsafe fn from_raw(err: c_int) -> AkidCheckResult { + AkidCheckResult(err) + } + + pub const OK: AkidCheckResult = AkidCheckResult(openssl_sys::X509_V_OK); + pub const ERR_AKID_ISSUER_SERIAL_MISMATCH: AkidCheckResult = + AkidCheckResult(openssl_sys::X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH); + pub const ERR_AKID_SKID_MISMATCH: AkidCheckResult = + AkidCheckResult(openssl_sys::X509_V_ERR_AKID_SKID_MISMATCH); +} + +impl AkidRef { + ///Check if the `Akid` matches the issuer + /// + pub fn check(&self, issuer: &X509Ref) -> AkidCheckResult { + unsafe { + let res = ffi::X509_check_akid(issuer.as_ptr(), self.as_ptr()); + AkidCheckResult::from_raw(res) + } + } +} + +pub trait AkidExtension { + fn akid(&self) -> Option; +} + +impl AkidExtension for X509Ref { + fn akid(&self) -> Option { + unsafe { + let ptr = openssl_sys::X509_get_ext_d2i( + self.as_ptr(), + openssl_sys::NID_authority_key_identifier, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + if ptr.is_null() { + None + } else { + Some(Akid::from_ptr(ptr as *mut _)) + } + } + } +} + +impl AkidExtension for X509CrlRef { + fn akid(&self) -> Option { + unsafe { + let ptr = openssl_sys::X509_CRL_get_ext_d2i( + self.as_ptr(), + openssl_sys::NID_authority_key_identifier, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + if ptr.is_null() { + None + } else { + Some(Akid::from_ptr(ptr as *mut _)) + } + } + } +} + +#[cfg(test)] +mod test { + use crate::test_utils::load_gen_cert; + + use super::*; + + #[test] + fn akid() { + let cert = load_gen_cert("ibm.crt"); + let ca = load_gen_cert("root_ca.crt"); + + let akid = cert.akid().unwrap(); + let res = akid.check(&ca); + assert_eq!(res, AkidCheckResult::OK); + } +} diff --git a/rust/pv/openssl_extensions/src/crl.rs b/rust/pv/openssl_extensions/src/crl.rs new file mode 100644 index 00000000..175cc380 --- /dev/null +++ b/rust/pv/openssl_extensions/src/crl.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +pub use crate::stackable_crl::*; +use foreign_types::{ForeignType, ForeignTypeRef}; +use openssl::{ + error::ErrorStack, + stack::{Stack, StackRef}, + x509::{ + store::{X509StoreBuilderRef, X509StoreRef}, + X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509, + }, +}; + +pub fn opt_to_ptr(o: Option<&T>) -> *mut T::CType { + match o { + None => std::ptr::null_mut(), + Some(p) => p.as_ptr(), + } +} + +mod ffi { + extern "C" { + #[cfg(ossl110)] + pub fn X509_STORE_CTX_get1_crls( + ctx: *mut openssl_sys::X509_STORE_CTX, + nm: *mut openssl_sys::X509_NAME, + ) -> *mut openssl_sys::stack_st_X509_CRL; + pub fn X509_STORE_add_crl( + xs: *mut openssl_sys::X509_STORE, + x: *mut openssl_sys::X509_CRL, + ) -> libc::c_int; + } +} + +pub trait X509StoreExtension { + fn add_crl(&mut self, crl: &X509CrlRef) -> Result<(), ErrorStack>; +} + +impl X509StoreExtension for X509StoreBuilderRef { + fn add_crl(&mut self, crl: &X509CrlRef) -> Result<(), ErrorStack> { + unsafe { + { + let r = ffi::X509_STORE_add_crl(self.as_ptr(), crl.as_ptr()); + if r <= 0 { + Err(ErrorStack::get()) + } else { + Ok(()) + } + } + } + } +} + +pub trait X509StoreContextExtension { + fn init_opt( + &mut self, + trust: &X509StoreRef, + cert: Option<&X509Ref>, + cert_chain: Option<&StackRef>, + with_context: F, + ) -> Result + where + F: FnOnce(&mut X509StoreContextRef) -> std::result::Result; + fn crls( + &mut self, + subj: &X509NameRef, + ) -> std::result::Result, ErrorStack>; +} + +impl X509StoreContextExtension for X509StoreContextRef { + fn init_opt( + &mut self, + trust: &X509StoreRef, + cert: Option<&X509Ref>, + cert_chain: Option<&StackRef>, + with_context: F, + ) -> Result + where + F: FnOnce(&mut X509StoreContextRef) -> std::result::Result, + { + struct Cleanup<'a>(&'a mut X509StoreContextRef); + + impl<'a> Drop for Cleanup<'a> { + fn drop(&mut self) { + unsafe { + openssl_sys::X509_STORE_CTX_cleanup(self.0.as_ptr()); + } + } + } + + unsafe { + { + let r = openssl_sys::X509_STORE_CTX_init( + self.as_ptr(), + trust.as_ptr(), + opt_to_ptr(cert), + opt_to_ptr(cert_chain), + ); + if r <= 0 { + Err(ErrorStack::get()) + } else { + Ok(r) + } + }?; + } + let cleanup = Cleanup(self); + with_context(cleanup.0) + } + /// Get all Certificate Revocation Lists with the subject currently stored + #[cfg(ossl110)] + fn crls( + &mut self, + subj: &X509NameRef, + ) -> std::result::Result, ErrorStack> { + unsafe { + { + let r = ffi::X509_STORE_CTX_get1_crls(self.as_ptr(), subj.as_ptr()); + if r.is_null() { + Err(ErrorStack::get()) + } else { + Ok(Stack::from_ptr(r)) + } + } + } + } +} diff --git a/rust/pv/openssl_extensions/src/lib.rs b/rust/pv/openssl_extensions/src/lib.rs new file mode 100644 index 00000000..075319dc --- /dev/null +++ b/rust/pv/openssl_extensions/src/lib.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![doc(hidden)] + +/// Extensions to the rust-openssl crate, that are not upstream yet +/// Upstreaming mostly work in progress +pub mod akid; +pub mod crl; +mod stackable_crl; + +/// Test if two CRLs are equal. +/// +/// relates to X509_CRL_match +/// (Upstream is missing that functionality) +pub fn x509_crl_eq(a: &openssl::x509::X509CrlRef, b: &openssl::x509::X509CrlRef) -> bool { + use foreign_types::ForeignTypeRef; + let cmp = unsafe { openssl_sys::X509_CRL_match(a.as_ptr(), b.as_ptr()) }; + cmp == 0 +} + +#[allow(dead_code)] +mod test_utils { + include!("../../src/test_utils.rs"); +} diff --git a/rust/pv/openssl_extensions/src/stackable_crl.rs b/rust/pv/openssl_extensions/src/stackable_crl.rs new file mode 100644 index 00000000..4582416d --- /dev/null +++ b/rust/pv/openssl_extensions/src/stackable_crl.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use std::{marker::PhantomData, ptr}; + +use foreign_types::{ForeignType, ForeignTypeRef}; +use libc::c_int; +use openssl::{ + error::ErrorStack, + stack::Stackable, + x509::{X509Crl, X509CrlRef}, +}; +use openssl_sys::BIO_new_mem_buf; + +pub struct StackableX509Crl(*mut openssl_sys::X509_CRL); + +impl ForeignType for StackableX509Crl { + type CType = openssl_sys::X509_CRL; + type Ref = X509CrlRef; + unsafe fn from_ptr(ptr: *mut openssl_sys::X509_CRL) -> StackableX509Crl { + StackableX509Crl(ptr) + } + fn as_ptr(&self) -> *mut openssl_sys::X509_CRL { + self.0 + } +} +impl Drop for StackableX509Crl { + fn drop(&mut self) { + unsafe { (openssl_sys::X509_CRL_free)(self.0) } + } +} +impl ::std::ops::Deref for StackableX509Crl { + type Target = X509CrlRef; + fn deref(&self) -> &X509CrlRef { + unsafe { ForeignTypeRef::from_ptr(self.0) } + } +} +impl ::std::ops::DerefMut for StackableX509Crl { + fn deref_mut(&mut self) -> &mut X509CrlRef { + unsafe { ForeignTypeRef::from_ptr_mut(self.0) } + } +} +#[allow(clippy::explicit_auto_deref)] +impl ::std::borrow::Borrow for StackableX509Crl { + fn borrow(&self) -> &X509CrlRef { + &**self + } +} +#[allow(clippy::explicit_auto_deref)] +impl ::std::convert::AsRef for StackableX509Crl { + fn as_ref(&self) -> &X509CrlRef { + &**self + } +} + +impl Stackable for StackableX509Crl { + type StackType = openssl_sys::stack_st_X509_CRL; +} + +pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>); +impl<'a> Drop for MemBioSlice<'a> { + fn drop(&mut self) { + unsafe { + openssl_sys::BIO_free_all(self.0); + } + } +} + +impl<'a> MemBioSlice<'a> { + pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { + openssl_sys::init(); + + assert!(buf.len() <= c_int::max_value() as usize); + let bio = unsafe { + { + let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int); + if r.is_null() { + Err(ErrorStack::get()) + } else { + Ok(r) + } + }? + }; + + Ok(MemBioSlice(bio, PhantomData)) + } + + pub fn as_ptr(&self) -> *mut openssl_sys::BIO { + self.0 + } +} + +impl StackableX509Crl { + pub fn stack_from_pem(pem: &[u8]) -> Result, ErrorStack> { + unsafe { + openssl_sys::init(); + let bio = MemBioSlice::new(pem)?; + + let mut crls = vec![]; + loop { + let r = openssl_sys::PEM_read_bio_X509_CRL( + bio.as_ptr(), + ptr::null_mut(), + None, + ptr::null_mut(), + ); + if r.is_null() { + let err = openssl_sys::ERR_peek_last_error(); + if openssl_sys::ERR_GET_LIB(err) as c_int == openssl_sys::ERR_LIB_PEM + && openssl_sys::ERR_GET_REASON(err) == openssl_sys::PEM_R_NO_START_LINE + { + openssl_sys::ERR_clear_error(); + break; + } + + return Err(ErrorStack::get()); + } else { + crls.push(X509Crl::from_ptr(r)); + } + } + + Ok(crls) + } + } +} +impl From for StackableX509Crl { + fn from(value: X509Crl) -> Self { + unsafe { + openssl_sys::X509_CRL_up_ref(value.as_ptr()); + StackableX509Crl::from_ptr(value.as_ptr()) + } + } +} +impl From for X509Crl { + fn from(value: StackableX509Crl) -> Self { + unsafe { + openssl_sys::X509_CRL_up_ref(value.as_ptr()); + X509Crl::from_ptr(value.as_ptr()) + } + } +} diff --git a/rust/pv/openssl_extensions/tests/assets b/rust/pv/openssl_extensions/tests/assets new file mode 120000 index 00000000..c1a4e675 --- /dev/null +++ b/rust/pv/openssl_extensions/tests/assets @@ -0,0 +1 @@ +../../tests/assets \ No newline at end of file diff --git a/rust/pv/src/brcb.rs b/rust/pv/src/brcb.rs new file mode 100644 index 00000000..4bf5f161 --- /dev/null +++ b/rust/pv/src/brcb.rs @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use std::{ + io::{Read, Seek, SeekFrom::Current}, + mem::size_of, +}; + +// (SE) boot request control block aka SE header +use crate::{ + assert_size, request::MagicValue, requires_feat, static_assert, Error, Result, PAGESIZE, +}; +use log::debug; +use zerocopy::{AsBytes, BigEndian, FromBytes, U32, U64}; + +/// Struct containing all SE-header tags. +/// +/// Contains: +/// Page List Digest (pld) +/// Address List Digest (ald) +/// Tweak List Digest (tld) +/// SE Header Tag (seht) +/// +#[doc = requires_feat!(request)] +#[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], +} + +/// Magiv value for a SE-(boot)header +pub struct BootHdrMagic; +impl MagicValue<8> for BootHdrMagic { + const MAGIC: [u8; 8] = [0x49, 0x42, 0x4d, 0x53, 0x65, 0x63, 0x45, 0x78]; +} + +impl BootHdrTags { + /// Returns a reference to the SE-hdr tag of this [`BootHdrTags`]. + pub fn seht(&self) -> &[u8; 16] { + &self.seht + } + + /// 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, + } + } + + /// returns false if no hdr found, true otherwise + /// in the very unlikel case an IO error can appear + /// when seeking to the beginning of the header + fn seek_se_hdr_start(img: &mut R) -> Result + where + R: Read + Seek, + { + const MAX_ITER: usize = 0x15; + const BUF_SIZE: i64 = 8; + static_assert!(BootHdrMagic::MAGIC.len() == BUF_SIZE as usize); + + let mut buf = [0; BUF_SIZE as usize]; + for _ in [0; MAX_ITER] { + match img.read_exact(&mut buf) { + Ok(it) => it, + Err(_) => return Ok(false), + }; + + if BootHdrMagic::starts_with_magic(&buf) { + // go back to the beginning of the header + img.seek(Current(-BUF_SIZE))?; + + return Ok(true); + } + // goto next page start + // or report invalid file format if file ends "early" + match img.seek(Current(PAGESIZE as i64 - BUF_SIZE)) { + Ok(it) => it, + Err(_) => return Ok(false), + }; + } + Ok(false) + } + + /// Deserializes a (SE) boot header and extracts the tags. + /// + /// Searches for the header; if found extracts the tags. + /// + /// # 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. + pub fn from_se_image(img: &mut R) -> Result + where + R: Read + Seek, + { + if !Self::seek_se_hdr_start(img)? { + debug!("No boot hdr found"); + return Err(Error::InvBootHdr); + } + // read in the header + let mut hdr = vec![0u8; size_of::()]; + img.read_exact(&mut hdr)?; + + let hdr_head = match BootHdrHead::read_from_prefix(hdr.as_mut_slice()) { + Some(hdr) => hdr, + None => { + debug!("Boot hdr is to small"); + return Err(Error::InvBootHdr); + } + }; + + //Some sanity checks + if !BootHdrMagic::starts_with_magic(&hdr) || hdr_head.version.get() != 0x100 { + debug!("Inv magic or size"); + return Err(Error::InvBootHdr); + } + + //go to the Bot header tag + img.seek(Current( + hdr_head.size.get() as i64 + - size_of::() as i64 + - BootHdrHead::SEHT_SIZE as i64, + ))?; + + // read in the tag + let mut seht = [0u8; BootHdrHead::SEHT_SIZE]; + img.read_exact(seht.as_mut_slice())?; + + Ok(BootHdrTags { + pld: hdr_head.pld, + ald: hdr_head.ald, + tld: hdr_head.tld, + seht, + }) + } +} + +#[repr(C)] +#[derive(Debug, Clone, FromBytes)] +struct BootHdrHead { + magic: U64, + version: U32, + size: U32, + iv: [u8; 12], + res1: u32, + nks: U64, + sea: U64, + nep: U64, + pcf: U64, + user_pubkey: [u8; 160], + pld: [u8; Self::DIGEST_SIZE], + ald: [u8; Self::DIGEST_SIZE], + tld: [u8; Self::DIGEST_SIZE], +} +assert_size!(BootHdrHead, 0x1A0); +impl BootHdrHead { + const DIGEST_SIZE: usize = 0x40; + const SEHT_SIZE: usize = 0x10; +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::*; + use crate::get_test_asset; + use crate::Error; + + const EXP_HDR: BootHdrTags = BootHdrTags { + pld: [ + 0xbe, 0x94, 0xb5, 0xea, 0xb3, 0xc1, 0xb1, 0x18, 0xc7, 0x57, 0xd7, 0xdb, 0x7e, 0xa0, + 0xf6, 0x5d, 0x9b, 0x64, 0x82, 0x3a, 0x8d, 0xc5, 0x5b, 0xf8, 0xa8, 0x72, 0x5b, 0x58, + 0x07, 0x2d, 0x9d, 0x42, 0x58, 0xc5, 0x3e, 0x8a, 0x5d, 0xa8, 0x2d, 0xfb, 0x21, 0x92, + 0xd9, 0x1d, 0x07, 0xbc, 0x1c, 0x39, 0xb9, 0x5d, 0x63, 0x21, 0xd3, 0xba, 0x16, 0xa7, + 0x51, 0xa6, 0xe3, 0xe3, 0x2f, 0x3e, 0x01, 0x61, + ], + ald: [ + 0x28, 0x58, 0xc3, 0x36, 0x8b, 0x2a, 0x0a, 0xf0, 0xc5, 0xea, 0x0f, 0xde, 0x79, 0x05, + 0xeb, 0x15, 0xaf, 0x9c, 0xd1, 0xdd, 0x73, 0x71, 0x65, 0x93, 0x3c, 0xda, 0xa2, 0xb8, + 0x50, 0xb6, 0xa8, 0xe2, 0xf0, 0xf4, 0x2c, 0x7b, 0x36, 0xdd, 0x53, 0x81, 0x09, 0x62, + 0x88, 0xdc, 0x09, 0x2d, 0xaa, 0x8a, 0x6f, 0xac, 0xec, 0x25, 0x34, 0x13, 0x7b, 0xc9, + 0x4c, 0xa8, 0x0b, 0xda, 0x4f, 0xcb, 0x93, 0x28, + ], + tld: [ + 0x48, 0x60, 0xeb, 0xcf, 0x7b, 0x9d, 0x24, 0xeb, 0x90, 0x9a, 0x79, 0x53, 0x56, 0xad, + 0x32, 0xc9, 0x36, 0xb6, 0x21, 0x65, 0x98, 0x8a, 0x9f, 0xfc, 0xd6, 0x61, 0x70, 0xdb, + 0xc5, 0x90, 0xc2, 0x30, 0x10, 0xd7, 0x95, 0x2f, 0xa8, 0x82, 0xd1, 0xbb, 0x79, 0x55, + 0x8f, 0x9b, 0xe0, 0xa5, 0x49, 0xd8, 0xd7, 0xa9, 0x4a, 0xe7, 0x20, 0xe5, 0xc0, 0x76, + 0x0a, 0x82, 0x5d, 0x47, 0x9f, 0xe6, 0x7a, 0xf5, + ], + seht: [ + 0x92, 0x30, 0x9d, 0x45, 0x89, 0xb9, 0xa8, 0x5b, 0x42, 0x7f, 0x87, 0x53, 0x17, 0x1d, + 0x15, 0x20, + ], + }; + + #[test] + fn from_se_image_hdr() { + let bin_hdr = get_test_asset!("exp/secure_guest.hdr"); + let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(bin_hdr.clone())).unwrap(); + assert_eq!(hdr_tags, EXP_HDR); + } + + #[test] + fn from_se_image_fail() { + let bin_hdr = get_test_asset!("exp/secure_guest.hdr"); + let short_hdr = &bin_hdr[1..]; + + assert!(matches!( + BootHdrTags::from_se_image(&mut Cursor::new(short_hdr)), + Err(Error::InvBootHdr) + )); + + // mess up magic + let mut bin_hdr_copy = bin_hdr.clone(); + bin_hdr_copy.swap(0, 1); + assert!(matches!( + BootHdrTags::from_se_image(&mut Cursor::new(bin_hdr_copy)), + Err(Error::InvBootHdr) + )); + + //header is at a non expected position + let mut img = vec![0u8; PAGESIZE]; + img[0x008..0x288].copy_from_slice(bin_hdr); + assert!(matches!( + BootHdrTags::from_se_image(&mut Cursor::new(img)), + Err(Error::InvBootHdr) + )); + } + + #[test] + fn from_se_image_img() { + let mut img = vec![0u8; 0x13000]; + let bin_hdr = get_test_asset!("exp/secure_guest.hdr"); + img[0x12000..0x12280].copy_from_slice(bin_hdr); + let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(img)).unwrap(); + assert_eq!(hdr_tags, EXP_HDR); + } +} diff --git a/rust/pv/src/cli.rs b/rust/pv/src/cli.rs new file mode 100644 index 00000000..5cf5693b --- /dev/null +++ b/rust/pv/src/cli.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::Result; +use crate::{create_buffered_file, open_buffered_file}; +use clap::{ArgGroup, Args, ValueHint}; +use std::io::{Read, Write}; + +/// CLI Argument collection for handling certificates. +/// +#[doc = requires_feat!(request)] +#[derive(Args, Debug, PartialEq, Eq, Default)] +#[command( + group(ArgGroup::new("pv_verify").required(true).args(["no_verify", "certs"])), + )] +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, + + /// 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 before. + #[arg(long)] + pub no_verify: bool, + + /// Use FILE as a certificate to verify the host-key(s). + /// + /// 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 rootCA). + #[arg( + short= 'C', + long = "cert", + value_name = "FILE", + alias("crt"), + value_hint = ValueHint::FilePath, + use_value_delimiter = true, + value_delimiter = ',', + )] + pub certs: Vec, + + /// Use FILE as a certificate revocation list. + /// + /// That 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, + + /// 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, +} + +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> { + 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 +#[cfg(feature = "request")] +pub const STDOUT: &str = "-"; +/// stdin +#[cfg(feature = "request")] +pub const STDIN: &str = "-"; + +/// Converts an argument value into a Writer. +/// +/// # Errors +/// No Error will occur but function must match a signature +/// +#[cfg(feature = "request")] +pub fn get_writer_from_cli_file_arg(path: &str) -> Result> { + if path == STDOUT { + Ok(Box::new(std::io::stdout())) + } else { + Ok(Box::new(create_buffered_file!(path))) + } +} + +/// Converts an argument value into a Reader. +/// +/// # Errors +/// No Error will occur but function must match a signature +/// +#[cfg(feature = "request")] +pub fn get_reader_from_cli_file_arg(path: &str) -> Result> { + if path == STDIN { + Ok(Box::new(std::io::stdin())) + } else { + Ok(Box::new(open_buffered_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()); + } + } +} diff --git a/rust/pv/src/crypto.rs b/rust/pv/src/crypto.rs new file mode 100644 index 00000000..910419e5 --- /dev/null +++ b/rust/pv/src/crypto.rs @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::requires_feat; +use crate::{error::Result, secret::Secret, Error}; +use openssl::rand::rand_bytes; +use openssl::{ + derive::Deriver, + ec::{EcGroup, EcKey}, + hash::{DigestBytes, MessageDigest}, + md::MdRef, + nid::Nid, + pkey::{Id, PKey, Private, Public}, + pkey_ctx::{HkdfMode, PkeyCtx}, + symm::{encrypt, encrypt_aead, Cipher}, +}; +use std::convert::TryInto; + +/// An AES256-key that will purge itself out of the memory when going out of scope +/// +#[doc = requires_feat!(request)] +pub type Aes256Key = Secret<[u8; 32]>; + +/// Types of symmetric keys, to specify during construction. +/// +#[doc = requires_feat!(request)] +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SymKeyType { + /// AES 256 key (32 bytes) + Aes256, +} + +/// Types of symmetric keys +/// +#[doc = requires_feat!(request)] +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SymKey { + /// AES 256 key (32 bytes) + Aes256(Aes256Key), +} + +impl SymKey { + /// Generates a random symmetric key. + /// + /// * `key_tp` - type of the symmetric key + /// + /// # Errors + /// + /// This function will return an error if the Key cannot be generated. + pub fn random(key_tp: SymKeyType) -> Result { + match key_tp { + SymKeyType::Aes256 => Ok(Self::Aes256(random_array().map(|v| v.into())?)), + } + } + + /// Returns a reference to the value of this [`SymKey`]. + pub fn value(&self) -> &[u8] { + match self { + Self::Aes256(key) => key.value(), + } + } +} + +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 for SymKey { + fn from(value: Aes256Key) -> Self { + Self::Aes256(value) + } +} + +/// Performs an hkdf according to RFC 5869. +/// See [`OpenSSL HKDF`]() +/// +#[doc = requires_feat!(request)] +/// # Errors +/// +/// This function will return an OpenSSL error if the key could not be generated. +pub fn hkdf_rfc_5869( + md: &MdRef, + ikm: &[u8], + salt: &[u8], + info: &[u8], +) -> Result<[u8; COUNT]> { + let mut ctx = PkeyCtx::new_id(Id::HKDF)?; + ctx.derive_init()?; + ctx.set_hkdf_mode(HkdfMode::EXTRACT_THEN_EXPAND)?; + ctx.set_hkdf_md(md)?; + ctx.set_hkdf_salt(salt)?; + ctx.set_hkdf_key(ikm)?; + ctx.add_hkdf_info(info)?; + + let mut res = [0; COUNT]; + ctx.derive(Some(&mut res))?; + Ok(res) +} + +/// Derive a symmetric key from a private and a public key. +/// +#[doc = requires_feat!(request)] +/// # Errors +/// +/// This function will return an error if something went bad in OpenSSL. +pub fn derive_key(k1: &PKey, k2: &PKey) -> Result { + 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); + + Ok(Aes256Key::from_digest(hash( + MessageDigest::sha256(), + secr.value(), + )?)) +} + +/// Generate a random array. +/// +#[doc = requires_feat!(request)] +/// # Errors +/// +/// This function will return an error if the entropy source fails or is not available. +pub fn random_array() -> Result<[u8; COUNT]> { + let mut rand = [0; COUNT]; + rand_bytes(&mut rand)?; + Ok(rand) +} + +/// Generate a new random EC-SECP521R1 key. +/// +#[doc = requires_feat!(request)] +/// # Errors +/// +/// This function will return an error if the key could not be generated by OpenSSL. +pub fn gen_ec_key() -> Result> { + let group = EcGroup::from_curve_name(Nid::SECP521R1)?; + let key: EcKey = EcKey::generate(&group)?; + PKey::from_ec_key(key).map_err(Error::Crypto) +} + +/// Encrypt confidential Data with a symmetric key. +/// +/// * `key` - symmetric key used for encryption +/// * `iv` - initialisation vector +/// * `conf` - data to be encrypted +/// +#[doc = requires_feat!(request)] +/// # Errors +/// +/// This function will return an error if the data could not be encrypted by OpenSSL. +pub fn encrypt_aes(key: &SymKey, iv: &[u8], conf: &[u8]) -> Result> { + match key { + SymKey::Aes256(key) => { + encrypt(Cipher::aes_256_gcm(), key.value(), Some(iv), conf).map_err(Error::Crypto) + } + } +} + +/// Encrypt confidential Data with a symmetric key and provida a gcm tag. +/// +/// * `key` - symmetric key used for encryption +/// * `iv` - initialisation vector +/// * `aad` - additional authentic data +/// * `conf` - data to be encrypted +/// +#[doc = requires_feat!(request)] +/// # Returns +/// [`Vec`] with the following content: +/// 1. `aad` +/// 2. `encr(conf)` +/// 3. `aes gcm tag` +/// +/// # 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> { + let mut tag = vec![0xff; 16]; + let encr = match key { + SymKey::Aes256(key) => encrypt_aead( + Cipher::aes_256_gcm(), + key.value(), + Some(iv), + aad, + conf, + &mut tag, + )?, + }; + + let mut res = vec![0; aad.len() + encr.len() + 16]; + res[0..aad.len()].copy_from_slice(aad); + res[aad.len()..aad.len() + encr.len()].copy_from_slice(&encr); + res[aad.len() + encr.len()..aad.len() + encr.len() + 16].copy_from_slice(&tag); + + Ok(res) +} + +/// Calculate the hash of a slice. +/// +#[doc = requires_feat!(request)] +/// # Errors +/// +/// This function will return an error if OpenSSL could not compute the hash. +pub fn hash(t: MessageDigest, data: &[u8]) -> Result { + openssl::hash::hash(t, data).map_err(Error::Crypto) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::*; + + #[test] + fn derive_key() { + let (cust_key, host_key) = get_test_keys(); + + let exp_key: Aes256Key = [ + 0x75, 0x32, 0x77, 0x55, 0x8f, 0x3b, 0x60, 0x3, 0x41, 0x9e, 0xf2, 0x49, 0xae, 0x3c, + 0x4b, 0x55, 0xaa, 0xd7, 0x7d, 0x9, 0xd9, 0x7f, 0xdd, 0x1f, 0xc8, 0x8f, 0xd8, 0xf0, + 0xcf, 0x22, 0xf1, 0x49, + ] + .into(); + + let calc_key = super::derive_key(&cust_key, &host_key).unwrap(); + + assert_eq!(&calc_key, &exp_key); + } + + #[test] + fn hkdf_rfc_5869() { + use openssl::md::Md; + // RFC 6869 test vector 1 + let ikm = [0x0bu8; 22]; + let salt: [u8; 13] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + ]; + let info: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9]; + let exp: [u8; 42] = [ + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, + 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, + 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65, + ]; + let res: [u8; 42] = super::hkdf_rfc_5869(Md::sha256(), &ikm, &salt, &info).unwrap(); + + assert_eq!(exp, res); + } + + #[test] + fn encrypt_aes_256_gcm() { + let aes_gcm_key = [ + 0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66, 0x5f, 0x8a, + 0xe6, 0xd1, 0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69, 0xa3, 0x52, 0x02, 0x93, + 0xa5, 0x72, 0x07, 0x8f, + ]; + let aes_gcm_iv = [ + 0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0, 0xee, 0xd0, 0x66, 0x84, + ]; + let aes_gcm_plain = [ + 0xf5, 0x6e, 0x87, 0x05, 0x5b, 0xc3, 0x2d, 0x0e, 0xeb, 0x31, 0xb2, 0xea, 0xcc, 0x2b, + 0xf2, 0xa5, + ]; + let aes_gcm_aad = [ + 0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec, + 0x78, 0xde, + ]; + let aes_gcm_res = vec![ + 0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec, + 0x78, 0xde, 0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c, 0xd5, 0x36, 0x86, 0x7e, + 0xb9, 0xf2, 0x17, 0x36, 0x67, 0xba, 0x05, 0x10, 0x26, 0x2a, 0xe4, 0x87, 0xd7, 0x37, + 0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c, + ]; + + let res = encrypt_aes_gcm( + &SymKey::Aes256(aes_gcm_key.into()), + &aes_gcm_iv, + &aes_gcm_aad, + &aes_gcm_plain, + ) + .unwrap(); + assert_eq!(res, aes_gcm_res); + } + + #[test] + fn encrypt_aes_256() { + let aes_gcm_key = [ + 0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66, 0x5f, 0x8a, + 0xe6, 0xd1, 0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69, 0xa3, 0x52, 0x02, 0x93, + 0xa5, 0x72, 0x07, 0x8f, + ]; + let aes_gcm_iv = [ + 0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0, 0xee, 0xd0, 0x66, 0x84, + ]; + let aes_gcm_plain = [ + 0xf5, 0x6e, 0x87, 0x05, 0x5b, 0xc3, 0x2d, 0x0e, 0xeb, 0x31, 0xb2, 0xea, 0xcc, 0x2b, + 0xf2, 0xa5, + ]; + let aes_gcm_res = vec![ + 0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c, 0xd5, 0x36, 0x86, 0x7e, 0xb9, 0xf2, + 0x17, 0x36, + ]; + + let res = encrypt_aes( + &&SymKey::Aes256(aes_gcm_key.into()), + &aes_gcm_iv, + &aes_gcm_plain, + ) + .unwrap(); + assert_eq!(res, aes_gcm_res); + } +} diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs new file mode 100644 index 00000000..56b3342c --- /dev/null +++ b/rust/pv/src/error.rs @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +/// Result type for this crate +pub type Result = std::result::Result; + +/// Error cases for this crate +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[cfg_attr(debug_assertions, error("Ultravisor: '{msg}' ({rc:#06x},{rrc:#06x})"))] + #[cfg_attr(not(debug_assertions), error("Ultravisor: '{msg}' ({rc:#06x})"))] + Uv { + rc: u16, + rrc: u16, + msg: &'static str, + }, + + #[error("Invalid SE header provided")] + #[cfg(feature = "request")] + InvBootHdr, + + #[error("{0}")] + Specification(String), + + #[error("Cannot {ty} {ctx} at `{path}`")] + FileIo { + ty: FileIoErrorType, + ctx: String, + path: String, + source: std::io::Error, + }, + #[error("Cannot {ty} `{path}`")] + FileAccess { + ty: FileAccessErrorType, + path: String, + source: std::io::Error, + }, + + #[error("Host-key verification failed: {0}")] + #[cfg(feature = "request")] + HkdVerify(HkdVerifyErrorType), + + #[error("No host-key provided")] + #[cfg(feature = "request")] + NoHostkey, + + #[error("To many host-keys provided")] + #[cfg(feature = "request")] + ManyHostkeys, + + #[error("Cannot load {ty} from {path}")] + #[cfg(feature = "request")] + X509Load { + path: String, + ty: &'static str, + source: openssl::error::ErrorStack, + }, + + #[error("Internal (unexpected) error: {0}, caused by {1}")] + #[cfg(feature = "request")] + InternalSsl(&'static str, #[source] openssl::error::ErrorStack), + + #[error("No Config UID found: {0}")] + NoCuid(String), + // errors from request types + #[cfg(feature = "uvsecret")] + #[error("Customer Communication Key must be 32 bytes long")] + CckSize, + + #[cfg(feature = "uvsecret")] + #[error("Cannot encode secrets (Too many secrets)")] + ManySecrets, + + #[cfg(feature = "uvsecret")] + #[error("Cannot decode secret list")] + InvSecretList(#[source] std::io::Error), + + #[cfg(feature = "uvsecret")] + #[error("Input does not contain an Add Secret Request")] + NoAsrcb, + + // errors from other crates + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + #[cfg(feature = "request")] + Crypto(#[from] openssl::error::ErrorStack), + #[error(transparent)] + ParseInt(#[from] std::num::ParseIntError), + #[cfg(feature = "request")] + #[error(transparent)] + Curl(#[from] curl::Error), +} + +// used in macros +#[doc(hidden)] +impl Error { + pub const CRL: &str = "CRL"; + pub const CERT: &str = "certificate"; +} + +/// Error cases for I/O operations +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum FileIoErrorType { + #[error("read")] + Read, + #[error("write")] + Write, +} + +/// Error cases for accessing files +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum FileAccessErrorType { + #[error("open")] + Open, + #[error("create")] + Create, +} + +/// Error cases for verifying host-key documents +/// +#[doc = crate::requires_feat!(request)] +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +#[non_exhaustive] +#[cfg(feature = "request")] +pub enum HkdVerifyErrorType { + #[error("Signature verification failed")] + Signature, + #[error("No valid CRL found")] + NoCrl, + #[error("Host-key document is revoked.")] + HdkRevoked, + #[error("Not enough bits of security. ({0}, {1} expected)")] + SecurityBits(u32, u32), + #[error("Authority Key Id mismatch")] + Akid, + #[error("CRL has no validity period")] + NoValidityPeriod, + #[error("Specify one IBM Z signing key")] + NoIbmSignKey, + #[error("Specify only one IBM Z signing key")] + ManyIbmSignKeys, + #[error("Before validity period")] + BeforeValidity, + #[error("After validity period")] + AfterValidity, + #[error("Issuer mismatch")] + IssuerMismatch, + #[error("No CRL distribution points found")] + NoCrlDP, + #[error("The IBM Z signing key could not be verified. Error occurred at level {1}")] + IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32), +} + +macro_rules! path_to_str { + ($path: expr) => { + $path.as_ref().to_str().unwrap_or("no UTF-8 path") + }; +} +pub(crate) use path_to_str; + +macro_rules! file_error { + ($ty: tt, $ctx: expr, $path:expr, $src: expr) => { + $crate::Error::FileIo { + ty: $crate::FileIoErrorType::$ty, + ctx: $ctx.to_string(), + path: $path.to_string(), + source: $src, + } + }; +} +pub(crate) use file_error; + +#[cfg(feature = "request")] +macro_rules! bail_hkd_verify { + ($var: tt) => { + return Err($crate::Error::HkdVerify($crate::HkdVerifyErrorType::$var)) + }; +} +#[cfg(feature = "request")] +pub(crate) use bail_hkd_verify; + +macro_rules! bail_spec { + ($str: expr) => { + return Err($crate::Error::Specification($str.to_string())) + }; +} +pub(crate) use bail_spec; diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs new file mode 100644 index 00000000..5b67da59 --- /dev/null +++ b/rust/pv/src/lib.rs @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)] +#![deny(missing_docs)] +//! pv - library for pv-tools +//! +//! This library is intened to be used by tools and libraries that +//! are used for creating and managing IBM Secure Execution guests. +//! `pv` provides abstraction layers for encryption, secure memory management, +//! logging, and accessing the uvdevice. +//! +//! ## Feature Flags +//! The following feature flags are available: +//! - `request` +//! - optional +//! - Enables generation of UV requests +//! - `uvsecret` +//! - optional +//! - Enables support for the UV Secret API. +mod error; +mod log; +mod utils; +mod uvdevice; + +/// Internal macro to conveninetly document required features on items +// #[macro_export] +#[doc(hidden)] +macro_rules! requires_feat { + (request) => { + " Requires the feature `request`" + }; + (uvsecret) => { + " Requires the feature `uvsecret`" + }; + (reqsecret) => { + "Requires the features `request` & `uvsecret`" + }; +} +#[allow(unused_imports)] +use requires_feat; + +//only some features need this +#[allow(dead_code)] +const PAGESIZE: usize = 0x1000; + +cfg_if::cfg_if! { + if #[cfg(feature = "request")] { + mod brcb; + mod cli; + mod crypto; + mod req; + mod secret; + mod uvsecret; + mod verify; + + /// utility functions for writing TESTS!!! + #[allow(dead_code)] + //hide any test helpers on docs! + #[doc(hidden)] + pub mod test_utils; + + } +} +/// Definitions and functions for interacting with the Ultravisor +pub mod uv { + pub use crate::uvdevice::{ + uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess, + }; + #[cfg(feature = "uvsecret")] + pub use crate::uvsecret::{ + secret_list::SecretList, + uvc::{AddCmd, ListCmd, LockCmd}, + }; +} + +/// Miscellaneous functions and definitions +pub mod misc { + + #[cfg(feature = "request")] + pub use crate::cli::{ + get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, CertificateOptions, STDIN, + STDOUT, + }; + pub use crate::log::PvLogger; + pub use crate::utils::{ + memeq, parse_hex, pv_guest_bit_set, read, read_exact_file, read_file, to_u16, to_u32, + try_parse_u128, try_parse_u64, write, write_file, Flags, Lsb0Flags64, Msb0Flags64, + }; + #[cfg(feature = "request")] + pub use crate::utils::{read_certs, read_crls}; +} + +#[cfg(feature = "request")] +pub use crate::error::HkdVerifyErrorType; +pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result}; + +/// Functionalities to build UV requests +#[doc = requires_feat!(request)] +pub mod request { + + cfg_if::cfg_if! { + if #[cfg(feature = "request")] { + pub use crate::brcb::{BootHdrTags, BootHdrMagic}; + pub use crate::crypto::{ + derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, hash, hkdf_rfc_5869, + random_array, Aes256Key, SymKey, SymKeyType, + }; + pub use crate::req::{Aad, Encrypt, Keyslot, ReqEncrCtx, Request}; + pub use crate::secret::{Secret, Zeroize}; + pub use crate::verify::HkdVerifier; + + /// Reexports some useful OpenSSL symbols + /// + #[doc = requires_feat!(request)] + pub mod openssl { + pub use openssl::error::ErrorStack; + pub use openssl::hash::MessageDigest; + pub use openssl::md::Md; + pub use openssl::pkey; + } + + } + } + + cfg_if::cfg_if! { + if #[cfg(feature = "uvsecret")] { + /// Functionalities for creating Add Secret requests + pub mod uvsecret { + #[cfg(feature = "request")] + pub use crate::uvsecret::{ + asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion,}, + ext_secret::ExtSecret, + guest_secret::GuestSecret, + }; + pub use crate::uvsecret::AddSecretMagic; + pub use crate::uvsecret::UserDataType; + } + } + } + + /// Version number of the request in system-endian + pub type RequestVersion = u32; + /// Request magic value + /// + /// The first 8 byte of a request providing an identifier of the request type + /// for programs + pub type RequestMagic = [u8; 8]; + /// A `MagicValue` is a bytepattern, that indicates if a byte slice contains the specified + /// (binary) data. + pub trait MagicValue { + /// Magic value as byte array + const MAGIC: [u8; N]; + /// Test whether the given slice starts with the magic value. + fn starts_with_magic(v: &[u8]) -> bool { + if v.len() < Self::MAGIC.len() { + return false; + } + crate::misc::memeq(&v[..Self::MAGIC.len()], &Self::MAGIC) + } + } +} + +/// Provides cargo version Info about this crate. +/// +/// Produces `pv-crate ` +pub const fn crate_info() -> &'static str { + concat!(env!("CARGO_PKG_NAME"), "-crate ", env!("CARGO_PKG_VERSION")) +} + +#[doc(hidden)] +#[macro_export] +macro_rules! file_acc_error { + ($ty: tt, $path:expr, $src: expr) => { + $crate::Error::FileAccess { + ty: $crate::FileAccessErrorType::$ty, + path: $path.to_string(), + source: $src, + } + }; +} + +#[macro_export] +/// Create a file wrapped in a [BufWriter] +/// +/// [BufWriter]: std::io#BufWriter +macro_rules! create_buffered_file { + ($path: expr) => { + std::io::BufWriter::new( + std::fs::File::create($path).map_err(|e| $crate::file_acc_error!(Create, $path, e))?, + ) + }; +} + +#[macro_export] +/// Open a file wrapped in a [BufReader] +/// +/// [BufReader]: std::io#BufReader +macro_rules! open_buffered_file { + ($path: expr) => { + std::io::BufReader::new( + std::fs::File::open($path).map_err(|e| $crate::file_acc_error!(Open, $path, e))?, + ) + }; +} diff --git a/rust/pv/src/log.rs b/rust/pv/src/log.rs new file mode 100644 index 00000000..127b95db --- /dev/null +++ b/rust/pv/src/log.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use log::{self, Level, LevelFilter, Log, Metadata, Record}; + +/// A simple Logger that prints to stderr if the verbosity level is high enough. +/// Prints log-level for Debug+Trace +#[derive(Clone, Default, Debug)] +pub struct PvLogger; + +fn to_level(verbosity: u8) -> LevelFilter { + match verbosity { + // Error and Warn on by default + 0 => LevelFilter::Warn, + 1 => LevelFilter::Info, + 2 => LevelFilter::Debug, + _ => LevelFilter::Trace, + } +} + +impl PvLogger { + /// Set self as the logger for this application. + /// + /// # Errors + /// + /// An error is returned if a logger has already been set. + pub fn start(&'static self, verbosity: u8) -> Result<(), log::SetLoggerError> { + log::set_logger(self).map(|()| log::set_max_level(to_level(verbosity))) + } +} + +impl Log for PvLogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + if record.level() > Level::Info { + eprintln!("{}: {}", record.level(), record.args()); + } else { + eprintln!("{}", record.args()); + } + } + } + fn flush(&self) {} +} diff --git a/rust/pv/src/req.rs b/rust/pv/src/req.rs new file mode 100644 index 00000000..cda448bf --- /dev/null +++ b/rust/pv/src/req.rs @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::misc::to_u32; +use crate::request::{ + derive_key, encrypt_aes, encrypt_aes_gcm, gen_ec_key, random_array, RequestMagic, + RequestVersion, SymKey, SymKeyType, +}; +use crate::{Error, Result}; +use openssl::bn::{BigNum, BigNumContext}; +use openssl::ec::{EcGroupRef, EcPointRef}; +use openssl::error::ErrorStack; +use openssl::hash::{hash, MessageDigest}; +use openssl::pkey::{PKey, PKeyRef, Private, Public}; +use std::convert::TryInto; +use zerocopy::{AsBytes, BigEndian, FromBytes, U32}; + +/// Encrypt a _secret_ using self and a given private key. +pub trait Encrypt { + /// Encrypts `secret` using `self` and `priv_key` the encryption. + /// + /// # Returns + /// the encrypted data. + /// + /// # Errors + /// + /// This function will return an error if OpenSSL could not encrypt the secret. + fn encrypt(&self, secret: &[u8], priv_key: &PKey) -> Result> { + let mut res = Vec::with_capacity(80); + self.encrypt_to(secret, priv_key, &mut res)?; + Ok(res) + } + + /// Encrypts `secret` using `self` and `priv_key` the encryption. + /// Appends the encrypted data to `to` + /// + /// # Returns + /// The encrypted data. + /// + /// # Errors + /// + /// This function will return an error if OpenSSL could not encrypt the secret. + fn encrypt_to(&self, secret: &[u8], priv_key: &PKey, to: &mut Vec) -> Result<()>; +} + +/// Types of Authenticated Data +pub enum Aad<'a> { + /// Authenticated Keyslot + Ks(&'a Keyslot), + /// Unchanged authenticated data + Plain(&'a [u8]), + /// Authenticated data that has to be encrypted in beforehand + Encr(&'a dyn Encrypt), +} + +/// IBM Z Host key-slot +/// +/// Layout in binary format: +/// ```none +/// _______________________________________________________________ +/// | Public Host Key Hash (32) | +/// | Wrapped(=Encrypted) Request Protection Key(32) | +/// | Key Slot Tag (16) | +/// |_____________________________________________________________| +/// ``` +#[derive(Debug, Clone)] +pub struct Keyslot(PKey); + +impl Keyslot { + /// Size of a host-key hash + pub const PHKH_SIZE: u32 = 0x20; + + /// Creates a new Keyslot from the provided public key + pub fn new(hostkey: PKey) -> Self { + Self(hostkey) + } +} + +impl Encrypt for Keyslot { + /// Encrypts the given request protection key `prot_key`. + /// + /// The AES256 encryption key is derived from `self` as public key, and `priv_key` as private key. + /// # Returns + /// The encrypted Keyslot. + /// + /// # Errors + /// + /// This function will return an error if OpenSSL could not encrypt the secret. + fn encrypt_to( + &self, + prot_key: &[u8], + priv_key: &PKey, + to: &mut Vec, + ) -> Result<()> { + let derived_key = derive_key(priv_key, &self.0)?; + let mut wrpk_and_kst = encrypt_aes_gcm(&derived_key.into(), &[0; 12], &[], prot_key)?; + let phk: EcdhPubkeyCoord = self.0.as_ref().try_into()?; + + to.reserve(80); + to.extend_from_slice(&hash(MessageDigest::sha256(), phk.as_ref())?); + to.append(&mut wrpk_and_kst); + Ok(()) + } +} + +/// Context used to mange the encryption of requests. +/// Intended to be used by [`Request`] implementations +#[derive(Debug)] +pub struct ReqEncrCtx { + iv: [u8; 12], + priv_key: PKey, + prot_key: SymKey, +} +impl ReqEncrCtx { + /// Create a new encryption context that uses AES256. + /// + /// * `iv` - Initialization vector for the request encryption + /// * `priv_key` - Private key to wrap [`Keyslot`] + /// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`] + /// + /// If an argument is set to `None` a ranom is generated + /// + /// # Errors + /// + /// This function will return an error if OpenSSL could not generate a random value. + pub fn new_aes_256(iv: I, priv_key: P, prot_key: S) -> Result + where + I: Into>, + P: Into>>, + S: Into>, + { + let iv = iv.into().unwrap_or(random_array()?); + let priv_key = priv_key.into().unwrap_or(gen_ec_key()?); + let prot_key = prot_key + .into() + .unwrap_or(SymKey::random(SymKeyType::Aes256)?); + Ok(ReqEncrCtx { + iv, + priv_key, + prot_key, + }) + } + /// + /// Create a new encryption context with random input values. + /// + /// # Errors + /// + /// This function will return an error if OpenSSL could not generate a random value. + pub fn random(ket_tp: SymKeyType) -> Result { + match ket_tp { + SymKeyType::Aes256 => Self::new_aes_256(None, None, None), + } + } + + ///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 + /// _______________________________________________________________ + /// | MAGIC (8) Version Number (4) Size (4)| + /// | IV (12) Reserved (4)| + /// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)| + /// | --------------------------------------------------- | + /// | Request type dependent AAD data | + /// |-------------------------------------------------------------| + /// ``` + /// + pub fn build_aad( + &self, + version: RequestVersion, + aad: &Vec, + encr_size: usize, + magic: O, + ) -> Result> + where + O: Into>, + { + self.build_aad_impl(version, aad, encr_size, magic.into()) + } + + /// Concrete implementation for [`ReqEncrCtx::build_aad`]. + fn build_aad_impl( + &self, + version: RequestVersion, + aad: &Vec, + encr_size: usize, + magic: Option, + ) -> Result> { + let nks = aad.iter().filter(|a| matches!(a, Aad::Ks(_))).count(); + let nks: u8 = match nks { + 0 => Err(Error::NoHostkey), + n if n > u8::MAX as usize => Err(Error::ManyHostkeys), + n => Ok(n as u8), + }?; + let mut auth_data: Vec = Vec::with_capacity(2048); + + //reserve space for the request header + auth_data.resize(std::mem::size_of::(), 0); + + for a in aad { + match a { + Aad::Plain(p) => auth_data.extend_from_slice(p), + Aad::Ks(ks) => { + ks.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)? + } + Aad::Encr(e) => { + e.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)? + } + } + } + + let rql = to_u32(auth_data.len() + encr_size + 16) + .ok_or_else(|| Error::Specification("Configured request size to large".to_string()))?; + let sea = to_u32(encr_size) + .ok_or_else(|| Error::Specification("Encrypted size to large".to_string()))?; + + let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic); + // copy request header to the start of the request + auth_data[..std::mem::size_of::()].copy_from_slice(req_hdr.as_bytes()); + Ok(auth_data) + } + + /// get the public coordinates from the private key (Customer private key) + /// # Errors + /// + /// This function will return an error if the public key could not be extracted by OpenSSL. + /// Very unlikely. + pub fn key_coords(&self) -> Result { + self.priv_key.as_ref().try_into().map_err(Error::Crypto) + } + + /// Encrypt confidential Data with this encryption context. + /// + /// * `conf` - data to be encrypted + /// + /// # Errors + /// + /// This function will return an error if the data could not be encrypted by OpenSSL. + pub fn encrypt(&self, conf: &[u8]) -> Result> { + encrypt_aes(&self.prot_key, &self.iv, conf) + } + + /// Encrypt confidential Data with this encryption context and provide a gcm tag. + /// + /// * `aad` - additional authentic data + /// * `conf` - data to be encrypted + /// + /// # Returns + /// [`Vec`] with the following content: + /// 1. `aad` + /// 2. `encr(conf)` + /// 3. `aes gcm tag` + /// + /// # 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> { + encrypt_aes_gcm(&self.prot_key, &self.iv, aad, conf) + } +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct EcdhPubkeyCoord([u8; 160]); +impl AsRef<[u8]> for EcdhPubkeyCoord { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + +/// Get the pub ecdh coordinates in the format the Ultravisor expects it: +/// The two coordinates are pdadded to 80 bytes each. +fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> { + const ECDH_PUB_KEY_COORD_POINT_SIZE: i32 = 0x50; + let mut x = BigNum::new()?; + let mut y = BigNum::new()?; + let mut bn_ctx = BigNumContext::new()?; + pkey.affine_coordinates(grp, &mut x, &mut y, &mut bn_ctx)?; + let mut coord: Vec = x.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE)?; + coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE)?); + Ok(coord.try_into().unwrap()) +} + +macro_rules! ecdh_from { + ($type: ty) => { + impl TryFrom<&PKeyRef<$type>> for EcdhPubkeyCoord { + type Error = ErrorStack; + fn try_from(key: &PKeyRef<$type>) -> Result { + let k = key.ec_key()?; + k.check_key()?; + let grp = k.group(); + let pub_key = k.public_key(); + let coord = get_pub_ecdh_points(pub_key, grp)?; + Ok(EcdhPubkeyCoord(coord)) + } + } + }; +} + +ecdh_from!(Private); +ecdh_from!(Public); + +/// Representation of the shared parts of the request header. +/// Used by [`ReqEncrCtx`] +#[repr(C)] +#[derive(Debug, Copy, Clone, AsBytes, FromBytes)] +struct RequestHdr { + magic: [u8; 8], + rqvn: U32, + rql: U32, + iv: [u8; 12], + reserved1c: [u8; 4], + reserved20: [u8; 7], + nks: u8, + reserved28: u32, + sea: U32, +} + +impl RequestHdr { + fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self { + Self { + magic: magic.unwrap_or_default(), + rqvn: rqvn.into(), + rql: rql.into(), + iv, + reserved1c: [0; 4], + reserved20: [0; 7], + nks, + reserved28: 0, + sea: sea.into(), + } + } +} + +/// A trait representing a request for the Ultravisor. +/// +/// All requests share a few things: +/// * All requests need to be encrypted on a trusted machine +/// * All requests have at least one Hostkeyslot +/// +/// The encryption setup is handled by [`ReqEncrCtx`]. Implementers need to pass the data to the +/// `ReqEncrCtx` when implementing `encrypt`. A hostkey should be represented by [`Keyslot`] during +/// encryption. +/// +/// An UV request consists of an authenticated area (AAD), an encrypted area (Encr) and a 16 byte tag. +/// The AAD contains a general header and Request type defined data (including Keyslots). +/// It is encrypted with an Request protection key (symmetric). This key is encrypted with a +/// (generated) private key and the public key of the host system (Host key) +/// ```none +/// _______________________________________________________________ +/// | MAGIC (8) Version Number (4) Size (4)| +/// | IV (12) Reserved (4)| +/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)| +/// | --------------------------------------------------- | +/// | Request type dependent AAD data | +/// | ---------------------------------------------------- | +/// | Encrypted (request type dependent) data | +/// | ---------------------------------------------------- | +/// | AES GCM Tag (16) | +/// |_____________________________________________________________| +///``` +pub trait Request { + /// Encrypt the request into its binary format + /// + /// # Errors + /// + /// This function will return an error if the encryption fails, the request does not have at + /// least a hostkey, or other implementation dependent contracts are not met. + fn encrypt(&self, ctx: &ReqEncrCtx) -> Result>; + /// Add a host-key to this request + /// + /// Must be called at least once, otherwise {`Request::encrypt`} will fail + fn add_hostkey(&mut self, hostkey: PKey); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::get_test_asset; + use crate::request::SymKey; + use crate::test_utils::*; + use openssl::ec::EcGroup; + use openssl::nid::Nid; + + static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes(); + + #[test] + fn encr_build_aad() { + let (cust_key, host_key) = get_test_keys(); + let ks = Keyslot::new(host_key); + let ctx = ReqEncrCtx::new_aes_256( + Some([0x11; 12]), + Some(cust_key), + Some(SymKey::Aes256([0x17; 32].into())), + ) + .unwrap(); + let v = [0x55; 8]; + let aad = Aad::Plain(&v); + let aad = ctx + .build_aad(0x200, &vec![aad, Aad::Ks(&ks)], 16, Some(TEST_MAGIC)) + .unwrap(); + + let mut aad_exp = vec![ + 0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, //progr + 0, 0, 2, 0, // vers + 0, 0, 0, 168, //size + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res + 1, //nks + 0, 0, 0, 0, // res + 0, 0, 0, 16, // sea + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, //aad + ]; + aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin")); + assert_eq!(&aad, &aad_exp); + } + + #[test] + fn encr_build_aad_nks_no() { + let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap(); + + let aad = Vec::::new(); + + let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)); + assert!(matches!(aad, Err(Error::NoHostkey))); + } + + #[test] + fn encr_build_aad_nks_many() { + let (_, host_key) = get_test_keys(); + let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap(); + + let ks: Vec = (0..257).map(|_| Keyslot::new(host_key.clone())).collect(); + let mut aad = Vec::::new(); + ks.iter().for_each(|ks| aad.push(Aad::Ks(ks))); + + let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)); + assert!(matches!(aad, Err(Error::ManyHostkeys))); + } + #[test] + fn encr_build_aad_nks() { + let (_, host_key) = get_test_keys(); + let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap(); + + let ks = vec![ + Keyslot::new(host_key.clone()), + Keyslot::new(host_key.clone()), + Keyslot::new(host_key.clone()), + ]; + let mut aad = Vec::::new(); + ks.iter().for_each(|ks| aad.push(Aad::Ks(ks))); + + let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)).unwrap(); + + assert_eq!(aad.get(39).unwrap(), &3u8); + } + + #[test] + fn req_hdr() { + let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None); + let hdr_bin = hdr.as_bytes(); + let hdr_bin_exp = [ + 0u8, 0, 0, 0, 0, 0, 0, 0, //magic + 0, 0, 2, 0, // vers + 0, 0, 0, 22, //size + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res + 15, //nks + 0, 0, 0, 0, // res + 0, 0, 0, 44, // sea + ]; + assert_eq!(hdr_bin, &hdr_bin_exp); + } + + #[test] + fn req_hdr2() { + let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC)); + let hdr_bin = hdr.as_bytes_mut(); + let hdr_bin_exp = [ + 0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, //magic + 0, 0, 2, 0, // vers + 0, 0, 0x12, 0x34, //size + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //res + 15, //nks + 0, 0, 0, 0, // res + 0, 0, 0, 44, // sea + ]; + assert_eq!(hdr_bin, &hdr_bin_exp); + } + + #[test] + fn keyslot() { + let (cust_key, host_key) = get_test_keys(); + let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec(); + + let keyslot = Keyslot::new(host_key); + let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap(); + + assert_eq!(exp_keyslot, encr_ks); + + let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap(); + assert_ne!(exp_keyslot, encr_ks); + } + + #[test] + fn get_pub_ecdh_points() { + let (cust_key, _) = get_test_keys(); + + let pub_key = get_test_asset!("keys/public_cust.bin"); + + assert_eq!(pub_key.len(), 160); + + let points = cust_key.ec_key().unwrap(); + let points = points.public_key(); + let grp = EcGroup::from_curve_name(Nid::SECP521R1).unwrap(); + + let points = super::get_pub_ecdh_points(points, &grp).unwrap(); + + assert_eq!(&points, pub_key); + } +} diff --git a/rust/pv/src/secret.rs b/rust/pv/src/secret.rs new file mode 100644 index 00000000..cdef9ef8 --- /dev/null +++ b/rust/pv/src/secret.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use std::fmt::Debug; + +/// Trait for securely zeroizing memory. +/// +/// To be used with [`Secret`] +pub trait Zeroize { + /// Reliably overwrites the given buffer with zeros, + fn zeroize(&mut self); +} + +/* Automatically impl Zeroize for u8 arrays */ +impl Zeroize for [u8; COUNT] { + /// Reliably overwrites the given buffer with zeros, + /// by performing a volatile write followed by a memory barrier + fn zeroize(&mut self) { + // SAFETY: given buffer(self) has the correct (compile time) size + unsafe { std::ptr::write_volatile(self, [0u8; COUNT]) }; + std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst); + } +} + +impl Zeroize for Vec { + /// 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 + let mut dst = self.as_mut_ptr(); + for _ in 0..self.capacity() { + // SAFETY: + // * Vec allocated at least capacity elements continuously + // * dst points always to a valid location + unsafe { + dst = dst.add(1); + std::ptr::write_volatile(dst, 0) + } + } + std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst); + } +} + +/// Thin wrapper around an type implementing Zeroize. +/// +/// A `Secret` 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]>) { +/// println!("value: {value:?}"); +/// } +/// # fn main() { +/// foo([1,2].into()); +/// // prints: +/// // in debug builds: +/// // value: Secret([1, 2]) +/// // in release builds: +/// // value: Secret(***) +/// # } +/// ``` +#[derive(Clone, PartialEq, Eq, Default)] +pub struct Secret(C); +impl Secret { + /// Convert a type into a self overwriting one. + /// + /// Prefer using [`Into`] + pub fn new(v: C) -> Self { + Secret(v) + } + + /// Get a reference to the contained value + pub fn value(&self) -> &C { + &self.0 + } + /// Get a imutable 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 + /// or operate on slices. The old locations can and will **NOT** be zeroized. + pub fn value_mut(&mut self) -> &mut C { + &mut self.0 + } +} + +impl Debug for Secret { + #[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(***)"); + + let mut b = f.debug_tuple("Secret"); + b.field(&self.0); + b.finish() + } +} + +impl From for Secret { + fn from(v: C) -> Secret { + Secret(v) + } +} + +impl Zeroize for Secret { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl Drop for Secret { + fn drop(&mut self) { + self.0.zeroize(); + } +} diff --git a/rust/pv/src/test_utils.rs b/rust/pv/src/test_utils.rs new file mode 100644 index 00000000..3859950c --- /dev/null +++ b/rust/pv/src/test_utils.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +// DO NOT USE ANY OF THESE ITEMS IN PRODUCTION CODE +// USED FOR INTERNAL UNIT AND FVT TESTING ONLY!!! +use openssl::{ + bn::BigNum, + ec::{EcGroup, EcKey}, + error::ErrorStack, + nid::Nid, + pkey::{PKey, Private, Public}, + x509::{X509Crl, X509}, +}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +/// TEST ONLY! Loads the specified asset into the binary at compile time. +/// +/// For testing-assets only! +/// The asset must be present at `{crate}/test/assets/{file}` +#[doc(hidden)] +#[macro_export] +macro_rules! get_test_asset { + ($file:expr) => { + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/assets/", $file)) + }; +} + +pub fn get_cert_asset_path>(path: P) -> PathBuf { + let mut p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + p.push("tests"); + p.push("assets"); + p.push("cert"); + p.push(path); + println!("CERT path: {}", p.to_str().unwrap()); + 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 +pub fn get_cert_asset(path: &'static str) -> Vec { + let p = get_cert_asset_path(path); + fs::read(p).unwrap() +} + +/// TEST ONLY! Load cert found in the asset path +/// +/// panic on errors +pub fn load_gen_cert(asset_path: &'static str) -> X509 { + let buf = get_cert_asset(asset_path); + let mut cert = X509::from_der(&buf) + .map(|crt| vec![crt]) + .or_else(|_| X509::stack_from_pem(&buf)) + .unwrap(); + assert_eq!(cert.len(), 1); + cert.pop().unwrap() +} + +/// TEST ONLY! Load the crl found in the asset path +/// +/// panic on errors +pub fn load_gen_crl(asset_path: &'static str) -> X509Crl { + let buf = get_cert_asset(asset_path); + + X509Crl::from_der(&buf) + .or_else(|_| X509Crl::from_pem(&buf)) + .unwrap() +} + +/// TEST ONLY! Get a fixed private/public pair and a fixed public key +/// +/// Intened for TESTING only. All parts of the key including the private key are checked in git and +/// visible for the public +pub fn get_test_keys() -> (PKey, PKey) { + let pub_key = get_test_asset!("keys/public_cust.bin"); + let priv_key = get_test_asset!("keys/private_cust.bin"); + let host_key = get_test_asset!("keys/host.pem.crt"); + + assert_eq!(pub_key.len(), 160); + assert_eq!(priv_key.len(), 80); + + let cust_key = get_keypair(pub_key, priv_key).unwrap(); + let host_key = X509::from_pem(host_key).unwrap().public_key().unwrap(); + + (cust_key, host_key) +} + +fn read_ecdh_pubkey(coords: &[u8]) -> Result, ErrorStack> { + assert!(coords.len() == 160); + let x = BigNum::from_slice(&coords[..80])?; + let y = BigNum::from_slice(&coords[80..])?; + let group = EcGroup::from_curve_name(Nid::SECP521R1)?; + + let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?; + PKey::from_ec_key(key) +} + +fn get_keypair(pub_coords: &[u8], priv_num: &[u8]) -> Result, ErrorStack> { + assert!(pub_coords.len() == 160); + assert!(priv_num.len() == 80); + let pub_key = read_ecdh_pubkey(pub_coords)?; + let pub_key = pub_key.ec_key()?; + let pub_key = pub_key.public_key(); + let priv_key = BigNum::from_slice(priv_num)?; + let group = EcGroup::from_curve_name(Nid::SECP521R1)?; + + let key = EcKey::from_private_components(&group, &priv_key, pub_key)?; + key.check_key()?; + PKey::from_ec_key(key) +} diff --git a/rust/pv/src/utils.rs b/rust/pv/src/utils.rs new file mode 100644 index 00000000..33bcd4d0 --- /dev/null +++ b/rust/pv/src/utils.rs @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::{ + error::{bail_spec, file_error, path_to_str}, + Error, FileIoErrorType, Result, +}; + +#[cfg(feature = "request")] +use openssl::x509::X509Crl; +#[cfg(feature = "request")] +use openssl::x509::X509; +use std::io::{Read, Write}; +use std::path::Path; +use zerocopy::{AsBytes, BigEndian, FromBytes, U64}; + +/// Asserts a constant expression evaluates to `true`. +/// +/// If the expression is not evaluated to `true` the compilation will fail. +#[macro_export] +macro_rules! static_assert { + ($condition:expr) => { + const _: () = core::assert!($condition); + }; +} + +/// Asserts that a type has a specific size. +/// +/// Useful to validate structs that are passed to C code. +/// If the expression is not evaluated to `true` the compilation will fail. +/// +/// # Example +/// ```rust +/// # use pv::assert_size; +/// # fn main() {} +/// #[repr(C)] +/// struct c_struct { +/// v: u64, +/// } +/// assert_size!(c_struct, 8); +/// // assert_size!(c_struct, 7);//won't compile +/// ``` +#[macro_export] +macro_rules! assert_size { + ($t:ty, $sz:expr ) => { + $crate::static_assert!(::std::mem::size_of::<$t>() == $sz); + }; +} + +/// Trait that describes bitflags, represented by `T`. +pub trait Flags: From + for<'a> From<&'a T> { + /// Set the specified bit to one. + /// # Panics + ///Panics if bit is >= 64 + fn set_bit(&mut self, bit: u8); + /// Set the specified bit to zero. + /// # Panics + ///Panics if bit is >= 64 + fn unset_bit(&mut self, bit: u8); + /// Test if the specified bit is set. + /// # Panics + ///Panics if bit is >= 64 + fn is_set(&self, bit: u8) -> bool; +} + +/// Bitflags in MSB0 ordering +/// +/// Wraps an u64 to set/get individual bits +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)] +pub struct Msb0Flags64(U64); +impl Flags for Msb0Flags64 { + #[track_caller] + fn set_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v |= 1 << (63 - bit); + self.0.set(v) + } + + #[track_caller] + fn unset_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v &= !(1 << (63 - bit)); + self.0.set(v) + } + + #[track_caller] + fn is_set(&self, bit: u8) -> bool { + assert!(bit < 64, "Flag bit set to greater than 63"); + self.0.get() & (1 << (63 - bit)) > 0 + } +} + +impl From for Msb0Flags64 { + fn from(value: u64) -> Self { + Self(value.into()) + } +} + +impl From<&u64> for Msb0Flags64 { + fn from(value: &u64) -> Self { + (*value).into() + } +} + +/// Bitflags in LSB0 ordering +/// +/// Wraps an u64 to set/get individual bits +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, AsBytes, FromBytes)] +pub struct Lsb0Flags64(U64); +impl Flags for Lsb0Flags64 { + #[track_caller] + fn set_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v |= 1 << bit; + self.0.set(v) + } + + #[track_caller] + fn unset_bit(&mut self, bit: u8) { + assert!(bit < 64, "Flag bit set to greater than 63"); + let mut v = self.0.get(); + v &= !(1 << bit); + self.0.set(v) + } + + #[track_caller] + fn is_set(&self, bit: u8) -> bool { + assert!(bit < 64, "Flag bit set to greater than 63"); + self.0.get() & (1 << bit) > 0 + } +} + +impl From for Lsb0Flags64 { + fn from(value: u64) -> Self { + Self(value.into()) + } +} + +impl From<&u64> for Lsb0Flags64 { + fn from(value: &u64) -> Self { + (*value).into() + } +} + +/// Tries to convert a BE hex string into a 128 unsigned integer +/// The hexstring must contain 32chars of hexdigits +/// +/// * `hex_str` - string to convert can be prepended with "0x" +/// * `ctx` - Error context string in case of an error +/// ```rust +/// # use std::error::Error; +/// # use pv::misc::try_parse_u128; +/// # fn main() -> Result<(), Box> { +/// let hex = "11223344556677889900aabbccddeeff"; +/// try_parse_u128(&hex, "The test")?; +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// If `hex_string` is not a 32 byte hex string an Error appears +pub fn try_parse_u128(hex_str: &str, ctx: &str) -> Result<[u8; 16]> { + let hex_str = if hex_str.starts_with("0x") { + hex_str.split_at(2).1 + } else { + hex_str + }; + if hex_str.len() != 32 { + bail_spec!(format!( + "{ctx} hexstring must be 32chars long to cover all 16 bytes" + )); + } + parse_hex(hex_str).try_into().map_err(|_| { + Error::Specification(format!( + "{ctx} hexstring must be 32chars long to cover all 16 bytes" + )) + }) +} + +/// Tries to convert a BE hex string into a 64 unsigned integer +/// The hexstring must *NOT* contain 16 chars of hexdigits, but +/// 16 chars at most. +/// +/// * `hex_str` - string to convert can be prepended with "0x" +/// * `ctx` - Error context string in case of an error +/// ```rust +/// # use std::error::Error; +/// # use pv::misc::try_parse_u64; +/// # fn main() -> Result<(), Box> { +/// let hex = "1234567890abcdef"; +/// try_parse_u64(&hex, "The test")?; +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// If `hex_string` is not a 32 byte hex string an Error appears +pub fn try_parse_u64(hex_str: &str, ctx: &str) -> Result { + let hex_str = if hex_str.starts_with("0x") { + hex_str.split_at(2).1 + } else { + hex_str + }; + if hex_str.len() > 16 { + bail_spec!(format!( + "{ctx} hexstring {hex_str} must be max 16 chars long" + )); + } + Ok(u64::from_str_radix(hex_str, 16)?) +} + +/// Read exactly COUNT bytes into the buffer. +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// # Errors +/// If this function encounters an "end of file" before completely filling +/// the buffer, it returns an error. The contents of `buf` are unspecified in this case. +/// +/// If any other read error is encountered then this function immediately +/// returns. The contents of `buf` are unspecified in this case. +/// +/// If this function returns an error, it is unspecified how many bytes it +/// has read, but it will never read more than would be necessary to +/// completely fill the buffer. +pub fn read_exact_file, const COUNT: usize>( + path: P, + ctx: &str, +) -> Result<[u8; COUNT]> { + let mut f = std::fs::File::open(&path).map_err(|e| Error::FileAccess { + ty: crate::FileAccessErrorType::Open, + path: path_to_str!(path).to_string(), + source: e, + })?; + + if f.metadata()?.len() as usize != COUNT { + bail_spec!(format!("{ctx} must be exactly {COUNT} bytes long")); + } + + let mut buf = [0; COUNT]; + f.read_exact(&mut buf) + .map_err(|e| file_error!(Read, ctx, path_to_str!(path).to_string(), e))?; + Ok(buf) +} + +/// Read content from a file and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::read` produces +pub fn read_file>(path: P, ctx: &str) -> Result> { + std::fs::read(&path).map_err(|e| { + file_error!( + Read, + ctx, + path.as_ref().to_str().unwrap_or("no UTF-8 path"), + e + ) + }) +} + +/// Reads all content from a [`std::io::Read`] and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::write` produces +pub fn read(rd: &mut R, path: &str, ctx: &str) -> Result> { + let mut buf = vec![]; + rd.read_to_end(&mut buf).map_err(|e| Error::FileIo { + ty: FileIoErrorType::Write, + ctx: ctx.to_string(), + path: path.to_string(), + source: e, + })?; + Ok(buf) +} + +/// write content to a file and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::write` produces +pub fn write_file>(path: &str, data: D, ctx: &str) -> Result<()> { + std::fs::write(path, data.as_ref()).map_err(|e| Error::FileIo { + ty: FileIoErrorType::Write, + ctx: ctx.to_string(), + path: path.to_string(), + source: e, + }) +} + +/// Write content to a [`std::io::Write`] and add context in case of an error +/// +/// * `path` - Path to file +/// * `ctx` - Error context string in case of an error +/// +/// +/// # Errors +/// Passes through any kind of error `std::fs::write` produces +pub fn write, W: Write>(wr: &mut W, data: D, path: &str, ctx: &str) -> Result<()> { + wr.write_all(data.as_ref()).map_err(|e| Error::FileIo { + ty: FileIoErrorType::Write, + ctx: ctx.to_string(), + path: path.to_string(), + source: e, + }) +} + +/// Read all CRLs from the buffer and parse them into a vector. +/// +/// # Errors +/// +/// This function will return an error if the underlying openssl implementation cannot parse `buf` +/// as `DER` or `PEM`. +/// +/// Requires the `request` feature. +#[cfg(feature = "request")] +pub fn read_crls(buf: &[u8]) -> Result> { + use openssl_extensions::crl::StackableX509Crl; + X509Crl::from_der(buf) + .map(|crl| vec![crl]) + .or_else(|_| StackableX509Crl::stack_from_pem(buf)) + .map_err(Error::Crypto) +} + +/// Read all certificates from the buffer and parse them into a vector. +/// +/// # Errors +/// +/// This function will return an error if the underlying openssl implementation cannot parse `buf` +/// as `DER` or `PEM`. +/// +/// Requires the `request` feature. +#[cfg(feature = "request")] +pub fn read_certs(buf: &[u8]) -> Result> { + X509::from_der(buf) + .map(|crt| vec![crt]) + .or_else(|_| X509::stack_from_pem(buf)) + .map_err(Error::Crypto) +} + +macro_rules! usize_to_ui { + ($(#[$attr:meta])* => $t: ident, $name:ident) => { + ///Converts an [`usize`] to an [` + $(#[$attr])* + ///`] if possible + pub fn $name(u: usize) -> Option<$t> { + if u > $t::MAX as usize { + None + } else { + Some(u as $t) + } + } + + } +} + +usize_to_ui! { +#[doc = r"u32"] +=> u32, to_u32} +usize_to_ui! { +#[doc = r"u16"] +=> u16, to_u16} + +/// Test if both slices contain the exact same bytes. +/// +/// Do not use this to compare cryptographic values (i.e. hashes) +pub fn memeq(lhs: &[u8], rhs: &[u8]) -> bool { + let size = lhs.len(); + + size == rhs.len() + && unsafe { + let l = lhs as *const _ as _; + let r = rhs as *const _ as _; + (l as usize) == (r as usize) || libc::memcmp(l, r, size) == 0 + } +} + +/// Converts the hexstring into a byte vector. +/// +/// Stops if the end or until a non hex chat is found +pub fn parse_hex(hex_str: &str) -> Vec { + let mut hex_bytes = hex_str.as_bytes().iter().map_while(|b| match b { + b'0'..=b'9' => Some(b - b'0'), + b'a'..=b'f' => Some(b - b'a' + 10), + b'A'..=b'F' => Some(b - b'A' + 10), + _ => None, + }); + + let mut bytes = Vec::new(); + while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) { + bytes.push(h << 4 | l) + } + bytes +} +/// Report if the `prot_virt_guest` sysfs entry is one. +/// +/// If the entry does not exist returns false. +/// +/// for non-s390-architectures: +/// Returns always false +/// A non-s390 system cannot be a secure execution guest. +#[allow(unreachable_code)] +pub fn pv_guest_bit_set() -> bool { + #[cfg(not(target_arch = "s390x"))] + return false; + //s390 branch + let v = std::fs::read("/sys/firmware/uv/prot_virt_guest").unwrap_or_else(|_| vec![0]); + let v: u8 = String::from_utf8_lossy(&v[..1]).parse().unwrap_or(0); + v == 1 +} + +#[cfg(test)] +mod tests { + use std::usize; + + use super::*; + #[cfg(feature = "request")] + use crate::test_utils::*; + + #[test] + fn msb_flags() { + let v = 17; + let v_flag: Msb0Flags64 = v.into(); + assert_eq!(v, v_flag.0.get()); + + let mut v: Msb0Flags64 = 4.into(); + v.unset_bit(61); + assert_eq!(v.0.get(), 0); + v.set_bit(61); + assert_eq!(4, v.0.get()); + + let mut v = Msb0Flags64::default(); + v.set_bit(0); + assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(0); + assert_eq!(&[0x80, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(1); + assert_eq!(&[0xc0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(2); + assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.set_bit(3); + assert_eq!(&[0xf0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + + v.unset_bit(3); + assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + v.unset_bit(3); + assert_eq!(&[0xe0, 0, 0, 0, 0, 0, 0, 0], v.as_bytes()); + + v.set_bit(16); + assert_eq!(&[0xe0, 0, 0x80, 0, 0, 0, 0, 0], v.as_bytes()); + } + + #[test] + #[should_panic] + fn msb_flags_set_panic() { + Msb0Flags64::default().set_bit(64) + } + + #[test] + #[should_panic] + fn msb_flags_unset_panic() { + Msb0Flags64::default().unset_bit(64) + } + + #[test] + fn lsb_flags() { + let v = 17; + let v_flag: Lsb0Flags64 = v.into(); + assert_eq!(v, v_flag.0.get()); + + let mut v: Lsb0Flags64 = 4.into(); + v.unset_bit(2); + assert_eq!(v.0.get(), 0); + v.set_bit(2); + assert_eq!(4, v.0.get()); + + let mut v = Lsb0Flags64::default(); + v.set_bit(0); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes()); + v.set_bit(0); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 1], v.as_bytes()); + v.set_bit(1); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 3], v.as_bytes()); + v.set_bit(2); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); + v.set_bit(3); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 0xf], v.as_bytes()); + + v.unset_bit(3); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); + v.unset_bit(3); + assert_eq!(&[0, 0, 0, 0, 0, 0, 0, 7], v.as_bytes()); + + v.set_bit(16); + assert_eq!(&[0, 0, 0, 0, 0, 1, 0, 7], v.as_bytes()); + } + + #[test] + #[should_panic] + fn lsb_flags_set_panic() { + Lsb0Flags64::default().set_bit(64) + } + + #[test] + #[should_panic] + fn lsb_flags_unset_panic() { + Lsb0Flags64::default().unset_bit(64) + } + #[test] + fn parse_hex() { + let s = "123456acbef0"; + let exp = vec![0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; + assert_eq!(super::parse_hex(&s), exp); + + let s = "00123456acbef0"; + let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; + assert_eq!(super::parse_hex(&s), exp); + + let s = "00123456acbef0ii90"; + let exp = vec![0, 0x12, 0x34, 0x56, 0xac, 0xbe, 0xf0]; + assert_eq!(super::parse_hex(&s), exp); + } + + #[test] + #[cfg(feature = "request")] + fn read_crls() { + let crl = get_cert_asset("ibm.crl"); + let crl_der = get_cert_asset("der.crl"); + let fail = get_cert_asset("ibm.crt"); + assert_eq!(super::read_crls(&crl).unwrap().len(), 1); + assert_eq!(super::read_crls(&crl_der).unwrap().len(), 1); + assert_eq!(super::read_crls(&fail).unwrap().len(), 0); + } + + #[test] + #[cfg(feature = "request")] + fn read_certs() { + let crt = get_cert_asset("ibm.crt"); + let crt_der = get_cert_asset("der.crt"); + let fail = get_cert_asset("ibm.crl"); + assert_eq!(super::read_certs(&crt).unwrap().len(), 1); + assert_eq!(super::read_certs(&crt_der).unwrap().len(), 1); + assert_eq!(super::read_certs(&fail).unwrap().len(), 0); + } + + #[test] + fn to_u32() { + assert_eq!(Some(17), super::to_u32(17)); + assert_eq!(Some(0), super::to_u32(0)); + assert_eq!(Some(u32::MAX), super::to_u32(u32::MAX as usize)); + assert_eq!(None, super::to_u32(u32::MAX as usize + 1)); + assert_eq!(None, super::to_u32(usize::MAX)); + } + + #[test] + fn parse_u128() { + assert!(matches!( + try_parse_u128("123456", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("-1234", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0011223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("dd11223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("-1223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + + assert!(matches!( + try_parse_u128("0x123456", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("-0x1234", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0x0011223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0xdd11223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + assert!(matches!( + try_parse_u128("0x-1223344556677889900aabbccddeeff", ""), + Err(Error::Specification(_)) + )); + + assert_eq!( + [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("11223344556677889900aabbccddeeff", "").unwrap() + ); + assert_eq!( + [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("0x11223344556677889900aabbccddeeff", "").unwrap() + ); + assert_eq!( + [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap() + ); + assert_eq!( + [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff + ], + try_parse_u128("00112233445566778899aabbccddeeff", "").unwrap() + ); + } + + #[test] + fn memeq() { + let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + let b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1]; + let c = [0, 0, 1, 2, 3, 4]; + + assert!(super::memeq(&a, &a)); + assert!(super::memeq(&a, &a.clone())); + assert!(!super::memeq(&b, &a)); + assert!(!super::memeq(&b, &c)); + assert!(!super::memeq(&b, &vec![])); + } +} diff --git a/rust/pv/src/uvdevice.rs b/rust/pv/src/uvdevice.rs new file mode 100644 index 00000000..a4852586 --- /dev/null +++ b/rust/pv/src/uvdevice.rs @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![allow(non_camel_case_types)] +use crate::file_acc_error; +use crate::{Error, Result}; +use libc::c_ulong; +use log::debug; +use std::convert::TryInto; +use std::fs::File; +use std::os::unix::prelude::{AsRawFd, RawFd}; + +#[cfg(not(test))] +use ::libc::ioctl; +#[cfg(test)] +use test::mock_libc::ioctl; + +/// Contains the rust representation of asm/uvdevice.h +/// from kernel version: 6.5 verify +mod ffi; +mod info; +mod test; +pub use ffi::uv_ioctl; + +pub use info::UvDeviceInfo; +#[allow(dead_code)] //TODO rm when pv learns attestation +pub type AttestationUserData = [u8; ffi::UVIO_ATT_USER_DATA_LEN]; + +///Configuration Unique Id of the Secure Execution guest +pub type ConfigUid = [u8; ffi::UVIO_ATT_UID_LEN]; + +/// Bitflags as used by the Ultravisor in MSB0 ordering +/// +/// Wraps an u64 to set/get individual bits +pub type UvFlags = crate::misc::Msb0Flags64; + +/// Fire an ioctl. +/// +/// # Safety: +/// Raw fd must point to an open file +fn ioctl_raw(raw_fd: RawFd, cmd: c_ulong, cb: &mut IoctlCb) -> Result<()> { + debug!("calling unsafe fn wrapper uv::ioctl_raw with {raw_fd:#x?}, {cmd:#x?}, {cb:?}"); + + let rc; + + // Get the raw pointer and do an ioctl. + // + // SAFETY: the passed pointer points to a valid memory region that + // contains the expected C-struct. The struct outlives this function. + unsafe { + rc = ioctl(raw_fd, cmd, cb.as_ptr_mut()); + } + + debug!("ioctl resulted with {cb:?}"); + match rc { + 0 => Ok(()), + //NOTE io::Error handles all errnos ioctl uses + _ => Err(std::io::Error::last_os_error().into()), + } +} + +/// Converts UV return codes into human readable error messages +fn rc_fmt(rc: u16, rrc: u16, cmd: &mut C) -> &'static str { + let s = match (rc, rrc) { + (0x0000, _) => Some("invalid rc"), + (0x0002, _) => Some("invalid UV command"), + (0x0005, _) => Some("request has an invalid size"), + (0x0030, _) => Some("home address space control bit has R-bit set to one"), + (0x0031, _) => Some("access exception"), + (0x0032, _) => Some("request contains virtual address translating to an invalid address"), + (UvDevice::RC_MORE_DATA, _) => unreachable!("This is no Error!!!!"), + (UvDevice::RC_SUCCESS, _) => unreachable!("This is no Error!!!!"), + + _ => cmd.rc_fmt(rc, rrc), + }; + s.unwrap_or("unexpected error-code") +} + +/// Ultravisor Command. +pub trait UvCmd { + /// Returns the uvdevice IOCTL command that his command uses. + /// + /// # Returns + /// The IOCTL cmd for this UvCmd usually sth like `uv_ioctl!(CMD_NR)` + fn cmd(&self) -> u64; + /// Converts UV return codes into human readable error messages + /// + /// no need to handle `0x0000, 0x0001, 0x0002, 0x0005, 0x0030, 0x0031, 0x0032, 0x0100` + fn rc_fmt(&self, rc: u16, rrc: u16) -> Option<&'static str>; + /// Returns data used by this command if available. + fn data(&mut self) -> Option<&mut [u8]> { + None + } +} + +/// [`UvDevice`] IOCTL control block. +#[derive(Debug)] +struct IoctlCb(ffi::uvio_ioctl_cb); +impl IoctlCb { + fn new(data: Option<&mut [u8]>) -> Result { + let (data_raw, data_size) = match data { + Some(data) => ( + data.as_mut_ptr(), + data.len() + .try_into() + .map_err(|_| Error::Specification("passed data too large".to_string()))?, + ), + None => (std::ptr::null_mut(), 0), + }; + + Ok(Self(ffi::uvio_ioctl_cb { + flags: 0, + uv_rc: 0, + uv_rrc: 0, + argument_addr: data_raw as u64, + argument_len: data_size, + reserved14: [0; 44], + })) + } + + fn rc(&self) -> u16 { + self.0.uv_rc + } + + fn rrc(&self) -> u16 { + self.0.uv_rrc + } + + fn as_ptr_mut(&mut self) -> *mut ffi::uvio_ioctl_cb { + &mut self.0 as *mut _ + } +} + +/// The Ultravisor has two codes that represent a successful execution. +/// These are represented by this enum. +#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UvcSuccess { + /// Command executed successfully + RC_SUCCESS = UvDevice::RC_SUCCESS, + /// Command executed successfully, but there is more data available and the buffer was to small + /// to hold it all. The returned data is still valid. + RC_MORE_DATA = UvDevice::RC_MORE_DATA, +} + +/// The UvDevice is a (virtual) device on s390 machines to send Ultravisor commands from userspace. +pub struct UvDevice(File); + +impl UvDevice { + const RC_SUCCESS: u16 = 0x0001; + const RC_MORE_DATA: u16 = 0x0100; + const PATH: &'static str = "/dev/uv"; + + /// IOCTL number for the info UVC + pub const INFO_NR: u8 = ffi::UVIO_IOCTL_UVDEV_INFO_NR; + /// IOCTL number for the attestation UVC + pub const ATTESTATION_NR: u8 = ffi::UVIO_IOCTL_ATT_NR; + /// IOCTL number for the add secret UVC + pub const ADD_SECRET_NR: u8 = ffi::UVIO_IOCTL_ADD_SECRET_NR; + /// IOCTL number for the list secret UVC + pub const LIST_SECRET_NR: u8 = ffi::UVIO_IOCTL_LIST_SECRETS_NR; + /// IOCTL number for the lock ksecret UVC + pub const LOCK_SECRET_NR: u8 = ffi::UVIO_IOCTL_LOCK_SECRETS_NR; + /// Maximum length for addsecret requests + pub const ADD_SECRET_MAX_LEN: usize = ffi::UVIO_ADD_SECRET_MAX_LEN; + /// Size of the buffer for list secret requests + pub const LIST_SECRETS_LEN: usize = ffi::UVIO_LIST_SECRETS_LEN; + + /// Open the uvdevice located at `/dev/uv` + /// + /// # Errors + /// + /// This function will return an error if the device file cannot be opened. + pub fn open() -> Result { + Ok(Self( + std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(UvDevice::PATH) + .map_err(|e| file_acc_error!(Open, UvDevice::PATH, e))?, + )) + } + + /// Send an Ultravisor Command via this uvdevice. + /// + /// This works by sending an IOCTL to the uvdevice. + /// # Errors + /// + /// This function will return an error if the IOCTL fails or the Ultravisor does not report + /// a success. + /// # Returns + /// [`UvcSuccess`] if the UVC ececuted successfully + pub fn send_cmd(&self, cmd: &mut C) -> Result { + let mut cb = IoctlCb::new(cmd.data())?; + ioctl_raw(self.0.as_raw_fd(), cmd.cmd(), &mut cb)?; + + match (cb.rc(), cb.rrc()) { + (Self::RC_SUCCESS, _) => Ok(UvcSuccess::RC_SUCCESS), + (Self::RC_MORE_DATA, _) => Ok(UvcSuccess::RC_MORE_DATA), + (rc, rrc) => Err(Error::Uv { + rc, + rrc, + msg: rc_fmt(rc, rrc, cmd), + }), + } + } +} diff --git a/rust/pv/src/uvdevice/ffi.rs b/rust/pv/src/uvdevice/ffi.rs new file mode 100644 index 00000000..706e77d5 --- /dev/null +++ b/rust/pv/src/uvdevice/ffi.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::{assert_size, static_assert}; +use zerocopy::{AsBytes, FromBytes}; + +pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000; +pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000; +pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000; +pub const UVIO_ADD_SECRET_MAX_LEN: usize = 0x100000; +pub const UVIO_LIST_SECRETS_LEN: usize = 0x1000; + +// equal to ascii 'u' +pub const UVIO_TYPE_UVC: u8 = 117u8; + +pub const UVIO_IOCTL_UVDEV_INFO_NR: u8 = 0; +pub const UVIO_IOCTL_ATT_NR: u8 = 1; +pub const UVIO_IOCTL_ADD_SECRET_NR: u8 = 2; +pub const UVIO_IOCTL_LIST_SECRETS_NR: u8 = 3; +pub const UVIO_IOCTL_LOCK_SECRETS_NR: u8 = 4; + +/// Uvdevice IOCTL control block +/// Programs can use this struct to communicate with the uvdevice via IOCTLs +/// `argument_{addr,len}` specifies in/out data depending on the request +/// +/// 'uv_rc' and `uv_rrc` are the response and reason response codes from the +/// Ultravisor. +/// +/// `flags` is currently unused and to be set zero +/// +#[repr(C)] +#[derive(Debug)] +pub struct uvio_ioctl_cb { + pub flags: u32, + pub uv_rc: u16, + pub uv_rrc: u16, + pub argument_addr: u64, + pub argument_len: u32, + pub reserved14: [u8; 44usize], +} +assert_size!(uvio_ioctl_cb, 0x40); + +/// Information of supported functions by the uvdevice +/// +/// * `supp_uvio_cmds` - supported IOCTLs by this device +/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL +/// +/// UVIO request to get information about supported request types by this +/// uvdevice and the Ultravisor. +/// Everything is output. Bits are in LSB0 ordering. +/// 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 (UVIO_IOCTL_UVDEV_INFO_NR) is always zero for `supp_uv_cmds` +/// as there is no corresponding UV-call. +#[repr(C)] +#[derive(Debug, Copy, Clone, AsBytes, FromBytes)] +pub struct uvio_uvdev_info { + pub supp_uvio_cmds: u64, + pub supp_uv_cmds: u64, +} +assert_size!(uvio_uvdev_info, 0x10); + +pub const UVIO_ATT_USER_DATA_LEN: usize = 0x100; +pub const UVIO_ATT_UID_LEN: usize = 0x10; + +/// Request Attestation Measurement control block +/// +/// The Attestation Request has two input and two outputs. +/// ARCB and User Data are inputs for the UV. +/// Measurement and Additional Data are outputs generated by UV. +/// +/// The Attestation Request Control Block (ARCB) is a cryptographically verified +/// and secured request to UV and User Data is some plaintext data which is +/// going to be included in the Attestation Measurement calculation. +/// +/// Measurement is a cryptographic measurement of the callers properties, +/// optional data configured by the ARCB and the user data. If specified by the +/// ARCB, UV will add some Additional Data to the measurement calculation. +/// This Additional Data is then returned as well. +/// +/// If the Retrieve Attestation Measurement UV facility is not present, +/// UV will return invalid command rc. +/// Obviously all numbers are in BIG-endian! +#[repr(C)] +#[derive(Debug, AsBytes, FromBytes)] +pub struct uvio_attest { + pub arcb_addr: u64, //in + pub meas_addr: u64, //out + pub add_data_addr: u64, //out + pub user_data: [u8; UVIO_ATT_USER_DATA_LEN], //in + pub config_uid: [u8; UVIO_ATT_UID_LEN], //out + pub arcb_len: u32, + pub meas_len: u32, + pub add_data_len: u32, + pub user_data_len: u16, + pub reserved136: u16, +} +assert_size!(uvio_attest, 0x138); + +#[allow(dead_code)] //TODO rm when pv learns attestation +impl uvio_attest { + pub const ARCB_MAX_LEN: usize = UVIO_ATT_ARCB_MAX_LEN; + pub const MEASUREMENT_MAX_LEN: usize = UVIO_ATT_MEASUREMENT_MAX_LEN; + pub const ADDITIONAL_MAX_LEN: usize = UVIO_ATT_ADDITIONAL_MAX_LEN; +} + +/// corresponds to the UV_IOCTL macro +pub const fn uv_ioctl(nr: u8) -> u64 { + iowr(UVIO_TYPE_UVC, nr, std::mem::size_of::()) +} +static_assert!(uv_ioctl(UVIO_IOCTL_ATT_NR) == 0xc0407501); + +/// corresponds to the __IOWR macro +const fn iowr(ty: u8, nr: u8, size: usize) -> u64 { + // constants and calculation from linux: asm-generic/ioctl.h + const _IOC_WRITE: u32 = 1; + const _IOC_READ: u32 = 2; + const _IOC_NRSHIFT: u32 = 0; + const _IOC_TYPESHIFT: u32 = 8; + const _IOC_SIZESHIFT: u32 = 16; + const _IOC_DIRSHIFT: u32 = 30; + ((_IOC_READ | _IOC_WRITE) as u64) << _IOC_DIRSHIFT + | ((ty as u64) << _IOC_TYPESHIFT) + | ((nr as u64) << _IOC_NRSHIFT) + | ((size as u64) << _IOC_SIZESHIFT) +} diff --git a/rust/pv/src/uvdevice/info.rs b/rust/pv/src/uvdevice/info.rs new file mode 100644 index 00000000..33b40af8 --- /dev/null +++ b/rust/pv/src/uvdevice/info.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use super::ffi::uvio_uvdev_info; +use crate::{ + misc::{Flags, Lsb0Flags64}, + uv::{uv_ioctl, UvCmd, UvDevice}, + Result, +}; +use std::fmt::Display; +use zerocopy::{AsBytes, FromBytes}; + +/// Information of supported functions by the uvdevice +/// +/// * `supp_uvio_cmds` - supported IOCTLs by this device +/// * `supp_uv_cmds` - supported UVCs corresponding to the IOCTL +/// +/// UVIO request to get information about supported request types by this +/// uvdevice and the Ultravisor. +/// Everything is output. +/// 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. +/// +#[derive(Debug)] +pub struct UvDeviceInfo { + supp_uvio_cmds: Lsb0Flags64, + supp_uv_cmds: Option, +} + +impl UvDeviceInfo { + /// Get information from the uvdevice. + /// + /// # Errors + /// + /// 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. + /// 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. + /// To check if the Ultravisor supports the Attestation call check at + /// `/sys/firmware/uv/query/facilities` and check for bit 28 (Msb0 ordering!) + pub fn get(uv: &UvDevice) -> Result { + let mut cmd = uvio_uvdev_info::new_zeroed(); + match uv.send_cmd(&mut cmd) { + Ok(_) => Ok(cmd.into()), + Err(crate::Error::Io(e)) if e.raw_os_error() == Some(libc::ENOTTY) => Ok(Self { + supp_uvio_cmds: (UvDevice::ATTESTATION_NR as u64).into(), + supp_uv_cmds: None, + }), + Err(e) => Err(e), + } + } +} + +impl From for UvDeviceInfo { + fn from(value: uvio_uvdev_info) -> Self { + Self { + supp_uvio_cmds: value.supp_uvio_cmds.into(), + supp_uv_cmds: Some(value.supp_uv_cmds.into()), + } + } +} + +impl UvCmd for uvio_uvdev_info { + fn cmd(&self) -> u64 { + uv_ioctl(UvDevice::INFO_NR) + } + + fn data(&mut self) -> Option<&mut [u8]> { + Some(self.as_bytes_mut()) + } + + fn rc_fmt(&self, _: u16, _: u16) -> Option<&'static str> { + None + } +} + +fn nr_as_string(nr: u8) -> Option<&'static str> { + match nr { + UvDevice::INFO_NR => Some("Info"), + UvDevice::ATTESTATION_NR => Some("Attestation"), + UvDevice::ADD_SECRET_NR => Some("Add Secret"), + UvDevice::LIST_SECRET_NR => Some("List Secrets"), + UvDevice::LOCK_SECRET_NR => Some("Lock Secret Store"), + _ => None, + } +} + +fn print_uvdevice_cmd(nr: u8, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match nr_as_string(nr) { + Some(s) => write!(f, "{s}"), + None => write!(f, "Unknown ({nr})"), + } +} + +fn parse_flags(uv_cmds: &Lsb0Flags64, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let supp_cmds: Vec<_> = (0u8..64) + .filter(|v| -> bool { uv_cmds.is_set(*v) }) + .enumerate() + .collect(); + let num_supp_cmds = supp_cmds.len(); + if num_supp_cmds == 0 { + println!("None"); + return Ok(()); + } + + for (n, cmd) in supp_cmds { + print_uvdevice_cmd(cmd, f)?; + if n != num_supp_cmds - 1 { + write!(f, ", ")?; + } + } + writeln!(f) +} +impl Display for UvDeviceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "uvdevice supports:")?; + parse_flags(&self.supp_uvio_cmds, f)?; + writeln!(f, "Ultravisor-calls available:")?; + match &self.supp_uv_cmds { + Some(cmds) => parse_flags(cmds, f), + None => writeln!(f, "Data not available"), + } + } +} diff --git a/rust/pv/src/uvdevice/test.rs b/rust/pv/src/uvdevice/test.rs new file mode 100644 index 00000000..c34c2946 --- /dev/null +++ b/rust/pv/src/uvdevice/test.rs @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![cfg(test)] + +use std::{ + os::unix::prelude::FromRawFd, + sync::{Mutex, MutexGuard}, +}; + +use super::*; +use lazy_static::lazy_static; + +lazy_static! { + /// needed to serialize all tests as tests operate on static data required by the mock + static ref TEST_LOCK: Mutex<()> = Mutex::new(()); + /// exists to have a lazy static mod variable + static ref IOCTL_MTX: Mutex = Mutex::new(IoctlCtx::new()); +} + +fn get_lock(m: &'static Mutex) -> MutexGuard<'static, T> { + match m.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + } +} + +struct IoctlCtx { + modify: Box i32 + Send + Sync>, + exp_cmd: ::libc::c_ulong, + called: bool, +} + +impl IoctlCtx { + pub fn exp_cmd(&mut self, cmd: ::libc::c_ulong) -> &mut Self { + self.exp_cmd = cmd; + self + } + pub fn set_mdfy(&mut self, mdfy: F) -> &mut Self + where + F: FnMut(&mut ffi::uvio_ioctl_cb) -> ::libc::c_int + 'static + Send + Sync, + { + self.modify = Box::new(mdfy); + self + } + pub fn reset(&mut self) -> bool { + let old = self.called; + self.called = false; + old + } + + fn new() -> Self { + Self { + modify: Box::new(|_| -1), + exp_cmd: 0, + called: false, + } + } +} + +pub mod mock_libc { + use super::*; + use std::mem::transmute; + + pub unsafe fn ioctl( + fd: ::libc::c_int, + cmd: ::libc::c_ulong, + data: *mut ffi::uvio_ioctl_cb, + ) -> ::libc::c_int { + let mut ctx = get_lock(&IOCTL_MTX); + assert!(!ctx.called, "IOCTL called more than once"); + ctx.called = true; + + assert_eq!(cmd, ctx.exp_cmd, "IOCTL cmd mismatch"); + assert_eq!(fd, 17, "IOCTL fd mismatch"); + + let data_ref: &mut ffi::uvio_ioctl_cb = transmute(data); + + (ctx.modify)(data_ref) + } +} + +impl ffi::uvio_ioctl_cb { + fn addr_eq(&self, exp: u64) -> &Self { + assert_eq!( + self.argument_addr, exp, + "ioctl arg addr not eq: {} == {}", + self.argument_addr, exp + ); + self + } + fn size_eq(&self, exp: u32) -> &Self { + assert_eq!( + self.argument_len, exp, + "ioctl arg len not eq: {} == {}", + self.argument_len, exp + ); + self + } + fn set_rc(&mut self, rc: u16) -> &mut Self { + self.uv_rc = rc; + self + } + fn set_rrc(&mut self, rrc: u16) -> &mut Self { + self.uv_rrc = rrc; + self + } +} + +const TEST_CMD: u64 = 17; +struct TestCmd(Option>); +impl UvCmd for TestCmd { + fn cmd(&self) -> u64 { + TEST_CMD + } + fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> { + None + } + fn data(&mut self) -> Option<&mut [u8]> { + match &mut self.0 { + None => None, + Some(d) => Some(d.as_mut_slice()), + } + } +} + +impl UvDevice { + ///use some random fd for `uvdevice` its OK, as the ioctl is mocked and never touches the passed file + fn test_dev() -> Self { + UvDevice(unsafe { std::fs::File::from_raw_fd(17) }) + } +} + +#[test] +fn ioctl_fail() { + let _m = get_lock(&TEST_LOCK); + + let mut mock_cmd = TestCmd(None); + + get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|_| -1); + + let uv = UvDevice::test_dev(); + + let res = uv.send_cmd(&mut mock_cmd); + assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called"); + assert!(matches!(res, Err(Error::Io(_)))); +} + +#[test] +fn ioctl_simpleo() { + let _m = get_lock(&TEST_LOCK); + + let mut mock_cmd = TestCmd(None); + + get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| { + cb.set_rc(1).addr_eq(0).size_eq(0); + 0 + }); + + let uv = UvDevice::test_dev(); + let res = uv.send_cmd(&mut mock_cmd); + assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called"); + assert!(res.is_ok()); +} + +#[test] +fn ioctl_simple_err() { + let _m = get_lock(&TEST_LOCK); + + let mut mock_cmd = TestCmd(None); + + get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(|cb| { + cb.set_rc(17).set_rrc(3).addr_eq(0).size_eq(0); + 0 + }); + + let uv = UvDevice::test_dev(); + let res = uv.send_cmd(&mut mock_cmd); + assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called"); + assert!(matches!(res, Err(Error::Uv{rc, rrc, ..}) if rc == 17 && rrc == 3 )); +} + +#[test] +fn ioctl_write_data() { + let _m = get_lock(&TEST_LOCK); + + let cmd_data = vec![0u8; 32]; + let cmd_data_len = cmd_data.len(); + let data_addr = cmd_data.as_ptr() as u64; + + let mut mock_cmd = TestCmd(Some(cmd_data)); + + get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| { + cb.set_rc(1).addr_eq(data_addr).size_eq(32); + unsafe { + ::libc::memset( + (*cb).argument_addr as *mut ::libc::c_void, + 0x42, + cmd_data_len, + ); + } + 0 + }); + + let uv = UvDevice::test_dev(); + let res = uv.send_cmd(&mut mock_cmd); + assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called"); + assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS); +} + +#[test] +fn ioctl_read_data() { + let _m = get_lock(&TEST_LOCK); + + let cmd_data = vec![42u8; 32]; + let cmd_data_len = cmd_data.len(); + let data_addr = cmd_data.as_ptr() as u64; + let data_exp = cmd_data.clone(); + + let mut mock_cmd = TestCmd(Some(cmd_data)); + + get_lock(&IOCTL_MTX).exp_cmd(TEST_CMD).set_mdfy(move |cb| { + cb.set_rc(1).addr_eq(data_addr).size_eq(32); + unsafe { + let data = std::slice::from_raw_parts((*cb).argument_addr as *const u8, cmd_data_len); + assert_eq!(data, data_exp); + } + 0 + }); + + let uv = UvDevice::test_dev(); + let res = uv.send_cmd(&mut mock_cmd); + assert!(get_lock(&IOCTL_MTX).reset(), "IOCTL was never called"); + assert_eq!(res.unwrap(), UvcSuccess::RC_SUCCESS); +} diff --git a/rust/pv/src/uvsecret.rs b/rust/pv/src/uvsecret.rs new file mode 100644 index 00000000..b9fa3560 --- /dev/null +++ b/rust/pv/src/uvsecret.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![cfg(feature = "uvsecret")] +//! Provides functionality to manage the UV secret store. +//! +//! Provides functionality to build `Add Secret` requests. +//! Also provides interfaces, to dispatch `Add Secret`, `Lock Secret Store`, +//! and `List Secrets` requests, +#[cfg(feature = "request")] +pub mod asrcb; +#[cfg(feature = "request")] +pub mod ext_secret; +#[cfg(feature = "request")] +pub mod guest_secret; +pub mod secret_list; +pub mod uvc; + +use crate::request::MagicValue; +use crate::requires_feat; + +#[allow(unused_imports)] //used for more convenient docstring +use asrcb::AddSecretRequest; +/// Types of (non architectured) user data for [`AddSecretRequest`] +/// +#[doc = requires_feat!(uvsecret)] +#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, zerocopy::AsBytes)] +pub enum UserDataType { + /// Marker that the request does not contain any user data + Null = 0x0000, +} + +/// The magic value used to identify an [`AddSecretRequest`] +/// +/// The magic value is ASCII: +/// ```rust +/// # use pv::request::uvsecret::AddSecretMagic; +/// # use pv::request::MagicValue; +/// # fn main() { +/// # let magic = +/// # b"asrcbM" +/// # ; +/// # assert!(AddSecretMagic::starts_with_magic(magic)); +/// # } +///``` +/// +#[doc = requires_feat!(uvsecret)] +#[repr(C)] +#[derive(Debug, Clone, Copy, zerocopy::AsBytes)] +pub struct AddSecretMagic { + magic: [u8; 6], // [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D] + tp: UserDataType, +} + +impl MagicValue<6> for AddSecretMagic { + // "asrcbM" + const MAGIC: [u8; 6] = [0x61, 0x73, 0x72, 0x63, 0x62, 0x4D]; +} + +impl From for AddSecretMagic { + fn from(tp: UserDataType) -> Self { + Self { + magic: Self::MAGIC, + tp, + } + } +} + +const SECRET_ID_SIZE: usize = 32; +fn ser_gsid(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result +where + S: serde::Serializer, +{ + let mut s = String::with_capacity(32 * 2 + 2); + s.push_str("0x"); + let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}")); + ser.serialize_str(&s) +} diff --git a/rust/pv/src/uvsecret/asrcb.rs b/rust/pv/src/uvsecret/asrcb.rs new file mode 100644 index 00000000..3279cdaf --- /dev/null +++ b/rust/pv/src/uvsecret/asrcb.rs @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use super::{AddSecretMagic, UserDataType}; +use crate::requires_feat; +use crate::{ + assert_size, + misc::Flags, + request::{ + hkdf_rfc_5869, + openssl::{ + pkey::{PKey, Public}, + Md, + }, + uvsecret::{ExtSecret, GuestSecret}, + Aad, BootHdrTags, Keyslot, ReqEncrCtx, Request, RequestVersion, Secret, + }, + uv::{ConfigUid, UvFlags}, + Result, +}; +use zerocopy::AsBytes; + +/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be +#[derive(Debug, Clone)] +struct BinGuestSecret(GuestSecret); +impl BinGuestSecret { + /// Reference to the confidential data + fn confidential(&self) -> &[u8] { + match &self.0 { + GuestSecret::Null => &[], + GuestSecret::Association { secret, .. } => secret.value().as_slice(), + } + } + fn dump_auth(&self) -> Vec { + match &self.0 { + GuestSecret::Null => vec![0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + GuestSecret::Association { id, .. } => { + let mut buf = vec![0; 48]; + buf[3] = 2; + buf[7] = 0x20; + buf[16..48].copy_from_slice(id.as_slice()); + buf + } + } + } +} + +impl From for BinGuestSecret { + fn from(secret: GuestSecret) -> Self { + BinGuestSecret(secret) + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, AsBytes)] +struct ReqAuthData { + flags: UvFlags, + boot_tags: BootHdrTags, + cuid: ConfigUid, + reserved90: [u8; 0x100], + prog_res190: [u8; 0x200], +} +assert_size!(ReqAuthData, 0x3e8); + +impl ReqAuthData { + fn new>(boot_tags: BootHdrTags, flags: F) -> Self { + ReqAuthData { + flags: flags.into(), + boot_tags, + cuid: [0; 0x10], + reserved90: [0; 0x100], + prog_res190: [0; 0x200], + } + } +} + +#[derive(Debug, Clone)] +struct ReqConfData { + secret: BinGuestSecret, + extension_secret: Secret<[u8; 32]>, +} + +impl ReqConfData { + fn to_bytes(&self) -> Secret> { + let secret = self.secret.confidential(); + + let mut v = vec![0; secret.len() + 32]; + if !secret.is_empty() { + v[..secret.len()].copy_from_slice(secret); + } + v[secret.len()..32 + secret.len()] + .copy_from_slice(self.extension_secret.value().as_slice()); + v.into() + } +} + +/// Flags for [`AddSecretRequest`] +/// +#[doc = requires_feat!(reqsecret)] +#[derive(Default, Clone, Copy, Debug)] +pub struct AddSecretFlags(UvFlags); +impl AddSecretFlags { + /// Enables the disable-dump flag + /// + /// After the request was dispatched successfully, + /// the UV will not provide any dump decryption information for the SE-guest anymore. + pub fn set_disable_dump(&mut self) { + self.0.set_bit(0) + } +} + +impl From<&u64> for AddSecretFlags { + fn from(v: &u64) -> Self { + Self(v.into()) + } +} + +impl From for UvFlags { + fn from(f: AddSecretFlags) -> Self { + f.0 + } +} + +/// Versions for [`AddSecretRequest`] +/// +#[doc = requires_feat!(reqsecret)] +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AddSecretVersion { + /// Version 1 (= 0x0100) + One = 0x0100, + + #[cfg(not(doc))] + #[cfg(any(debug_assertions, test))] + /// Only for testing + Inv = 0, +} + +impl From for RequestVersion { + fn from(val: AddSecretVersion) -> Self { + val as RequestVersion + } +} + +impl AddSecretMagic { + fn get(&self) -> crate::request::RequestMagic { + self.as_bytes().try_into().unwrap() + } +} + +/// Add Secret Request Control Block +/// +/// An ASRCB wraps a secret to transport is securely to the Ultravisor. +/// +/// Layout: +///```none +/// _______________________________________________________________ +/// | generic header (48) +/// | --------------------------------------------------- | +/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) | +/// | Configuration unique ID(16) (Attestation) | +/// | Optional, defaults to 0 | +/// | Reserved(256) | +/// | User Data(512) (reserved) | +/// | Customer Public Key (160) generated for each request | +/// | N Keyslots(80 each) | +/// | Secret header (Secret dependent) | +/// | --------------------------------------------------- | +/// | Secret to add (Secret type dependent)(may be 0 bytes) | Encrypted +/// | Extension secret(32) Optional, defaults to 0 | Encrypted +/// | --------------------------------------------------- | +/// | AES GCM Tag (16) | +/// |_____________________________________________________________| +///``` +/// +#[doc = requires_feat!(reqsecret)] +#[derive(Clone, Debug)] +pub struct AddSecretRequest { + magic: AddSecretMagic, + version: AddSecretVersion, + aad: ReqAuthData, + keyslots: Vec, + conf: ReqConfData, +} + +impl AddSecretRequest { + /// Create a new Add Secret request. + /// + /// The request has no extension secret, no configuration UID, no host-keys, + /// and no user data + /// + pub fn new( + version: AddSecretVersion, + secret: GuestSecret, + boot_tags: BootHdrTags, + flags: AddSecretFlags, + ) -> Self { + AddSecretRequest { + conf: ReqConfData { + extension_secret: Secret::new([0; 32]), + secret: secret.into(), + }, + aad: ReqAuthData::new(boot_tags, flags), + keyslots: vec![], + version, + magic: UserDataType::Null.into(), + } + } + + /// Sets the Configuration Unique Id of this [`AddSecretRequest`]. + pub fn set_cuid(&mut self, cuid: ConfigUid) { + self.aad.cuid = cuid; + } + + /// Sets the extension secret of this [`AddSecretRequest`]. + /// + /// # Errors + /// + /// This function will return an error if the key derivation fails for a [`ExtSecret::Derived`]. + pub fn set_ext_secret(&mut self, ext_secret: ExtSecret) -> Result<()> { + const DER_EXT_SECRET_INFO: &[u8] = "IBM Z Ultravisor Add-Secret".as_bytes(); + self.conf.extension_secret = match ext_secret { + ExtSecret::Simple(s) => s, + ExtSecret::Derived(cck) => hkdf_rfc_5869( + Md::sha512(), + cck.value(), + self.aad.boot_tags.seht(), + DER_EXT_SECRET_INFO, + )? + .into(), + }; + Ok(()) + } + + /// Returns a reference to the guest secret of this [`AddSecretRequest`]. + pub fn guest_secret(&self) -> &GuestSecret { + &self.conf.secret.0 + } + + /// compiles the authenticated area of this request + fn aad(&self, ctx: &ReqEncrCtx, conf_len: usize) -> Result> { + let cust_pub_key = ctx.key_coords()?; + let secr_auth = self.conf.secret.dump_auth(); + + let mut aad: Vec = Vec::with_capacity(3 + self.keyslots.len()); + aad.push(Aad::Plain(self.aad.as_bytes())); + aad.push(Aad::Plain(cust_pub_key.as_ref())); + self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k))); + aad.push(Aad::Plain(&secr_auth)); + + ctx.build_aad(self.version.into(), &aad, conf_len, self.magic.get()) + } + + #[doc(hidden)] + #[cfg(any(debug_assertions, test))] + pub fn aad_and_conf(&self, ctx: &ReqEncrCtx) -> Result<(Vec, Vec)> { + let conf = self.conf.to_bytes(); + let aad = self.aad(ctx, conf.value().len())?; + Ok((aad, conf.value().to_owned())) + } + + #[doc(hidden)] + #[cfg(any(debug_assertions, test))] + pub fn no_encrypt(&self, ctx: &ReqEncrCtx) -> Result> { + let (mut res, mut conf) = self.aad_and_conf(ctx)?; + res.append(&mut conf); + res.append(&mut vec![0x24; 32]); + Ok(res) + } +} + +impl Request for AddSecretRequest { + fn encrypt(&self, ctx: &ReqEncrCtx) -> Result> { + let conf = self.conf.to_bytes(); + let aad = self.aad(ctx, conf.value().len())?; + ctx.encrypt_aead(&aad, conf.value()) + } + + fn add_hostkey(&mut self, hostkey: PKey) { + self.keyslots.push(Keyslot::new(hostkey)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn guest_secret_bin_null() { + let gs: BinGuestSecret = GuestSecret::Null.into(); + let gs_bytes = gs.dump_auth(); + let exp = vec![0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + assert_eq!(exp, gs_bytes); + assert_eq!(&Vec::::new(), gs.confidential()) + } + + #[test] + fn guest_secret_bin_ap() { + let gs: BinGuestSecret = GuestSecret::Association { + name: "test".to_string(), + id: [1; 32], + secret: [2; 32].into(), + } + .into(); + let gs_bytes_auth = gs.dump_auth(); + let mut exp = vec![0u8, 0, 0, 2, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0]; + exp.extend([1; 32]); + + assert_eq!(exp, gs_bytes_auth); + assert_eq!(&[2; 32], gs.confidential()); + } +} diff --git a/rust/pv/src/uvsecret/ext_secret.rs b/rust/pv/src/uvsecret/ext_secret.rs new file mode 100644 index 00000000..38c4e8c8 --- /dev/null +++ b/rust/pv/src/uvsecret/ext_secret.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::{request::Secret, requires_feat}; + +/// Extension Secret for [`crate::request::uvsecret::AddSecretRequest`] +/// +#[doc = requires_feat!(reqsecret)] +#[derive(Debug, Clone)] +pub enum ExtSecret { + /// A bytepattern that must be equal for each request targeting the same SE-guest instance + Simple(Secret<[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 +} diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs new file mode 100644 index 00000000..8ad4fa55 --- /dev/null +++ b/rust/pv/src/uvsecret/guest_secret.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#[allow(unused_imports)] //used for more convenient docstring +use super::asrcb::AddSecretRequest; +use super::{ser_gsid, SECRET_ID_SIZE}; +use crate::{ + request::{hash, openssl::MessageDigest, random_array, Secret}, + requires_feat, Result, +}; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; + +const SECRET_SIZE: usize = 32; +/// A Secret to be added in [`AddSecretRequest`] +/// +#[doc = requires_feat!(reqsecret)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum GuestSecret { + /// No guest secret + Null, + /// Association secret used to associate an extension card to a SE guest + /// + /// Create Associations using [`GuestSecret::association`] + Association { + /// Name of the secret + name: String, + #[serde(serialize_with = "ser_gsid", deserialize_with = "de_gsid")] + /// SHA256 hash of [`GuestSecret::Association::name`] + id: [u8; SECRET_ID_SIZE], + /// Confidential actual assocuiation secret (32 bytes) + #[serde(skip)] + secret: Secret<[u8; SECRET_SIZE]>, + }, +} + +impl GuestSecret { + /// Create a new [`GuestSecret::Association`]. + /// + /// * `name` - Name of the secret. Will be hashed into a 32 byte id + /// * `secret` - Value of the secret. Ranom if [`Option::None`] + /// + /// # Errors + /// + /// This function will return an error if OpenSSL cannot create a hash. + pub fn association(name: &str, secret: O) -> Result + where + O: Into>, + { + let id = hash(MessageDigest::sha256(), name.as_bytes())?.to_vec(); + let secret = match secret.into() { + Some(s) => s, + None => random_array()?, + }; + + Ok(GuestSecret::Association { + name: name.to_string(), + id: id.try_into().unwrap(), + secret: secret.into(), + }) + } +} +fn de_gsid<'de, D>(de: D) -> Result<[u8; 32], D::Error> +where + D: serde::Deserializer<'de>, +{ + struct FieldVisitor; + + impl<'de> serde::de::Visitor<'de> for FieldVisitor { + type Value = [u8; SECRET_ID_SIZE]; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a `32 bytes long hexstring` prepended with 0x") + } + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + if s.len() != SECRET_ID_SIZE * 2 + 2 { + return Err(serde::de::Error::invalid_length(s.len(), &self)); + } + let nb = s.strip_prefix("0x").ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self) + })?; + crate::misc::parse_hex(nb) + .try_into() + .map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)) + } + } + de.deserialize_identifier(FieldVisitor) +} + +#[cfg(test)] +mod test { + use super::*; + use serde_test::{assert_tokens, Token}; + + //todo test GuestSecret::association + #[test] + fn association() { + let secret_value = [0x11; 32]; + let exp_id = [ + 0x75, 0xad, 0x01, 0xb4, 0x03, 0xa9, 0xe4, 0x59, 0x5d, 0xf0, 0x7a, 0xce, 0x38, 0x12, + 0x97, 0x99, 0xdd, 0xad, 0x90, 0x8a, 0x8f, 0x82, 0xf9, 0xc3, 0x2c, 0xdd, 0x7d, 0x53, + 0xef, 0xc7, 0x3c, 0x62, + ]; + let name = "association secret".to_string(); + let secret = GuestSecret::association("association secret", secret_value.clone()).unwrap(); + let exp = GuestSecret::Association { + name, + id: exp_id, + secret: secret_value.into(), + }; + assert_eq!(secret, exp); + } + + #[test] + fn ap_asc_parse() { + let id = [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, + 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, + 0x89, 0xab, 0xcd, 0xef, + ]; + let asc = GuestSecret::Association { + name: "test123".to_string(), + id, + secret: [0; 32].into(), + }; + + assert_tokens( + &asc, + &[ + Token::StructVariant { + name: "GuestSecret", + variant: "Association", + len: 2, + }, + Token::String("name"), + Token::String("test123"), + Token::String("id"), + Token::String("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), + Token::StructVariantEnd, + ], + ); + } +} diff --git a/rust/pv/src/uvsecret/secret_list.rs b/rust/pv/src/uvsecret/secret_list.rs new file mode 100644 index 00000000..724702a1 --- /dev/null +++ b/rust/pv/src/uvsecret/secret_list.rs @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use serde::{Serialize, Serializer}; +use std::usize; +use std::{ + fmt::Display, + io::{Cursor, Read, Seek, Write}, +}; +use zerocopy::{AsBytes, FromBytes, U16, U32}; + +use super::ser_gsid; + +/// List of secrets used to parse the [`crate::uv::ListCmd`] result +/// +/// Requires the `uvsecret` feature. +#[derive(Debug, PartialEq, Eq, Serialize)] +pub struct SecretList { + total_num_secrets: u16, + secrets: Vec, +} + +impl SecretList { + /// Encodes the list in the same binary format the UV would do + pub fn encode(&self, w: &mut T) -> Result<()> { + let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?; + w.write_u16::(num_s)?; + w.write_u16::(self.total_num_secrets)?; + w.write_all(&[0u8; 12])?; + for secret in &self.secrets { + w.write_all(secret.as_bytes())?; + } + w.flush().map_err(Error::Io) + } + + /// Decodes the list from the binary format of the UV into this internal representation + pub fn decode(r: &mut R) -> std::io::Result { + let num_s = r.read_u16::()?; + let total_num_secrets = r.read_u16::()?; + let mut v: Vec = Vec::with_capacity(num_s as usize); + r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes + let mut buf = [0u8; SECRET_ENTRY_SIZE]; + for _ in 0..num_s { + r.read_exact(&mut buf)?; + //cannot fail. buffer has the same size as the secret entry + let secr = SecretEntry::read_from(buf.as_slice()).unwrap(); + v.push(secr); + } + Ok(Self { + total_num_secrets, + secrets: v, + }) + } +} + +impl TryFrom for SecretList { + type Error = Error; + fn try_from(mut list: ListCmd) -> Result { + SecretList::decode(&mut Cursor::new(list.data().unwrap())).map_err(Error::InvSecretList) + } +} + +impl Display for SecretList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?; + if !self.secrets.is_empty() { + writeln!(f)?; + } + for s in &self.secrets { + writeln!(f, "{s}")?; + } + Ok(()) + } +} + +fn ser_u32(v: &U32, ser: S) -> Result { + ser.serialize_u32(v.get()) +} + +fn ser_u16(v: &U16, ser: S) -> Result { + ser.serialize_u16(v.get()) +} + +/// A secret in a [`SecretList`] +/// +/// Fields are in big endian +#[repr(C)] +#[derive(Debug, PartialEq, Eq, AsBytes, FromBytes, Serialize)] +pub struct SecretEntry { + #[serde(serialize_with = "ser_u16")] + index: U16, + #[serde(serialize_with = "ser_u16")] + stype: U16, + #[serde(serialize_with = "ser_u32")] + len: U32, + #[serde(skip)] + res_8: u64, + #[serde(serialize_with = "ser_gsid")] + id: [u8; 32], +} +const SECRET_ENTRY_SIZE: usize = 0x30; + +fn stype_str(stype: u16) -> String { + match stype { + // should never match (not incl in list), but here for completeness + 1 => "Null".to_string(), + 2 => "Association".to_string(), + n => format!("Unknown {n}"), + } +} + +impl Display for SecretEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{} {}:", self.index, stype_str(self.stype.get()))?; + write!(f, " ")?; + for b in self.id { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::io::{BufReader, BufWriter, Cursor}; + + #[test] + fn secret_entry_size() { + assert_eq!(::std::mem::size_of::(), SECRET_ENTRY_SIZE); + } + #[test] + fn dump_secret_entry() { + const EXP: &[u8] = &[ + 0x00, 0x01, 0x00, 0x02, //idx + type + 0x00, 0x00, 0x00, 0x20, //len + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + // id + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let s = SecretEntry { + index: 1.into(), + stype: 2.into(), + len: 32.into(), + res_8: 0, + id: [0; 32], + }; + + assert_eq!(s.as_bytes(), EXP); + } + + #[test] + fn secret_list_dec() { + let buf = [ + 0x00u8, 0x01, // num secr stored + 0x01, 0x12, // total num secrets + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved + // secret + 0x00, 0x01, 0x00, 0x02, //idx + type + 0x00, 0x00, 0x00, 0x20, //len + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + // id + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let exp = SecretList { + total_num_secrets: 0x112, + secrets: vec![SecretEntry { + index: 1.into(), + stype: 2.into(), + len: 32.into(), + res_8: 0, + id: [0; 32], + }], + }; + + let mut br = BufReader::new(Cursor::new(buf)); + let sl = SecretList::decode(&mut br).unwrap(); + assert_eq!(sl, exp); + } + + #[test] + fn secret_list_enc() { + const EXP: &[u8] = &[ + 0x00, 0x01, // num secr stored + 0x01, 0x12, // total num secrets + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //reserved + // secret + 0x00, 0x01, 0x00, 0x02, //idx + type + 0x00, 0x00, 0x00, 0x20, //len + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + // id + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let sl = SecretList { + total_num_secrets: 0x112, + secrets: vec![SecretEntry { + index: 1.into(), + stype: 2.into(), + len: 32.into(), + res_8: 0, + id: [0; 32], + }], + }; + + let mut buf = [0u8; 0x40]; + { + let mut bw = BufWriter::new(&mut buf[..]); + sl.encode(&mut bw).unwrap(); + } + println!("list: {sl:?}"); + assert_eq!(buf, EXP); + } +} diff --git a/rust/pv/src/uvsecret/uvc.rs b/rust/pv/src/uvsecret/uvc.rs new file mode 100644 index 00000000..4f172144 --- /dev/null +++ b/rust/pv/src/uvsecret/uvc.rs @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use super::AddSecretMagic; +use crate::{ + request::MagicValue, + requires_feat, + uv::{uv_ioctl, UvCmd, UvDevice}, + Error, Result, PAGESIZE, +}; +use std::io::Read; +use std::usize; + +/// _List Secrets_ Ultravisor command. +/// +/// The List Secrets Ultravisor call is used to list the +/// secrets that are in the secret store for the current SE-guest. +/// +#[doc = requires_feat!(uvsecret)] +pub struct ListCmd(Vec); +impl ListCmd { + fn with_size(size: usize) -> Self { + Self(vec![0; size]) + } +} + +impl Default for ListCmd { + fn default() -> Self { + Self::with_size(PAGESIZE) + } +} + +impl UvCmd for ListCmd { + fn data(&mut self) -> Option<&mut [u8]> { + Some(self.0.as_mut_slice()) + } + + fn cmd(&self) -> u64 { + uv_ioctl(UvDevice::LIST_SECRET_NR) + } + + fn rc_fmt(&self, _rc: u16, _rrc: u16) -> Option<&'static str> { + None + } +} + +/// _Add Secret_ Ultravisor command. +/// +/// The Add Secret Ultravisor-call is used to add a secret +/// to the secret store for the current SE-guest. +/// +#[doc = requires_feat!(uvsecret)] +pub struct AddCmd(Vec); + +impl AddCmd { + /// Create a new Add Secret command using the provided data. + /// + /// # Errors + /// + /// This function will return an error if the provided data does not start with the + /// ['crate::AddSecretRequest'] magic Value. + pub fn new(bin_add_secret_req: &mut R) -> Result { + let mut data = Vec::with_capacity(PAGESIZE); + bin_add_secret_req.read_to_end(&mut data)?; + + if !AddSecretMagic::starts_with_magic(&data[..6]) { + return Err(Error::NoAsrcb); + } + Ok(Self(data)) + } +} + +impl UvCmd for AddCmd { + fn data(&mut self) -> Option<&mut [u8]> { + Some(&mut self.0) + } + + fn cmd(&self) -> u64 { + uv_ioctl(UvDevice::ADD_SECRET_NR) + } + + fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> { + match rc { + 0x0101 => Some("not allowed to modify the secret store"), + 0x0102 => Some("secret store locked"), + 0x0103 => Some("access exception when accessing request control block"), + 0x0104 => Some("unsupported add secret version"), + 0x0105 => Some("invalid request size"), + 0x0106 => Some("invalid number of host-keys"), + 0x0107 => Some("unsupported flags specified"), + 0x0108 => Some("unable to decrypt the request"), + 0x0109 => Some("unsupported secret provided"), + 0x010a => Some("invalid length for the specified secret"), + 0x010b => Some("secret store full"), + 0x010c => Some("unable to add secret"), + 0x010d => Some("dump in progress, try again later"), + _ => None, + } + } +} + +/// _Lock Secret Store_ Ultravisor command. +/// +/// The Lock Secret Store Ultravisor-call is used to block +/// all changes to the secret store. Upon successful +/// completion of a Lock Secret Store Ultravisor-call, any +/// request to modify the secret store will fail. +/// +#[doc = requires_feat!(uvsecret)] +pub struct LockCmd; +impl UvCmd for LockCmd { + fn cmd(&self) -> u64 { + uv_ioctl(UvDevice::LOCK_SECRET_NR) + } + + fn rc_fmt(&self, rc: u16, _rrc: u16) -> Option<&'static str> { + match rc { + 0x0101 => Some("not allowed to modify the secret store"), + 0x0102 => Some("secret store already locked"), + _ => None, + } + } +} diff --git a/rust/pv/src/verify.rs b/rust/pv/src/verify.rs new file mode 100644 index 00000000..54fe4357 --- /dev/null +++ b/rust/pv/src/verify.rs @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use core::slice; +use log::debug; +use openssl::stack::Stack; +use openssl::x509::store::X509Store; +use openssl::x509::{CrlStatus, X509Ref, X509StoreContext, X509}; +use openssl_extensions::crl::StackableX509Crl; +use openssl_extensions::crl::X509StoreContextExtension; + +use crate::error::bail_hkd_verify; +use crate::misc::{read_certs, read_file}; +use crate::Result; + +mod helper; +mod test; + +/// A HkdVerifier verifies that a host-key document(HKD) can be trusted. +/// +/// If the verification fails the HKD should not be used to create requests. +pub trait HkdVerifier { + /// Checks if the given host-key document can be trusted. + /// + /// #Errors + /// + /// This function will return an error if the Hostkey cannot be trusted. + /// Refer to the concrete Error type for the specific reason. + fn verify(&self, hkd: &X509Ref) -> Result<()>; +} + +/// A "verifier" that does not verify and accepts all given host-keys as valid. +pub struct NoVerifyHkd; +impl HkdVerifier for NoVerifyHkd { + fn verify(&self, _hkd: &X509Ref) -> Result<()> { + Ok(()) + } +} + +/// A Verifier that checks the host-key document against a chain of trust. +pub struct CertVerifier { + store: X509Store, + ibm_z_sign_key: X509, + offline: bool, +} + +impl HkdVerifier for CertVerifier { + /// This function verifies a host-key + /// document. To do so multiple steps are required: + /// + /// 1. issuer(host_key) == subject(ibm_z_sign_key) + /// 2. Signature verification + /// 3. @hkd must not be expired + /// 4. @hkd must not be revoked + fn verify(&self, hkd: &X509Ref) -> Result<()> { + helper::verify_hkd_options(hkd, &self.ibm_z_sign_key)?; + + // verify that the hkd was signed with the key of the IBM signing key + if !hkd.verify(self.ibm_z_sign_key.public_key()?.as_ref())? { + bail_hkd_verify!(Signature); + } + + // Find matching crl for sign key in the store or download them + let crls = self.hkd_crls(hkd)?; + + // Verify that the CLRs are still valid + let mut verified_crls = Vec::with_capacity(crls.len()); + for crl in &crls { + if helper::verify_crl(crl, &self.ibm_z_sign_key).is_some() { + verified_crls.push(crl.to_owned()); + } + } + + // Test if hkd was revoked (min1 required) + if verified_crls.is_empty() { + bail_hkd_verify!(NoCrl); + } + for crl in &verified_crls { + match crl.get_by_cert(&hkd.to_owned()) { + CrlStatus::NotRevoked => (), + _ => bail_hkd_verify!(HdkRevoked), + } + } + debug!("HKD: verified"); + Ok(()) + } +} + +impl CertVerifier { + ///Download the CLRs that a HKD refers to. + pub fn hkd_crls(&self, hkd: &X509Ref) -> Result> { + let mut ctx = X509StoreContext::new()?; + // Unfortunately we cannot use a dedicated function here and have to use a closure (E0434) + // Otherwise, we cannot refer to self + let mut crls = ctx.init_opt(&self.store, None, None, |ctx| { + let subject = self.ibm_z_sign_key.subject_name(); + match ctx.crls(subject) { + Ok(crls) => Ok(crls), + _ => { + // reorder the name and try again + let broken_subj = helper::reorder_x509_names(subject)?; + ctx.crls(&broken_subj).or_else(helper::stack_err_hlp) + } + } + })?; + + if !self.offline { + // Try to download a CRL if defined in the HKD + if let Some(crl) = helper::download_first_crl_from_x509(hkd)? { + crl.into_iter().try_for_each(|c| crls.push(c.into()))?; + } + } + Ok(crls) + } +} + +impl CertVerifier { + /// Create a `CertVerifier`. + /// + /// * `cert_paths` - Paths to Cerificates 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 + /// internet. + /// # Errors + /// + /// 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, + offline: bool, + ) -> Result { + let mut store = helper::store_setup(root_ca_path, crl_paths, cert_paths)?; + let mut untr_certs = Vec::with_capacity(cert_paths.len()); + for path in cert_paths { + let mut crt = read_certs(&read_file(path, "certificate")?)?; + if !offline { + helper::download_crls_into_store(&mut store, &crt)?; + } + untr_certs.append(&mut crt); + } + + // remove the IBM signing certificate from chain. + // We have to verify them separately as they are not marked as intermediate certs + let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs)?; + + let store = store.build(); + helper::verify_chain(&store, &chain, slice::from_ref(&ibm_z_sign_key))?; + + Ok(Self { + store, + ibm_z_sign_key, + offline, + }) + } +} diff --git a/rust/pv/src/verify/helper.rs b/rust/pv/src/verify/helper.rs new file mode 100644 index 00000000..89f92b87 --- /dev/null +++ b/rust/pv/src/verify/helper.rs @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +use crate::error::bail_hkd_verify; +use crate::misc::{memeq, read_crls}; +use crate::HkdVerifyErrorType::*; +use crate::{Error, Result}; +use curl::easy::{Easy2, Handler, WriteError}; +use libc::c_int; +use log::debug; +use openssl::{ + asn1::{Asn1Time, Asn1TimeRef}, + error::ErrorStack, + nid::Nid, + ssl::SslFiletype, + stack::{Stack, Stackable}, + x509::{ + store::{File, X509Lookup, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}, + verify::{X509VerifyFlags, X509VerifyParam}, + X509Crl, X509CrlRef, X509Name, X509NameRef, X509PurposeId, X509Ref, X509StoreContext, + X509StoreContextRef, X509VerifyResult, X509, + }, +}; +use openssl_extensions::{ + akid::{AkidCheckResult, AkidExtension}, + crl::X509StoreExtension, +}; +use std::cmp::Ordering; +use std::time::Duration; +use std::usize; + +/// 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 +/// for details). +/// +const SECURITY_LEVEL: usize = 2; +const SECURITY_BITS_ARRAY: [u32; 6] = [0, 80, 112, 128, 192, 256]; +const SECURITY_BITS: u32 = SECURITY_BITS_ARRAY[SECURITY_LEVEL]; +const SECURITY_CHAIN_MAX_LEN: c_int = 2; + +/// verifies that the HKD +/// * has enough security bits +/// * is inside its validity period +/// * issuer name is the subject name of the [`sign_key`] +/// * the Authority Key ID matches the Signing Key ID of the [`sign_key`] +pub fn verify_hkd_options(hkd: &X509Ref, sign_key: &X509Ref) -> Result<()> { + let hk_pkey = hkd.public_key()?; + let security_bits = hk_pkey.security_bits(); + + if SECURITY_BITS > 0 && SECURITY_BITS > security_bits { + return Err(Error::HkdVerify(SecurityBits(security_bits, SECURITY_BITS))); + } + // TODO rust-openssl fix X509::not.after/before() impl to return Option& not panic on nullptr from C? + //try_... rust-openssl + // verify that the hkd is still valid + check_validity_period(hkd.not_before(), hkd.not_after())?; + + // check if hkd.issuer_name == issuer.subject + check_x509_name_equal(sign_key.subject_name(), hkd.issuer_name())?; + + // verify that the AKID of the hkd matches the SKID of the issuer + if let Some(akid) = hkd.akid() { + if akid.check(sign_key) != AkidCheckResult::OK { + bail_hkd_verify!(Akid); + } + } + Ok(()) +} + +pub fn verify_crl(crl: &X509CrlRef, issuer: &X509Ref) -> Option<()> { + let last = crl.last_update(); + let next = crl.next_update()?; + + check_validity_period(last, next).ok()?; + if let Some(akid) = crl.akid() { + if akid.check(issuer) != AkidCheckResult::OK { + return None; + } + } + + check_x509_name_equal(crl.issuer_name(), issuer.subject_name()).ok()?; + + match crl.verify(issuer.public_key().ok()?.as_ref()).ok()? { + true => Some(()), + false => None, + } +} + +/// Setup the x509Store such that it can be used it for verifying certificates +pub fn store_setup( + root_ca_path: &Option, + crl_paths: &[String], + cert_w_crl_paths: &[String], +) -> Result { + let mut x509store = X509StoreBuilder::new()?; + + match root_ca_path { + None => x509store.set_default_paths()?, + Some(p) => load_root_ca(p, &mut x509store)?, + } + + for crl in crl_paths { + load_crl_to_store(&mut x509store, crl, true).map_err(|source| Error::X509Load { + path: crl.to_owned(), + ty: Error::CRL, + source, + })?; + } + + for crl in cert_w_crl_paths { + load_crl_to_store(&mut x509store, crl, false).map_err(|source| Error::X509Load { + path: crl.to_owned(), + ty: Error::CRL, + source, + })?; + } + let mut param = X509VerifyParam::new()?; + let flags = X509VerifyFlags::X509_STRICT + | X509VerifyFlags::CRL_CHECK + | X509VerifyFlags::CRL_CHECK_ALL + | X509VerifyFlags::TRUSTED_FIRST + | X509VerifyFlags::CHECK_SS_SIGNATURE + | X509VerifyFlags::POLICY_CHECK; + + param.set_depth(SECURITY_CHAIN_MAX_LEN); + param.set_auth_level(SECURITY_LEVEL as i32); + param.set_purpose(X509PurposeId::ANY)?; + param.set_flags(flags)?; + x509store.set_param(¶m)?; + + Ok(x509store) +} + +/// Verify that the given IBM signing keys can be trusted +/// -> check the chain: IBMsignKey<-InterCA(s)<-RootCA +pub fn verify_chain( + store: &X509StoreRef, + untrusted_certs: &Stack, + sign_keys: &[X509], +) -> Result<()> { + fn verify_fun(ctx: &mut X509StoreContextRef) -> std::result::Result { + // verify certificate + let res = ctx.verify_cert()?; + if !res { + debug!("Failed to verify the singing key with the chain of trust"); + return Ok(res); + } + // verify that the chain is as expected + let chain = match ctx.chain() { + Some(c) => c, + None => { + debug!("No verification chain in verify-context. (openssl BUG)"); + ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION); + return Ok(false); + } + }; + if chain.len() < SECURITY_CHAIN_MAX_LEN as usize { + debug!("Verification expects one root and at least one intermediate certificate",); + ctx.set_error(X509VerifyResult::APPLICATION_VERIFICATION); + Ok(false) + } else { + Ok(true) + } + } + + let mut store_ctx = X509StoreContext::new()?; + + for sign_key in sign_keys { + // (rust)OpenSSL should not error out on `X509_verify_cert`\ + // (Internal (probably unrecoverable) error like OOM) + if !store_ctx + .init(store, sign_key, untrusted_certs, verify_fun) + .map_err(|e| Error::InternalSsl("The IBM Z signing key could not be verified.", e))? + { + return Err(Error::HkdVerify(IbmSignInvalid( + store_ctx.error(), + store_ctx.error_depth(), + ))); + } + } + Ok(()) +} + +/// Consumes and splits the given vector into a single IBM Z signing key and other certificates +/// +/// Error if not exactly one IBM Z signing key available +pub fn extract_ibm_sign_key(certs: Vec) -> Result<(X509, Stack)> { + let ibm_z_sign_key = get_ibm_z_sign_key(&certs)?; + + let mut chain = Stack::::new()?; + for x in certs.into_iter().filter(|x| !is_ibm_signing_cert(x)) { + chain.push(x)?; + } + Ok((ibm_z_sign_key, chain)) +} + +/// for all certs load the first CRL specified into our store +pub fn download_crls_into_store(store: &mut X509StoreBuilderRef, crts: &[X509]) -> Result<()> { + for crt in crts { + debug!("Download crls for {crt:?}"); + if let Some(crl) = download_first_crl_from_x509(crt)? { + crl.iter().try_for_each(|c| store.add_crl(c))?; + } + } + Ok(()) +} + +// Name Entry values of an IBM Z key signing cert +//Asn1StringRef::as_slice aka ASN1_STRING_get0_data gives a string without \0 delimiter +const IBM_Z_COMMON_NAME: &[u8; 43usize] = b"International Business Machines Corporation"; +const IBM_Z_COUNTRY_NAME: &[u8; 2usize] = b"US"; +const IBM_Z_LOCALITY_NAME: &[u8; 12usize] = b"Poughkeepsie"; +const IBM_Z_ORGANIZATIONAL_UNIT_NAME_SUFFIX: &str = "Key Signing Service"; +const IBM_Z_ORGANIZATION_NAME: &[u8; 43usize] = b"International Business Machines Corporation"; +const IBM_Z_STATE: &[u8; 8usize] = b"New York"; +const IMB_Z_ENTRY_COUNT: usize = 6; +fn name_data_eq(entries: &X509NameRef, nid: Nid, rhs: &[u8]) -> bool { + let mut it = entries.entries_by_nid(nid); + match it.next() { + None => false, + Some(entry) => memeq(entry.data().as_slice(), rhs), + } +} + +fn is_ibm_signing_cert(cert: &X509) -> bool { + let subj = cert.subject_name(); + + if subj.entries().count() != IMB_Z_ENTRY_COUNT + || !name_data_eq(subj, Nid::COUNTRYNAME, IBM_Z_COUNTRY_NAME) + || !name_data_eq(subj, Nid::STATEORPROVINCENAME, IBM_Z_STATE) + || !name_data_eq(subj, Nid::LOCALITYNAME, IBM_Z_LOCALITY_NAME) + || !name_data_eq(subj, Nid::ORGANIZATIONNAME, IBM_Z_ORGANIZATION_NAME) + || !name_data_eq(subj, Nid::COMMONNAME, IBM_Z_COMMON_NAME) + { + return false; + } + + return match subj.entries_by_nid(Nid::ORGANIZATIONALUNITNAME).next() { + None => false, + Some(entry) => match entry.data().as_utf8() { + Err(_) => false, + Ok(s) => s + .as_bytes() + .ends_with(IBM_Z_ORGANIZATIONAL_UNIT_NAME_SUFFIX.as_bytes()), + }, + }; +} + +fn get_ibm_z_sign_key(certs: &[X509]) -> Result { + let mut ibm_sign_keys = certs.iter().filter(|x| is_ibm_signing_cert(x)).cloned(); + match ibm_sign_keys.next() { + None => bail_hkd_verify!(NoIbmSignKey), + Some(k) => match ibm_sign_keys.next() { + None => Ok(k), + Some(_) => bail_hkd_verify!(ManyIbmSignKeys), + }, + } +} + +fn load_root_ca(path: &str, x509_store: &mut X509StoreBuilder) -> Result<()> { + let lu = x509_store.add_lookup(X509Lookup::::file())?; + + // Try to load cert as PEM file + match lu.load_cert_file(path, SslFiletype::PEM) { + Ok(_) => lu + .load_crl_file(path, SslFiletype::PEM) + .map(|_| ()) + .or(Ok(())), + // Not a PEM file? try ASN1 + Err(_) => lu + .load_cert_file(path, SslFiletype::ASN1) + .map(|_| ()) + .map_err(|source| Error::X509Load { + path: path.to_string(), + ty: Error::CERT, + source, + }), + } +} + +fn load_crl_to_store( + x509_store: &mut X509StoreBuilder, + path: &str, + err_out_empty_crl: bool, +) -> std::result::Result<(), openssl::error::ErrorStack> { + let lu = x509_store.add_lookup(X509Lookup::::file())?; + // Try to load cert as PEM file + if lu.load_crl_file(path, SslFiletype::PEM).is_err() { + // Not a PEM file? try read as ASN1 + let res = lu.load_crl_file(path, SslFiletype::ASN1); + if err_out_empty_crl { + res?; + } + } + Ok(()) +} + +///Run through the forest of the distribution points and find them +pub fn x509_dist_points(cert: &X509Ref) -> Vec { + let mut res = Vec::::with_capacity(1); + let dps = match cert.crl_distribution_points() { + Some(d) => d, + None => return res, + }; + for dp in dps { + let dp_nm = match dp.distpoint() { + Some(nm) => nm, + None => continue, + }; + let dp_gns = match dp_nm.fullname() { + Some(gns) => gns, + None => continue, + }; + for dp_gn in dp_gns { + match dp_gn.uri() { + Some(uri) => res.push(uri.to_string()), + None => continue, + }; + } + } + res +} + +const CRL_TIMEOUT_MAX: Duration = Duration::from_secs(3); + +/// Searches for CRL Distribution points and downloads the CRL. Stops after the first successful +/// download. +/// +/// Error if sth bad(=unexpected) happens (not bad: crl not available at link, unexpected format) +/// Other issues are mapped to Ok(None) +pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result>> { + struct Buf(Vec); + + impl Handler for Buf { + fn write(&mut self, data: &[u8]) -> std::result::Result { + self.0.extend_from_slice(data); + Ok(data.len()) + } + } + + for dist_point in x509_dist_points(cert) { + // A typical certificate is about 1200 bytes long + let mut handle = Easy2::new(Buf(Vec::with_capacity(1500))); + handle.url(&dist_point)?; + handle.get(true)?; + handle.follow_location(true)?; + handle.timeout(CRL_TIMEOUT_MAX)?; + handle.useragent("s390-tools-pv-crl")?; + + if handle.perform().is_err() { + continue; + } + match read_crls(&handle.get_ref().0) { + Err(_) => continue, + Ok(crl) => return Ok(Some(crl)), + } + } + Ok(None) +} + +fn check_validity_period(not_before: &Asn1TimeRef, not_after: &Asn1TimeRef) -> Result<()> { + let now = Asn1Time::days_from_now(0)?; + if let Ordering::Less = now.compare(not_before)? { + bail_hkd_verify!(BeforeValidity); + } + match now.compare(not_after)? { + Ordering::Less => Ok(()), + _ => bail_hkd_verify!(AfterValidity), + } +} + +fn check_x509_name_equal(lhs: &X509NameRef, rhs: &X509NameRef) -> Result<()> { + if lhs.entries().count() != rhs.entries().count() { + bail_hkd_verify!(IssuerMismatch); + } + + for l in lhs.entries() { + let ldata = l.data().as_slice(); + + // search for the matching value in the rhs names + // found none? -> names are not equal + if !rhs.entries().any(|r| memeq(ldata, r.data().as_slice())) { + bail_hkd_verify!(IssuerMismatch); + } + } + Ok(()) +} + +const NIDS_CORRECT_ORDER: [Nid; 6] = [ + Nid::COUNTRYNAME, + Nid::ORGANIZATIONNAME, + Nid::ORGANIZATIONALUNITNAME, + Nid::LOCALITYNAME, + Nid::STATEORPROVINCENAME, + Nid::COMMONNAME, +]; +/** + * Workaround to fix the mismatch between issuer name of the + * IBM Z signing CRLs and the IBM Z signing key subject name. + */ +pub fn reorder_x509_names(subject: &X509NameRef) -> std::result::Result { + let mut correct_subj = X509Name::builder()?; + for nid in NIDS_CORRECT_ORDER { + if let Some(name) = subject.entries_by_nid(nid).next() { + correct_subj.append_entry(name)?; + } + } + Ok(correct_subj.build()) +} + +pub fn stack_err_hlp( + e: ErrorStack, +) -> std::result::Result, openssl::error::ErrorStack> { + match e.errors().len() { + 0 => Stack::::new(), + _ => Err(e), + } +} + +#[cfg(test)] +/// tests for some private functions +mod test { + + use openssl_extensions::x509_crl_eq; + + use super::*; + use crate::test_utils::*; + use std::time::{Duration, SystemTime}; + + fn sys_to_asn1_time(syst: SystemTime) -> Asn1Time { + let secs = syst + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + Asn1Time::from_unix(secs as i64).unwrap() + } + + #[test] + fn check_validity_period() { + let day = Duration::from_secs(60 * 60 * 24); + let yesterday = sys_to_asn1_time(SystemTime::now() - day); + let tomorrow = sys_to_asn1_time(SystemTime::now() + day); + + assert!(super::check_validity_period(&yesterday, &tomorrow).is_ok()); + assert!(matches!( + super::check_validity_period(&tomorrow, &tomorrow), + Err(Error::HkdVerify(BeforeValidity)) + )); + assert!(matches!( + super::check_validity_period(&yesterday, &yesterday), + Err(Error::HkdVerify(AfterValidity)) + )); + } + + #[test] + fn x509_name_equal() { + let sign_crt = load_gen_cert("ibm.crt"); + let hkd = load_gen_cert("host.crt"); + let other = load_gen_cert("inter_ca.crt"); + + assert!(super::check_x509_name_equal(sign_crt.subject_name(), hkd.issuer_name()).is_ok(),); + + assert!(matches!( + super::check_x509_name_equal(other.subject_name(), hkd.subject_name()), + Err(Error::HkdVerify(IssuerMismatch)) + )); + } + + #[test] + fn is_ibm_z_sign_key() { + let ibm_crt = load_gen_cert("ibm.crt"); + let no_ibm_crt = load_gen_cert("inter_ca.crt"); + let ibm_wrong_subj = load_gen_cert("ibm_wrong_subject.crt"); + + assert!(is_ibm_signing_cert(&ibm_crt)); + assert!(!is_ibm_signing_cert(&no_ibm_crt)); + assert!(!is_ibm_signing_cert(&ibm_wrong_subj)); + } + + #[test] + fn get_ibm_z_sign_key() { + let ibm_crt = load_gen_cert("ibm.crt"); + let ibm_wrong_subj = load_gen_cert("ibm_wrong_subject.crt"); + let no_sign_crt = load_gen_cert("inter_ca.crt"); + + assert!(super::get_ibm_z_sign_key(&vec!(ibm_crt.clone())).is_ok()); + assert!(matches!( + super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), ibm_crt.clone())), + Err(Error::HkdVerify(ManyIbmSignKeys)) + )); + assert!(matches!( + super::get_ibm_z_sign_key(&vec!(ibm_wrong_subj)), + Err(Error::HkdVerify(NoIbmSignKey)) + )); + assert!(matches!( + super::get_ibm_z_sign_key(&vec!(no_sign_crt.clone())), + Err(Error::HkdVerify(NoIbmSignKey)) + )); + assert!(super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), no_sign_crt.clone())).is_ok(),); + } + + #[test] + fn download_first_crl_from_x509() { + let ibm_crt = load_gen_cert("ibm.crt"); + let inter_crl = load_gen_crl("inter_ca.crl"); + let _m_inter = super::super::test::mock_endpt("inter_ca.crl"); + + let crl_d = super::download_first_crl_from_x509(&ibm_crt) + .unwrap() + .unwrap(); + assert_eq!(crl_d.len(), 1); + assert!(x509_crl_eq( + crl_d.first().unwrap().as_ref(), + inter_crl.as_ref() + )); + } +} diff --git a/rust/pv/src/verify/test.rs b/rust/pv/src/verify/test.rs new file mode 100644 index 00000000..8a6258da --- /dev/null +++ b/rust/pv/src/verify/test.rs @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![cfg(test)] + +use super::{helper, helper::*, *}; +use crate::{Error, HkdVerifyErrorType::*}; +use core::slice; +use openssl::stack::Stack; + +use crate::test_utils::*; + +pub fn mock_endpt(res: &str) -> mockito::Mock { + let res_path = get_cert_asset_path(res); + + mockito::mock("GET", format!("/crl/{res}").as_str()) + .with_header("content-type", "application/pkix-crl") + .with_body_from_file(res_path) + .create() +} + +#[track_caller] +fn verify_sign_error(exp_raw: libc::c_int, obs: Error) { + verify_sign_error_slice(&[exp_raw], obs) +} +fn verify_sign_error_slice(exp_raw: &[libc::c_int], obs: Error) { + if exp_raw + .into_iter() + .filter(|e| match &obs { + Error::HkdVerify(ty) => match ty { + IbmSignInvalid(err, _d) => &&err.as_raw() == e, + _ => false, + }, + e => panic!("Unexpected error type: {e:?}"), + }) + .count() + == 0 + { + panic!("Error {obs:?} did not match one of the expected {exp_raw:?}"); + } +} +impl std::fmt::Debug for CertVerifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("CertVerifier") + } +} + +#[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 store = helper::store_setup(&None, &vec![], &vec![ibm_str, inter_str]); + assert!(store.is_ok()); +} + +#[test] +fn verify_chain_online() { + let ibm_crt = load_gen_cert("ibm.crt"); + let inter_crt = load_gen_cert("inter_ca.crt"); + let root_crt = get_cert_asset_path_string("root_ca.chained.crt"); + + let mock_inter = mock_endpt("inter_ca.crl"); + + let mut store = helper::store_setup(&Some(root_crt), &vec![], &vec![]).unwrap(); + download_crls_into_store(&mut store, slice::from_ref(&ibm_crt)).unwrap(); + let store = store.build(); + + mock_inter.assert(); + + let mut sk = Stack::::new().unwrap(); + sk.push(inter_crt).unwrap(); + verify_chain(&store, &sk, &vec![ibm_crt.clone()]).unwrap(); + assert!(verify_chain(&store, &sk, &vec!(ibm_crt)).is_ok()); +} + +#[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_crt = load_gen_cert("inter_ca.crt"); + let root_crt = get_cert_asset_path_string("root_ca.chained.crt"); + + let store = helper::store_setup(&Some(root_crt), &vec![inter_crl], &vec![]) + .unwrap() + .build(); + + let mut sk = Stack::::new().unwrap(); + sk.push(inter_crt).unwrap(); + assert!(verify_chain(&store, &sk, &vec![ibm_crt]).is_ok()); +} + +#[test] +fn verify_online() { + let root_crt = get_cert_asset_path_string("root_ca.chained.crt"); + let inter_crt = get_cert_asset_path_string("inter_ca.crt"); + let ibm_crt = get_cert_asset_path_string("ibm.crt"); + let hkd_revoked = load_gen_cert("host_rev.crt"); + let hkd_inv = load_gen_cert("host_invalid_signing_key.crt"); + let hkd_exp = load_gen_cert("host_crt_expired.crt"); + let hkd = load_gen_cert("host.crt"); + + let mock_inter = mock_endpt("inter_ca.crl"); + let mock_ibm = mock_endpt("ibm.crl"); + + let inter_crl = get_cert_asset_path_string("inter_ca.crl"); + let ibm_crl = get_cert_asset_path_string("ibm.crl"); + let verifier = CertVerifier::new( + &vec![ibm_crt, inter_crt], + &vec![ibm_crl, inter_crl], + &Some(root_crt), + false, + ) + .unwrap(); + + mock_inter.assert(); + + verifier.verify(&hkd).unwrap(); + + mock_ibm.assert(); + assert!(matches!( + verifier.verify(&hkd_revoked), + Err(Error::HkdVerify(HdkRevoked)) + )); + + assert!(matches!( + verifier.verify(&hkd_inv), + Err(Error::HkdVerify(IssuerMismatch)) + )); + + assert!(matches!( + verifier.verify(&hkd_exp), + Err(Error::HkdVerify(AfterValidity)) + )); +} + +#[test] +fn verify_offline() { + 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 hkd_revoked = load_gen_cert("host_rev.crt"); + let hkd_inv = load_gen_cert("host_invalid_signing_key.crt"); + let hkd_exp = load_gen_cert("host_crt_expired.crt"); + let hkd = load_gen_cert("host.crt"); + + let verifier = CertVerifier::new( + &vec![ibm_crt, inter_crt], + &vec![ibm_crl, inter_crl], + &Some(root_crt), + true, + ) + .unwrap(); + + verifier.verify(&hkd).unwrap(); + assert!(matches!( + verifier.verify(&hkd_revoked), + Err(Error::HkdVerify(HdkRevoked)) + )); + + assert!(matches!( + verifier.verify(&hkd_inv), + Err(Error::HkdVerify(IssuerMismatch)) + )); + + assert!(matches!( + verifier.verify(&hkd_exp), + Err(Error::HkdVerify(AfterValidity)) + )); +} + +#[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"); + + // To many signing keys + let verifier = CertVerifier::new( + &vec![ibm_crt.clone(), ibm_rev_crt.clone()], + &vec![], + &None, + true, + ); + assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys)))); + + // no CRL for each X509 + let verifier = CertVerifier::new( + &vec![inter_crt.clone(), ibm_crt.clone()], + &vec![inter_crl.clone()], + &Some(root_crt.clone()), + false, + ); + verify_sign_error(3, verifier.unwrap_err()); + let verifier = CertVerifier::new( + &vec![inter_crt.clone(), ibm_crt.clone()], + &vec![], + &Some(root_chn_crt.clone()), + false, + ); + verify_sign_error(3, verifier.unwrap_err()); + + // wrong intermediate (or ibm key) + let verifier = CertVerifier::new( + &vec![inter_fake_crt, ibm_crt.clone()], + &vec![inter_fake_crl], + &Some(root_chn_crt.clone()), + 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( + &vec![inter_crt.clone(), ibm_crt.clone()], + &vec![inter_crl.clone()], + &None, + true, + ); + verify_sign_error(20, verifier.unwrap_err()); + + //correct signing key + intermediate cert + let _verifier = CertVerifier::new( + &vec![inter_crt.clone(), ibm_crt.clone()], + &vec![inter_crl.clone()], + &Some(root_chn_crt.clone()), + false, + ) + .unwrap(); + + // no intermediate key + let verifier = CertVerifier::new( + &vec![ibm_crt.clone()], + &vec![], + &Some(root_chn_crt.clone()), + false, + ); + verify_sign_error(20, verifier.unwrap_err()); + + //Ibm Sign outdated + let verifier = CertVerifier::new( + &vec![inter_crt.clone(), ibm_early_crt.clone()], + &vec![inter_crl.clone()], + &Some(root_chn_crt.clone()), + false, + ); + assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey)))); + let verifier = CertVerifier::new( + &vec![inter_crt.clone(), ibm_late_crt.clone()], + &vec![inter_crl.clone()], + &Some(root_chn_crt.clone()), + false, + ); + assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey)))); + + // revoked + let verifier = CertVerifier::new( + &vec![inter_crt.clone(), ibm_rev_crt.clone()], + &vec![inter_crl.clone()], + &Some(root_chn_crt.clone()), + false, + ); + verify_sign_error(23, verifier.unwrap_err()); +} + +#[test] +fn dist_points() { + let crt = load_gen_cert("ibm.crt"); + let res = x509_dist_points(&crt); + let exp = vec!["http://127.0.0.1:1234/crl/inter_ca.crl"]; + assert_eq!(res, exp); +} diff --git a/rust/pv/tests/add_secret_request.rs b/rust/pv/tests/add_secret_request.rs new file mode 100644 index 00000000..2b3e861e --- /dev/null +++ b/rust/pv/tests/add_secret_request.rs @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2023 + +#![cfg(all(feature = "request", feature = "uvsecret"))] +use pv::{ + get_test_asset, + request::{ + openssl::pkey::{PKey, Public}, + uvsecret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret}, + BootHdrTags, ReqEncrCtx, Request, SymKey, + }, + test_utils::get_test_keys, + uv::ConfigUid, + Result, +}; + +const TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]); +const CUID: ConfigUid = [0x42u8; 16]; +const ASSOC_SECRET: [u8; 32] = [0x11; 32]; +const ASSOC_ID: &'static str = "add_secret_request"; + +fn create_asrcb( + guest_secret: GuestSecret, + ext_secret: Option, + flags: AddSecretFlags, + cuid: Option, + hkd: PKey, + ctx: &ReqEncrCtx, +) -> Result> { + let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags); + + if let Some(s) = ext_secret { + asrcb.set_ext_secret(s)? + }; + if let Some(c) = cuid { + asrcb.set_cuid(c); + }; + + asrcb.add_hostkey(hkd); + Ok(asrcb.encrypt(ctx)?) +} + +fn get_crypto() -> (PKey, ReqEncrCtx) { + let (cust_key, host_key) = get_test_keys(); + let ctx = ReqEncrCtx::new_aes_256( + Some([0x55; 12]), + Some(cust_key), + Some(SymKey::Aes256([0x17; 32].into())), + ) + .unwrap(); + (host_key, ctx) +} + +fn gen_asrcb( + guest_secret: GuestSecret, + ext_secret: E, + flags: AddSecretFlags, + cuid: bool, +) -> Result> +where + E: Into>, +{ + let (host_key, ctx) = get_crypto(); + let cuid = match cuid { + true => Some(CUID.into()), + false => None, + }; + create_asrcb( + guest_secret, + ext_secret.into(), + flags, + cuid.into(), + host_key, + &ctx, + ) +} + +fn association() -> GuestSecret { + GuestSecret::association(ASSOC_ID, ASSOC_SECRET).unwrap() +} + +fn ext_simple() -> ExtSecret { + ExtSecret::Simple([0x17; 32].into()) +} + +fn ext_derived() -> ExtSecret { + ExtSecret::Derived([0; 32].into()) +} + +fn no_flag() -> AddSecretFlags { + AddSecretFlags::default() +} + +#[test] +fn null_none_default_cuid_one() { + let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), true).unwrap(); + let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn assoc_none_default_cuid_one() { + let asrcb = gen_asrcb(association(), None, no_flag(), true).unwrap(); + let exp = get_test_asset!("exp/asrcb/assoc_none_default_cuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn null_simple_default_cuid_one() { + let asrcb = gen_asrcb(GuestSecret::Null, ext_simple(), no_flag(), true).unwrap(); + let exp = get_test_asset!("exp/asrcb/null_simple_default_cuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn assoc_simple_default_cuid_one() { + let asrcb = gen_asrcb(association(), ext_simple(), no_flag(), true).unwrap(); + let exp = get_test_asset!("exp/asrcb/assoc_simple_default_cuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn null_derived_default_cuid_one() { + let asrcb = gen_asrcb(GuestSecret::Null, ext_derived(), no_flag(), true).unwrap(); + let exp = get_test_asset!("exp/asrcb/null_derived_default_cuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn assoc_derived_default_cuid_one() { + let asrcb = gen_asrcb(association(), ext_derived(), no_flag(), true).unwrap(); + let exp = get_test_asset!("exp/asrcb/assoc_derived_default_cuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn null_none_dump_cuid_one() { + let mut flags = no_flag(); + flags.set_disable_dump(); + let asrcb = gen_asrcb(GuestSecret::Null, None, flags, true).unwrap(); + let exp = get_test_asset!("exp/asrcb/null_none_dump_cuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn null_none_default_ncuid_one() { + let asrcb = gen_asrcb(GuestSecret::Null, None, no_flag(), false).unwrap(); + let exp = get_test_asset!("exp/asrcb/null_none_default_ncuid_one"); + assert_eq!(asrcb, exp); +} + +#[test] +fn null_none_default_cuid_seven() { + let (hkd, ctx) = get_crypto(); + let mut asrcb = + AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()); + (0..7).for_each(|_| asrcb.add_hostkey(hkd.clone())); + asrcb.set_cuid(CUID.into()); + let asrcb = asrcb.encrypt(&ctx).unwrap(); + + let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven"); + assert_eq!(asrcb, exp); +} diff --git a/rust/pv/tests/assets/cert/der.crl b/rust/pv/tests/assets/cert/der.crl new file mode 100644 index 0000000000000000000000000000000000000000..075024c79051678f0637d67b59f7523aa2554870 GIT binary patch literal 859 zcmXqLVh%TGVzg&sWHjJqHR`6@&yc;pwCD0rt< zDg$f@EaLhm>U`y8$tyPYz%~%n3%L7 z>WnO4!m3OnQmPR~MU;I6 z-z{FWWktD9`^7oh553f`+MQIMA{NDJYbw4b${}Z3sBEpW3Ge)i8!eKx_R8#EsIl-| zK^(LHyzl+h3XiXAb=9k#S(z$Za((XKc~^5^-%T${*FCcG_N|g1<$MQrDP2Btw76vc zi=9>abG|Eh{eS%_hH-KD^&>9?rZOF|(=B_Mm@x~2N9M(s%q3cV|t$e-X=^qw0 z%fOc(j!$3AzL@uFmbR71Yn>h!^Oha17EWsKa<fAaj#rLndAipfEUtSq`Dp#2@G^hy3;8x{xz+o&t6j*s zV4MFuecIVO0jA-xf7f1K=pl5`iLL9L^&Z{#H>+~LzSrba-LPwpfuCEJW4P&!(gItT z6Bahz<`YG;mVfc-QdoMvck|)nrjsP+$S?G}$rt(9`_-qLlk~*iz4*bV@X09q^MuS3 z%sm#(l1JP2t+~pX{r?3QcU9i)CqLFb)_ET0D8igkuJ=o+vE?KeEI;*Z%$-ivryc=H|bY|VyweI^1-__~9h6%IRB-W5zdT${{O8S*MXh+9hR;rkM#d%mEX^6pbXMmbOL_ zT}DZxgH|P7B9(I5HrgT)a$cKNk=mKsy{`W5zy0^#_w(G>``p)iKQAOfw?h)-%qgr z$W+Kl1vGH>eq14w&y5jsc-)wHkR}pvxJ-cnq{qavRVUD!$4}((KiB_NA^w|62g0lF zQp2eQF;l<@9zTwZSI(!TFgUaqKY_=MBb!3TDgujJ;YXu`k>Ka45|}$UF+wKj?FE4b znobmm;^YdsLR6}Aq#@pk;^ItpqLBX~pzMeLD~Br}y)O=N!8{R*9mixQ3OG!%HAMX4 z)AWl^U!Fh+1~A277>C8>a9Lm&lb_6qWkM3T`PXj(B2c`}YQ=0lU0kWsnz`U)v0xjQ_2!am+;beTGyWl^@MztrwAn3-*N z#l&=DgSq92)1G0bCHd9i2*y%%k7h+!))3!6?RbE1%5Y@HqUMn}yq%U-@SD{qde%u( zy)6EcQFC(XjN%rNz?Elrt>m!GmC%auA@~@{OG%RYZ?2D}ovqoTcXvG2lXK&u=hk=K zEd+rjJN3PTQL~k-TWsj8h{kdrB138GV*{H8^={oUYg#pXDg9k$dCGB;l+d~3BDmIj zpv&e?m+5qv*++%3oMU}BW6o#LcIH))w>+}ZK`>k&QeP^}rkNHA*p&6D556Cb3P3{h zxDDPuUdy5QIx~$OmD_Rd)4C-ZZ8+xs`j&jN`Pi+Fk32qEj5idG_2ak|13GOTsRyEN z87;@35?S|`MoO*mYYB+fz2=6mqvw;F`-`-#(r*M?>OAicJkTo^R);3-1GaT;raqdN zp-AT_b5#j#QLpcQyIj8wtZi93U^@!pismu9PEE)(URtyU|8~uQvDt0co#B_$!s7VJ zGvIfWy_t+D-i!8~S3PS*;2xdUAziqyOGEzoou2B(Gkbdq@3nT|7s#FQcu9wJ06U3E zP<&`>p1p}n_a=F%yCjvI4GfhlQn82LVvVNe#%#pQkf=4=;0OQ)RAgU-vTGn`Wu#2Z zkPt+IR+rh8+2mTWg~CKPM@O=gGf7F3$ZpC_p*qI$;~hDwb&rmXAt?cbQwj9|;1d!K z!5}Ra!mAJx0mo#04zLLkiGwuIYW8RVg~A{ZkeRB<0%-^lvh;PT_iwrMbn(=)nvJKi zg%+*4*4PoqMAe{$Bp~$%4)*@>x~mVsBxUfPvLiqb1uOmQOe_B-Wu^g`e#Rx>yt7HUoYPaIy*xaTlAq^##_D8Uo0=^I zEoe;#9*PK}`Mxo>?kHL*=39.0.0 diff --git a/rust/pv/tests/assets/cert/host.crt b/rust/pv/tests/assets/cert/host.crt new file mode 100644 index 00000000..3334a811 --- /dev/null +++ b/rust/pv/tests/assets/cert/host.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFDCCAvygAwIBAgIUO+SNGBgRvkmR/lKiZhiN4l4zS70wDQYJKoZIhvcNAQEN +BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl +eSBTaWduaW5nIFNlcnZpY2UwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0 +NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp +bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h +bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv +cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw +EAYHKoZIzj0CAQYFK4EEACMDgYYABAF2iBzYS4tt5xI8fpAO33jn97aEmZFGsSY7 +qLhshEfwirbUacKxKO2eHDUBWAWs09MCM9ORIvfi+KocKxR7eIO0BgDDRVbCXPkv +Kc5mvUQRTWt7PHjhOj+QvbkAQPaHblJi93imGpN9GzSALrJX404Gct1fKjR63aoo +IDUJKnDDXC55c6OBhzCBhDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vMTI3LjAu +MC4xOjEyMzQvY3JsL2libS5jcmwwDgYDVR0PAQH/BAQDAgMIMB8GA1UdIwQYMBaA +FN6M1/Do0NgBB3F9b9loWSA+sGd8MB0GA1UdDgQWBBR6ESQ+J+iXQe0a/KZ2+N+Z +8a9SZjANBgkqhkiG9w0BAQ0FAAOCAgEAVWJHoCxxkqh7b+y93PY4KPy4jJC4gN1S +dAPqttD/yteJ/4mbVel1/KNSoQBk5EpJmRqeBwHCgGJaT/TxYXNw/b8mRPxe/xbb +wieZMqlSmH028UjYDku1eM0IgHISgoCesIR95D5iAOWbMMVUwIHIHTfmhK7DmZVe +SPf7RIkctrpYxZh0Gw8KLZO6Mfy/9tq3dps0A7KS6jjdrF+M9LavPGwFvtfvRMTi +rdteByO2saGAKDvrjtievwlWCNBJlKV1arW9krN7eqJY5YO6eRbX6UjuhbPRgjte +eZ4jL121TBJaKZU7Q/lvYHIWfzstwQdiem2Ua1GyiiEvPZrQlmqQ3gDBtwJQGB4Q +2myP7MY7THiKObjaB8qRsVxKM78ktwAtAYSZv7gZlmSJ/uTMzDV5D2TQxqs7zwCj +sV+psUn4nvh58xP+DW+MYbF/Cpmzvul9FjMKBs270vE1q+gMot27rbQHRRJ4lVN5 +khiG6Oi6blVkPKExIVIiaZ9diXK6NhtWp15PWljNiDxZO+zpkeuw7cKLn/idzmvP +Gcj6m7DqcdsSIHNKbR5iM2VuDhg/j8uBD3uF2Wlymp31TBQgdYWSihJpwZKHNqJD +uq9SmegwI5gYg64KwABZM9hGbl/krXt/0CeCR5HRc+fthanKx/tO2tbCuVT3FR5J +XpKNUy1D78o= +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/host_crt_expired.crt b/rust/pv/tests/assets/cert/host_crt_expired.crt new file mode 100644 index 00000000..e439ec72 --- /dev/null +++ b/rust/pv/tests/assets/cert/host_crt_expired.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFEjCCAvqgAwIBAgIUMXh4o6xcPRTKpYDr+YgZnmWeatMwDQYJKoZIhvcNAQEN +BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl +eSBTaWduaW5nIFNlcnZpY2UwHhcNMjEwMzI5MTEwNDQ4WhcNMjIwMzI5MTEwNDQ4 +WjCBtjELMAkGA1UEBhMCVVMxNDAyBgNVBAoMK0ludGVybmF0aW9uYWwgQnVzaW5l +c3MgTWFjaGluZXMgQ29ycG9yYXRpb24xNDAyBgNVBAMMK0ludGVybmF0aW9uYWwg +QnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRpb24xETAPBgNVBAgMCE5ldyBZb3Jr +MQ8wDQYDVQQHDAZBcm1vbmsxFzAVBgNVBAsMDklCTSBaIEhvc3QgS2V5MIGbMBAG +ByqGSM49AgEGBSuBBAAjA4GGAAQBdogc2EuLbecSPH6QDt945/e2hJmRRrEmO6i4 +bIRH8Iq21GnCsSjtnhw1AVgFrNPTAjPTkSL34viqHCsUe3iDtAYAw0VWwlz5LynO +Zr1EEU1rezx44To/kL25AED2h25SYvd4phqTfRs0gC6yV+NOBnLdXyo0et2qKCA1 +CSpww1wueXOjgYcwgYQwMgYDVR0fBCswKTAnoCWgI4YhaHR0cDovLzEyNy4wLjAu +MToxMjM0L2NybC9pYm0uY3JsMA4GA1UdDwEB/wQEAwIDCDAfBgNVHSMEGDAWgBTe +jNfw6NDYAQdxfW/ZaFkgPrBnfDAdBgNVHQ4EFgQUehEkPifol0HtGvymdvjfmfGv +UmYwDQYJKoZIhvcNAQENBQADggIBAEsSd5vd7Vk1y1YsE4eWkrBrMElYa3/O6G2Q +oMZFo2mzzDH50NBEwYG4K+SjEmqJbAErtNHsAcJLWlvORiNoBmPcB6FEMifgCvuZ +zbSEiL/tt8XLI1M04DdKjVZ6AIrdhMKPz/AaRycnlHjbq0R0fEJP/SnWxtGnHewB +QGM8TDGCzXrwXsOr50soxQ+cbXFJ6eQyGrtNP0eyJ7kkIrz6+SJ0dQPXxoZpdtfY +XEv1OagX0tAuDUG26do6MjwC1qiDKoLdkxSFRkCvyRHqFapKlLhzBMrLhQ+Hl6E/ +kD5ORD2nMvTHcHWbjb7Mr6tcxKG+7CcJO0hYJbdfNCcKYc3EmE49wazSTBKvWfJp +XObVEGeM/11cdcg6Li1jw/JrrexEeQpjgoNuAgGKRmxzJBOCPNkU8jGs5QEqFCyw +fpl7BA+ydW2/zAvcr7mZZgyK4KiRTdK5VTGfXuwTv+Q3hsE0CZ6L+byNCZajyGzs +xq9ydh2G4kIlWFzs+2gSxQWYRiOGt6W7FVdiPYOnAVgzmRJdfR1qVrWZTGbPxJbX +1O3qYBPQE2tU8xsyl/HuikGProda2xwfTjmRhr7DPYyF75nGPvtGm6vwBBcWm+xl +jI0a/dHqE5MR6acOrXYNSFflPcfd04vJ87Ajx/wFr0Glo/8LWtzMp0nFpvif9LDX +3sGEtRA1 +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/host_invalid_signing_key.crt b/rust/pv/tests/assets/cert/host_invalid_signing_key.crt new file mode 100644 index 00000000..d31905c4 --- /dev/null +++ b/rust/pv/tests/assets/cert/host_invalid_signing_key.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFHzCCAwegAwIBAgIUBphLhhHJL2K96bK/cfgBk0d+7gkwDQYJKoZIhvcNAQEN +BQAwgckxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMSQwIgYDVQQLDBtLZXkgU2lnbmluZyBT +ZXJ2aWNlIEludmFsaWQwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0NDha +MIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVz +cyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBC +dXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsx +DzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZswEAYH +KoZIzj0CAQYFK4EEACMDgYYABAF709fZwb3pGZWfHLcCG2fLNcvNh6IqPHwfjRYp +brM5BEE8XoXxcCAXwxo4EAGOcuZBRKP6Ofahek7ppizV8bkPMQDCIgNq2N8mwPFO +99CbyrZs4ZMeAWWWPJiHXUHbWQq4ko8w4UUT7nUIgdHQwWM6TdZml8ke+LE9Jxtx +h9KkfCPi/aOBlTCBkjBABgNVHR8EOTA3MDWgM6Axhi9odHRwOi8vMTI3LjAuMC4x +OjEyMzQvY3JsL2libV93cm9uZ19zdWJqZWN0LmNybDAOBgNVHQ8BAf8EBAMCAwgw +HwYDVR0jBBgwFoAU+jIyiVonTYe6GuSPiAkxA65qou0wHQYDVR0OBBYEFJdEECIf +7UdEf08saThHZDzSfxjhMA0GCSqGSIb3DQEBDQUAA4ICAQBgvREPqqKvAZM3q9pG +5S6wtUspz1Y1sBD4duPEnMZ7Vf9a1HRPrR4vc5ncFIcyS/U/UusvWgFMYoa6WIZR +l4OqRplKF1pwCaQ2F/8OdGMV37iUqZuN6V/GggbFXgMFK1dH29T6h4VtoKC9yScQ +ToHQLuz4ymkd2BwxYix19M6QwdrqomjJb2/zrc7pvMZ0k8KKYi/wt6tlz7FDvsxF +VSDf29gm98kfDJfzPfAC5D93YruAohsP8SakVdA2/YbTkDfImT8ggSnsE83upSD6 +ssjKPPNRunLeCKLb55/Ikcok1iyGhfdmkJvdIHSEvyNp0p7mrohz6l748xdKkKNt +9hOzsfNjThq3zp97ND7M+knqNuzsZIkcV/OUdxNBootIrJXvfeqpaw++5SfWvf+6 +1dHJQpDU3cXKAQ0/RvvqLC+aPvklk2efuvBKIKP9X4WqcP+l2P19GMaM1SZtr5S5 +OWMxqT6sW5lSX5Smm4rMB3UmLDS0SXxHIIvFEQSABiWb6Y0ibDj8a+YrWVyQNH1K +fCSNvsW1r03D06q6gp9fxL1hFBLUw9ooi7ewPfNmm3doe2R0TQpE0pWkiRhhjDjJ +RcIPp+gfYDtt0LcoYVpNdKtLoRPVtO2K3zeU0ezW4PL9Q0tjxh2K/+1+8UEv7nJD +sFg4nQMeygdSK4w0ZlT8xqXVJw== +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/host_rev.crt b/rust/pv/tests/assets/cert/host_rev.crt new file mode 100644 index 00000000..b6c584b3 --- /dev/null +++ b/rust/pv/tests/assets/cert/host_rev.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFDCCAvygAwIBAgIUGuqbEx5X1pFQfF4T6kIfdcrUwbMwDQYJKoZIhvcNAQEN +BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl +eSBTaWduaW5nIFNlcnZpY2UwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0 +NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp +bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h +bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv +cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw +EAYHKoZIzj0CAQYFK4EEACMDgYYABABphgYAOqZ4uJPUtVIqaU7UsJgz9+xMmGDq +V7nFimGmkbmqPLT96jIyN4CWdLzfbxP0xvIklkEoOm1xV07YR2LHHQATNEPISTDH +7fySZ47QVc5tyECpZVW7JvSvWKA/KiApAbB1ixErfDrqW1nG5IUtEbDYJgtGwPI/ ++I7e9cU2wkA5mKOBhzCBhDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vMTI3LjAu +MC4xOjEyMzQvY3JsL2libS5jcmwwDgYDVR0PAQH/BAQDAgMIMB8GA1UdIwQYMBaA +FN6M1/Do0NgBB3F9b9loWSA+sGd8MB0GA1UdDgQWBBTECeFXnyfTnFazbR0K9cYk +lS3O0jANBgkqhkiG9w0BAQ0FAAOCAgEAOjEE4/KdvcJZbloSGLue27FSrhExvUJ3 +tYS3rs2xg3Ua2daCioI00VrwIN2Fjisqvi10Nv6+NWz5w1220AJyjlmPxvWFcPco +sXLAOWDhi217JaoJ+RzavpOwzhTffEpvPwR6RU4A36vvonc4jm3mWs6F1i5T6YPi +ZaYuk3CRme6WX012rBhIc+heTGh5ZDwwPmGDMLXdpsu+2+3sCPxUW6eQcOWoXkhJ +jn+n6mU18JdN5+wU6Lig5uxXnoP1VN8Xog/mmKV4ThVAS8k9iS5wFK4jl27n6XZy +wfd65WWlm4MMEvKNruj025aSPJp/bcnchBNfnXXPuI5GnYS2cC3TXHd4XT3r3pYn +qgLoNi+AkxnTnxEv5lg+oE+yTMNxDh4iiYkX96ljamGbUPBvbD7bi3Oc/7EOVra1 +BmCGmcjToEnm0e0it9yyuYwKQ6nTz906W3XzaFB+awnXZcGEMgwwdvdal8+eki/r +ofT0nO6cuXbbPYc0rSs/2f3WxjDVmQtiVoJ1dPYIrTX/4lbrsYWVS016Y0UcZ/D/ +/qWWWZIyYpzPasTEgGqtwb5WhBvz3RAFTePefFlTzBHhSPk0Tsu4+W7zHpapfZ0M +0LXT1lGR5WBmur002vbTm4yt7tzdypMbL2i70WGEp4mpRohmBG1m9hYGcelip3iL +Im6vFBNWpVE= +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/ibm.crl b/rust/pv/tests/assets/cert/ibm.crl new file mode 100644 index 00000000..e87cd599 --- /dev/null +++ b/rust/pv/tests/assets/cert/ibm.crl @@ -0,0 +1,20 @@ +-----BEGIN X509 CRL----- +MIIDVzCCAT8CAQEwDQYJKoZIhvcNAQELBQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYD +VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u +MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv +cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEVMBMGA1UEBwwMUG91Z2hrZWVwc2ll +MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UXDTIzMDMx +OTExMDQ0OFoYDzIzODcxMjMxMTEwNDQ4WjA8MBMCAgIrFw0yMzAzMjgxMTA0NDha +MCUCFBrqmxMeV9aRUHxeE+pCH3XK1MGzFw0yMzAzMjgxMTA0NDhaMA0GCSqGSIb3 +DQEBCwUAA4ICAQCGdm0ls5MXM6MUI0wR7qOitKh3TIfRnCvhSibVPskjlBZaBT01 +F6xaQGyWVR19IzQNn9GxOGMqvRy/oSihznBeA0+e9497IOPXKop/JsypZR101539 +ntVt691ncmctxKnb2nT4dw7AuiLTxMVzdJ/ouXovnPcgSv/r8lwBo1fXxOgQlQLE +Pi126WFkkgBK7EANnAXiXVWvdM6p67jl/AQGOVHp8MeXowejDdVqKzoU6yyMRDeE +uEU4QibvH/J8VPLC/A2oh4XTZbJ5rB6u3rz2fFGI03XqSrJJHbNenGVQ2ar5qJeI +6kHNDIuuwXN+7JPFf8JXdk8L0G88rQsnjrcm0GzQPW/nZ5bN3FA1V139rdOhSBLR +QgaKzju8Le/Zem317ykOJbC6nDBORmpBVzXYdXA9RMg4PIs3kRVqp/RMiiClz42z +w8c1khmcH6FO2Q5Z40vq8tmSLhbu6PgGIPIya/OQacgDjDiDGcWGvqzVCWv/6AoL +em7b5Piu4yznVkEUA2h3LvoigYTJCgHFrQnoIcuM8vx8QkDjXxSHeuy3wTd2l67S +pZp+jSJPdqWe2PWALJrYuq736E2rZ013eLybHKYOkoJP6ZLewh4gsomO0bpxTL1U +TjPsJncaAP/gLqHi0QD4+irMlo6Q9YpEIkbp9ScEoVMHRL9A/vBQfUJfZw== +-----END X509 CRL----- diff --git a/rust/pv/tests/assets/cert/ibm.crt b/rust/pv/tests/assets/cert/ibm.crt new file mode 100644 index 00000000..94818c69 --- /dev/null +++ b/rust/pv/tests/assets/cert/ibm.crt @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIGyzCCBLOgAwIBAgIUeGuWhNwpt9CPzFJ5UJAKfkIlLDcwDQYJKoZIhvcNAQEL +BQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEPMA0GA1UEBwwGQXJtb25rMR4wHAYDVQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUg +Q0EwIBcNMjMwMzI5MDkwNDQ3WhgPMjM4NzEyMzEwOTA0NDdaMIHMMQswCQYDVQQG +EwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lcyBD +b3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNo +aW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsxFTATBgNVBAcMDFBv +dWdoa2VlcHNpZTEnMCUGA1UECwweSUJNIFogSG9zdCBLZXkgU2lnbmluZyBTZXJ2 +aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsXm4EgSxVmuNsKgo +sfJNxNQFkdbpCxxyMOuyBFyGKxSwiGl20VWB6itsG+b9NOmAI6D6FPvLPuB/gSq0 +HR5FuSIkmp9AUx6Xi6lVA2L7qdINo1OA4nJKe5hLSHfjWn36DZFrDyoODlDzLJrS +O4h4QSP1nDSFhk1ilsR1czlvKreXkJw5FsUHRhZGG6idLoK2Ibrne6WsVhPG6WNA +abuvQFv40cQWcyRoePktG8ImnNF5GDewXwtpzARSQj9jTL/gE77DH78a77J8+H+h +d5guiBbPZK8gNkPe0CjD0B7tUx/+sByoaSeQffFG4Cnu5JJDqFq9LXPjtlG2nXSB +Qh6Sc2gzP3jKV+ZcSwQw9AxEQ0ZB/VIPsR8KZKReDDztEpcKxwxlh7bEix/0Y1sv +zT7/I+m5kufdDG6j3hHHzniKXL4b/WyedSfdnVqIJw82FgPFgyIY6F/0ccLdkhAm +fLtQJBHc3UyK13l0qVJxhAFdz1Q0zfScBS6qM/Gnbcdc6MY9/bZdIK7E+4op5iAM +kvQHap7qnArhI8VQ1bcXYlQ6asPj4e10lmzroiBHM4N/Yuxv38tmtUCudSB+EcXi +EgJIOLmLq2ZACRzug9KPyMXOD/Yxz2wPgs6I3gvrB22w/MfC77wMfEMuQQk4cZel +TFKosHgLjvcLG+zx5yh5ZVFcNV0CAwEAAaOBrzCBrDA3BgNVHR8EMDAuMCygKqAo +hiZodHRwOi8vMTI3LjAuMC4xOjEyMzQvY3JsL2ludGVyX2NhLmNybDAMBgNVHRMB +Af8EAjAAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNV +HSMEGDAWgBQRqYdWstn6ntusRZ8LjyPFEicL5TAdBgNVHQ4EFgQU3ozX8OjQ2AEH +cX1v2WhZID6wZ3wwDQYJKoZIhvcNAQELBQADggIBAEqkZawT89MngrmMTREjYGKZ ++qrm7uQf9wFiM7H7Xs11OEJ5PkNh4jNnnIsXZxc8rr76x+zLr4F6aI08AQn5QOy9 +JXGIbrMHLebtn198aIOYbxZisbXnBlVO3Xz+k8JLdzsu5zxjjaDY3/a63X2ccStJ +U53pSqvgJi6/AvMPA1CPazSjxu6na8rYz6d7c/god7OF0qwQ/ePqd4uJOaImm7HH +CCkwMPYO7UyOWU5CSPMcJ86SGYhvYkoM7wZeJoukK6HlKDI1SRubiTFAx+Hbyk1R +dyVY9vmIOeUlsGEMgsW836g++dg8efRIbIYbSBLQhUL64lLA6wZJ6/oCtC29aX+o +UfxcGUROrpZ5Xi4b4sn0vW4rYq65BzlU17x45XsZMh11hX9aPNE4B62Jl2XLjX3P +Sedu7b/QB6jWpwTAdH96LeLxVepAWiVcFBApBqpu7wxRhCs6M1t3Gh9nvlPE5NRz +zsmx+HVZIgWoP3CgHmiHqajphL0xp6R9qJOyzAVChsmbQYvr+rfaXMv24KBvJYgc +xq5iCP7IccgC6WlhpWyAoTSuhiStTZJtlCKPZqc+HRcuf2fLWXip8YKNHgEtTxNz +7citBXZNoRDFULWwiYDnwhGcZ53p5zPLABYKZdfNHdI+tV92AbzYyQaV0ZwcxL9K +ObAAlDzZKE8vJwT94E3O +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/ibm_outdated_early.crl b/rust/pv/tests/assets/cert/ibm_outdated_early.crl new file mode 100644 index 00000000..2505decd --- /dev/null +++ b/rust/pv/tests/assets/cert/ibm_outdated_early.crl @@ -0,0 +1,19 @@ +-----BEGIN X509 CRL----- +MIIDGzCCAQMCAQEwDQYJKoZIhvcNAQELBQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYD +VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u +MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv +cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEVMBMGA1UEBwwMUG91Z2hrZWVwc2ll +MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UYDzMwMjIw +NzMwMTEwNDQ4WhgPMzAyMzA3MzAxMTA0NDhaMA0GCSqGSIb3DQEBCwUAA4ICAQAa +bg7llgCL+OdmZQEeBKey5Dm/NxJGJljoT/sxFbQ+86lwACh1mbdxVkaPyUC/oE9T +4ppC/eHoaRcdmvN4FlIYrUhqnrTGD4s8VSoYvJ7+f5ZFGjUyflnMwyaal21hDaG4 +2SZjPVOQ0ksEA3mrHE1MTRVFqFl4ZFxGhh7NYMoOEkffM1UooChWHTTBMz67nmbh +Ih0MDHhS5J7677K2N05402Z3v3S+Y8QEjIQjDsTC1S9V607eEfG9YEND2KicQKPH +r+CK9/fVaiTz9wgUEyybps4MFoWBuUqqRebQoargFZW8w329LuS6VokbM6BSduOT +qaYFtzp3DGZbvKwUGjiGVgB/PzzB1rv+2+i/EI3D4RJt+k8xvlBIIONxwK/hcjI3 +/i6hJueQpeCuasfX8ck/uKzSf0PhCmyLwWxQux66FJq4sXqWoqwf5P/U+tbB8zna +0cX5/f8+rS7ansbxjeiCHUkbdUEoY7k7KMSNUrtqbgQ4VyjTziysTbSEG7jkb4ri +Jaa9mDfWCkdwfB3TqDofWRkOdNpPTkj9TVZJ5FdV1h39D9O7B+VvedIiVod/KhB1 +DyOa44YpkEcS51PuNAC/exUd6nOv9Mz+WOUP+RrxHqndRYE0RGaFP9vENyks0Kga +4CLB/IbT2rmpLivK2i6i3NOqzcykHOab3LtwOLDDmg== +-----END X509 CRL----- diff --git a/rust/pv/tests/assets/cert/ibm_outdated_late.crl b/rust/pv/tests/assets/cert/ibm_outdated_late.crl new file mode 100644 index 00000000..b2db7507 --- /dev/null +++ b/rust/pv/tests/assets/cert/ibm_outdated_late.crl @@ -0,0 +1,19 @@ +-----BEGIN X509 CRL----- +MIIDFjCB/wIBATANBgkqhkiG9w0BAQsFADCBzDELMAkGA1UEBhMCVVMxNDAyBgNV +BAoMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRpb24x +NDAyBgNVBAMMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9y +YXRpb24xETAPBgNVBAgMCE5ldyBZb3JrMRUwEwYDVQQHDAxQb3VnaGtlZXBzaWUx +JzAlBgNVBAsMHklCTSBaIEhvc3QgS2V5IFNpZ25pbmcgU2VydmljZRcNMjEwMzI5 +MTEwNDQ4WhcNMjIwMzI5MTEwNDQ4WjANBgkqhkiG9w0BAQsFAAOCAgEAh+81L4ws +rvOC1j7nwxthaIE4m8alnuaq9h9uxYfDZooimvGwhCjfcoWmfSSA8lt3vbMJ2vRe +ZwnXXCyGWmetZ6ObnT0jwL0QuOYEuIdA8QHleBxLYBeFr4k0O9i8i3VUG0YcgZuN +H+lcpGdoIx2WV0cZ4rbzZ1cNx3bieaivqNoLQAy7g9jTizmTfY5ZvlvuG3iqSG+P +08APxmtt1qFEm1LVu4SyUSyoGB1NaxeoziMITQdfFqHoPRsu7Wdyuqi9f5irIPwM +VQNKs/Y+3Q3S8YkTW3yqxhj4HdSKJE4qVBLMYm7muirDFWo25u2sDX1LJHBsQLvV +fi7cGY0YnOJL2Y7A3XKDuqtZ34zpXg3Hhqpa9RF55K2u5dYUaPq8MEQUHK67II1r +YZAwfarhijQQ6t03E0vrzPVYpK8VjNUunYKQdOBS3OkKgXCwEMuqQDrps98BgDQ4 +qfbVfxwm9XEHJZaX/qFR0sp8OQd/SD5dnS3DBl0Pp5w+w2xIaSA7QmBpDqWY66Hb +cJq4CLOKTasHTddHKz7O7zIu8QhwJGLabtnx18iaHTNNHaTF6k/51pwvA3HkJgds +HVcUNljsDNSPE258JwR2XoQAUu6VuFwRzgD7lGwdI70CpIBeAP1TRSius5RsZB+u +cw+872CILlIdNJ72lzMPWQNH+IB1RU8U/eA= +-----END X509 CRL----- diff --git a/rust/pv/tests/assets/cert/ibm_rev.crt b/rust/pv/tests/assets/cert/ibm_rev.crt new file mode 100644 index 00000000..6dc45445 --- /dev/null +++ b/rust/pv/tests/assets/cert/ibm_rev.crt @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIGyzCCBLOgAwIBAgIUTPiBWJn8k37onZ/aYgLxudbD3kgwDQYJKoZIhvcNAQEL +BQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEPMA0GA1UEBwwGQXJtb25rMR4wHAYDVQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUg +Q0EwIBcNMjMwMzI5MDkwNDQ3WhgPMjM4NzEyMzEwOTA0NDdaMIHMMQswCQYDVQQG +EwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lcyBD +b3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNo +aW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsxFTATBgNVBAcMDFBv +dWdoa2VlcHNpZTEnMCUGA1UECwweSUJNIFogSG9zdCBLZXkgU2lnbmluZyBTZXJ2 +aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsXm4EgSxVmuNsKgo +sfJNxNQFkdbpCxxyMOuyBFyGKxSwiGl20VWB6itsG+b9NOmAI6D6FPvLPuB/gSq0 +HR5FuSIkmp9AUx6Xi6lVA2L7qdINo1OA4nJKe5hLSHfjWn36DZFrDyoODlDzLJrS +O4h4QSP1nDSFhk1ilsR1czlvKreXkJw5FsUHRhZGG6idLoK2Ibrne6WsVhPG6WNA +abuvQFv40cQWcyRoePktG8ImnNF5GDewXwtpzARSQj9jTL/gE77DH78a77J8+H+h +d5guiBbPZK8gNkPe0CjD0B7tUx/+sByoaSeQffFG4Cnu5JJDqFq9LXPjtlG2nXSB +Qh6Sc2gzP3jKV+ZcSwQw9AxEQ0ZB/VIPsR8KZKReDDztEpcKxwxlh7bEix/0Y1sv +zT7/I+m5kufdDG6j3hHHzniKXL4b/WyedSfdnVqIJw82FgPFgyIY6F/0ccLdkhAm +fLtQJBHc3UyK13l0qVJxhAFdz1Q0zfScBS6qM/Gnbcdc6MY9/bZdIK7E+4op5iAM +kvQHap7qnArhI8VQ1bcXYlQ6asPj4e10lmzroiBHM4N/Yuxv38tmtUCudSB+EcXi +EgJIOLmLq2ZACRzug9KPyMXOD/Yxz2wPgs6I3gvrB22w/MfC77wMfEMuQQk4cZel +TFKosHgLjvcLG+zx5yh5ZVFcNV0CAwEAAaOBrzCBrDA3BgNVHR8EMDAuMCygKqAo +hiZodHRwOi8vMTI3LjAuMC4xOjEyMzQvY3JsL2ludGVyX2NhLmNybDAMBgNVHRMB +Af8EAjAAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNV +HSMEGDAWgBQRqYdWstn6ntusRZ8LjyPFEicL5TAdBgNVHQ4EFgQU3ozX8OjQ2AEH +cX1v2WhZID6wZ3wwDQYJKoZIhvcNAQELBQADggIBAA1ipq2MIRdRIiUZ6M6AjY91 +L5iUpgKGrmhna/gg2b6AlugDOtVDsfeFm389aSplwY+zcJZ2AbUXhXe8RVHOEUwf +O7iEDWsMZ/wxit5ZotJ5kzr49n9RTtomgbQSqxWadLq6Q9hYOWg/cr1FvXwL5tyO +rHyuRXhkUMmuk4aQ1sfybfPp0PJKbzWu001Q4rxbJTlaib9b+CcsyLWHs06JXVKi +755Lg9/ND1bQjW2CMJbZ2rm8V7oh4J0tJuF3DntOjyOk+yosckTF3bFffGPR67WC +RkZebIKx2Rh6OXMrQTLz9ldqWo0cW0O353gSmrMxExWKhgoDrZKc4UeOanweDTqO +4lU0RP/4naDuQl6/FE0rUzUkfAJmsKIuI4G1lQNZhaqUH/BdN1du094RON0T5agK +etoBcPpNpxOn4N86TJaYoDjRSDpKwxXVKZs9lk5GRLxRhqtY3iQYVZrz2gY36Ri4 +lnuKZCeFmfjHvvktmb08EmrvGiQAhXzI8yfeVhlwP8lhtumIO877VW++tedK0z0D +6aBz1LsVI3IbinDZPRsWl0EEi+JFmpIktmMTdSn+0vTs7XJjbBZ+VKaPPWOG/Afg +Qav6+1AnnMtieGfrj3tyyfKo0vPSZKbdGzEr79Ukl+cxLdG5O5qZTy4ASm3EOw36 +p70HafhN8Vioa+uhQObP +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/ibm_wrong_subject.crt b/rust/pv/tests/assets/cert/ibm_wrong_subject.crt new file mode 100644 index 00000000..077e0a8b --- /dev/null +++ b/rust/pv/tests/assets/cert/ibm_wrong_subject.crt @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIGyDCCBLCgAwIBAgIUOgU+VHYEK4Q4dZokfM01Ok2XBtUwDQYJKoZIhvcNAQEL +BQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEPMA0GA1UEBwwGQXJtb25rMR4wHAYDVQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUg +Q0EwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0NDhaMIHJMQswCQYDVQQG +EwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lcyBD +b3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNo +aW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlvcmsxFTATBgNVBAcMDFBv +dWdoa2VlcHNpZTEkMCIGA1UECwwbS2V5IFNpZ25pbmcgU2VydmljZSBJbnZhbGlk +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA61P3Lfow3E6wGpMJmUWp +wsfFZwcuKSf64JXDn1pVJLUcjTwhApshnGaSBb+knlpwvsO1evrR7re9ZRh51730 +IintOP5IA3CSGd7fqmTpchx3kdFOndrXS7BwAWuB/eZ1qzKeOYpyAS3VSE4FphYi +LSxGfwSUl89pwYyWyqGl21hv/sBL6cc+Lm55vXbeRwWKW9K/w7BkhtK1zx1xm4i9 +4x1aXJ6DGWQpIk1sVDNPtzQVZYvmR1Y10/r75sNgA/WMiZx3/2VyCREnV+UXfvsX +fyMLcbwMWWt6psdhtoGFZ2sLJka5ZNvttQKfbde4TA3I6fpsrMi+oTT9YO3it5zG +ORCUC+j5B+zrzbSv+RgL+SnnAPkHqufb1a/4mFs/uTbjUYHN2/rhObnkLK4Xtfly +FBlivxx5haT9o49YkCv7l57+We4nafBPMw96ac5AGzA0gVwdMTeRZ3joT2Pc/zSf +H5E9wg3MZfg3TN2THB4S//r1/XOaA5F4BGjorbpPhp1/YaeF0rRMlAbZVKXHZJBR +n5qN8hD/V2tXviEkrZRL+iW6ltkslsjIkzrYSS+6goymUjWrkGjmcsTo0SStHE0p +7pOChLwpUtpaElemp1NDzVJqvrglWPkM1ZIIjxpk23zxKj7V2FazqP6PVuyeWdkj +VYN86ULDRG5j1hfn/n0HEC0CAwEAAaOBrzCBrDA3BgNVHR8EMDAuMCygKqAohiZo +dHRwOi8vMTI3LjAuMC4xOjEyMzQvY3JsL2ludGVyX2NhLmNybDAMBgNVHRMBAf8E +AjAAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSME +GDAWgBQRqYdWstn6ntusRZ8LjyPFEicL5TAdBgNVHQ4EFgQU+jIyiVonTYe6GuSP +iAkxA65qou0wDQYJKoZIhvcNAQELBQADggIBABcvU42Z/T7hT8ke5viG2x7FJmwA +gkrphOYiooB77sxY+cjxaYsct4yvFXvwuNAcstnlBK0jJRaWzLwswR1t2bXbRwQF +kjDO3br4ALRMHkDPa8iNioogyap8X6r40p7rvfnudKX0+MruLHXN3ZM2ltucYAYU +oR/Wa04KxDuZQHeKrDosAsJCv5MwgF69H3oPbhspFQsP2V5fFsxupnWFzVlwPfcQ +0lgHVC3nZ2Rj7ZariT/px3nfZ6Eg3pRyK32r2SQWVN/oVBEd5cCTONvD7Hr2SrtB +9D58f+vDyVNWM5OED7NqlNDaQw2x9BMjdEVYTGGRW4IXPbXWH08NUcEkT1Tx/vUE +EPlTgwt88Fca03yvAn/8Daw7ezsJNAFwDpPDcQhPi3vg2l32nuRkuQ5641hJiTGw +TEtpJc3dg3FJymG999rOCLLIheNLMehEDMPZHqG7XeEg/42F0580MdkOenMpjhwg +ZhrommB85sZcGBOwc63VMb5PPInYDQi5PXz9Tpann/VliVd4Dpnyn0XVy73VccXu +WWgDt8gJKWUpRiJ6MZzEKkBrXYjPLmrKB64usEJNQ1e2NIKV3bwvH5K3PmibyVBu +9fT5t0VXQpNxxlwngCjvjtt0D/frMCJQXpXpnz25aQDog9bnD1yl02SzxdZaVK05 +LZP4wR2beOGlz828 +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/inter_ca.crl b/rust/pv/tests/assets/cert/inter_ca.crl new file mode 100644 index 00000000..311eaceb --- /dev/null +++ b/rust/pv/tests/assets/cert/inter_ca.crl @@ -0,0 +1,20 @@ +-----BEGIN X509 CRL----- +MIIDSDCCATACAQEwDQYJKoZIhvcNAQELBQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYD +VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u +MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv +cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEPMA0GA1UEBwwGQXJtb25rMR4wHAYD +VQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUgQ0EXDTIzMDMxOTExMDQ0N1oYDzIzODcx +MjMxMTEwNDQ3WjA8MBMCAgG8Fw0yMzAzMjgxMTA0NDdaMCUCFEz4gViZ/JN+6J2f +2mIC8bnWw95IFw0yMzAzMjgxMTA0NDdaMA0GCSqGSIb3DQEBCwUAA4ICAQA0FKRq +yESt1SYxVW+BlFjeDWCf1GL471q+603JiRek5iEVt+bZXrII9y9lXsYhZ1d7BxCt +Wyo/497tPMwRKSiPJwrXPODQn3DTl6EM6VB+w9Kipmm3Fq97TSRBuiDkYaS/nUHh +nDfj40qb1tc18SgBXVLSSiu97U0JMAq8AHIfMzlnhIe4fJ7TJU2TFSrkFAOUVqZs +p0/J3aDccYJBnUnEeGD44i80wd3xmuOoBDqRgKcasYsv8QmSFhbD4BTvicxKueDD +kiWTFbgNTDQU9Prp8gYmuSOaQoK6S+8DlO80IRTDwpDq1nQaf5MvwfOqfwQVgAjt +RgrC9BI1RvQu0OyihvcqOh9EEj5O9D/nrgTdsWYJGF/otb8lL6JdXDOAjqWSkZVA +gKDq4NPUskgKzoccD6HY5wgIvSZTV8bXjz2ST2oddfg0/7akNBEmq4TQV9NHb/G0 +AihNJgd3HtESn5Fhm51aJZPyuwqzmkmNHTuHZ5qeDB1dN/UVSqfnLXKeKirOtJCq +VdWGZTFEKJSDPgmLOMy0GhrOeM/y5N5MJZBrwBxPJ3No3TOGr13Ir8E1cxCnchZX +PgpyXNU183gMX4k5NVEWYpCzoTxzY7PNvaMft61IkC9DIdnRxRbEqfdLiVy/k4jP +MUjX4ThGwtUVzqVOiH5uRRE1J7Msk/W3EYzIWg== +-----END X509 CRL----- diff --git a/rust/pv/tests/assets/cert/inter_ca.crt b/rust/pv/tests/assets/cert/inter_ca.crt new file mode 100644 index 00000000..8cf1cc45 --- /dev/null +++ b/rust/pv/tests/assets/cert/inter_ca.crt @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIGoTCCBImgAwIBAgIUXCG9Tf1Ea3mKUicsQMd1lldTtIgwDQYJKoZIhvcNAQEL +BQAwgbUxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEPMA0GA1UEBwwGQXJtb25rMRYwFAYDVQQLDA1JQk0gWiBSb290IENBMCAXDTIz +MDMyOTA5MDQ0NloYDzIzODcxMjMxMDkwNDQ2WjCBvTELMAkGA1UEBhMCVVMxNDAy +BgNVBAoMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRp +b24xNDAyBgNVBAMMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29y +cG9yYXRpb24xETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBcm1vbmsxHjAc +BgNVBAsMFUlCTSBaIEludGVybWVkaWF0ZSBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJYQSR4xUs7bFbMY4r4v8l/9cuRxOxnZ8/cFpIDKzCA4c7/U +omjJCaPOFJrldYthRnyjN6MdqKyIED0XjW/MIlbqjMJ4E4AvWKZC1+r3+YoFCYmg +pLq7kfBkw8Kd8BFhfRsGg60VeAzZ79Y6BvJZSyXXQeH1RYiH/bW4o6PdFaO/tchx +KFlYgOW5MoGTemx+muifZ9iKav4/feFZsh73+OFf+KyruSkGnM18YNqnoMiNL3M9 +H5T86OBjcPYHhhwCp/v5cjfD4Yaa1WAM0Bsy+o6b/VwSNhrk8U8JF2rjuK3wZm3L +hyMa3QOn/kgoonl7sVCKes6GpwOmiS/+qKf14JBK+bjTpDk/CsRYpnonBJKNGOzy +tr6CTqLWcJtoUz3kr1ZZnGXUmBjqYc9vYI2EnlzHnBAf+gplJVqtbEZckLx7rKBD +QXyXp5pqDZmnnxQ9qlk1ZMeqw/mjLackdi1CRg8SfA0GlRcQYmcWxc2U5iCcs+ym +q+V0ciK4YFg/z2wEMFEsarGclW8YrZ1RtY+IcmtCXf3rRa0CEbHCiclHoVtuX20c +LZuYsQ5y6TdeWkDTcAwm3ZCYa54LeySuYny8F8A7by42KRg+Z/JjaOlA6hBPGwvC +p1frJqQod5uGd0Zg0DrNKjjWIVc4Z38dRy48b1Ija081WBtKwyJiX8jto6yfAgMB +AAGjgZwwgZkwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovLzEyNy4wLjAuMToxMjM0 +L2NybC9yb290X2NhLmNybDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAfBgNVHSMEGDAWgBRJTc+PTs7kYFuicyYmOttP7ItAazAdBgNVHQ4EFgQUEamH +VrLZ+p7brEWfC48jxRInC+UwDQYJKoZIhvcNAQELBQADggIBAD3ZOiRT6ESzxFIK +76FY7qNM1PWcNrgTmMDy8iHfBWEBmkAUQbHvY/U5fPnPj3vPLiHXkLDbUliXyEnL +4myo966j8dettrvB7pxibCy7J2FxwoKwMUOY+4IgGBuxVVoWVBwzu5me35RnDZ6i +9+dRJXiZnO2cEVvfFmEfq0w5SQLsqmR6EeIhoUOepmEJDpjE3cSz6QnQ6KWdw2wf +e+dviPlDwS0sNg006lqSy3rVzsnlqLAsoDkeOEZyZmPbc6sAx4RJS9nBH4WERWb5 +XxVOYIn0QmlJKwlULB3x8dhxUv+a7alBjDt6v2MW1zXH8v1ZcMcJnFy8i6m0VzkV +edrO/ONmqfi/EUr/FothDLQnCoykWjcfL1JGLADjzyRE86Wg4L/DRil8k5wH8Fir +ZZE/kLeOkQN5FhvQK+m3YzGtxkehO7Io3YWmzbv05ZI2d6zroyP6DXS/zJY9wuNd +I/6zp6eUYb/mtT3NF3h1C3SjQpELT2IDoXXYQsbvcVk7pgMB2mP9sYnoDlQsXZvC +oEzD/ollmkHsgD3Zr3p6ANSiNpW6iRYBiWsRoXmVJw+nTSYvWLiMI/vuABXYPPn1 +Tc6yypXgtezMNtUI4fxJ5pU5aHMKL4+XGtCcACyazoVZaUam1DulWUcDhuH/4Om9 +FhcPOT6WqhWjn/zZLW6Rr5OvXwoF +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/cert/root_ca.chained.crt b/rust/pv/tests/assets/cert/root_ca.chained.crt new file mode 100644 index 00000000..9a03bc51 --- /dev/null +++ b/rust/pv/tests/assets/cert/root_ca.chained.crt @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIGXzCCBEegAwIBAgIUWAen+5+bRZaL8kVlq51CXPQUdZUwDQYJKoZIhvcNAQEL +BQAwgbUxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEPMA0GA1UEBwwGQXJtb25rMRYwFAYDVQQLDA1JQk0gWiBSb290IENBMCAXDTIz +MDMyOTA5MDQ0NFoYDzIzODcxMjMxMDkwNDQ0WjCBtTELMAkGA1UEBhMCVVMxNDAy +BgNVBAoMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRp +b24xNDAyBgNVBAMMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29y +cG9yYXRpb24xETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBcm1vbmsxFjAU +BgNVBAsMDUlCTSBaIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQD0gnTWPpI/FahyE+PizunOHzNBi88KObopjL6+6p9J10RB82pDxaLoMxPo +6xkm0ZKiFea6ll6sAfcKPzCei+3691sCOiGN/JYdSZR+SynRGeD1o8KJWAwfmZ4w +DNHWHRqUE6u3oqAPDQUqaOnzyGdh2vGLP3HU5F5rIDhYzilm7WuXykYdiF/P6Rjs +xzzQZOxHTnvV8byEDN0Sw0ONsQSFee0uM22e20Kme34A4p+p7QBiRFSJAQyLtp/o +YBY8zeIgekr+BppRbCItEFAgHBS8DLk04jhNfGlU5RteaOJe7D6tg7z9CC8X0dAI +/p1vJkz5zYMPGuoKKaowDAJ+ncwN+UDYhbd4g0ATKC7M5DFmn4Wy60G+3op2BNp4 +PadcsOcXwzKsOSQix0+n4Tfw1QQ07+ilBHBe8fCRpk2Nf/byYuEaBjPBTXUcpTsp +Ydv0s5HjSyRfhEWKMQBOIn6mSORayTJ1/xQECrOfuIopLiZyMCjhixT4PImiGucM +ruCmsHZvfA/8VQxyEcPK5K+XqFaSr5r423C4CzcafHhvUFOYj2WmJyZzWc2X2fjo +l+ZspNpQTlvYgVi2cTeBnbNqCNMcTiGoyie5roHGa4CnigHuBIVSuUVGq/rqFKnT +KgKboI8C3XpQID6rFcYY444wOpfea8LodHTXFF6o8A5m+6QlGwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRJTc+P +Ts7kYFuicyYmOttP7ItAazAdBgNVHQ4EFgQUSU3Pj07O5GBbonMmJjrbT+yLQGsw +DQYJKoZIhvcNAQELBQADggIBAFCGlLEdAsicVPPuekhdfOyoK/P6r5JHnPIpC894 +9TVUp/7LBvDOwcNAvs+34dsbfRtMmwXUCGofrS4S0+zugnpgx6D1ScWC+FNdBJf9 +C6XKBiU4Mxn/mKWTDUMBCxI+RRbpOvOox5h6pQcfo0IYz5okazol1nT40IdvNkZ7 +WDAz6Xqmw8k/+8Y+l1fDSfUqcgPnTBfgX3kkna8VHBx74nSqeFgOVvtd8BJq/xIG +6rAkGsv/PzmQqKpyoD70N/nGALomLlVScX5qIbNrXWW0DWxY+kDwhRlskPdYS7RJ +Qol3NX0t7GZ+l+0W2Kpp7k04n5xrJ7pzTDyltJJEeaLfqrMlF5MCK/UyJqxa3LQr +IkP2wTxM4HR1gsIvr8ei3QFSBtxEGiQl2VWfBs8bwy+KkL1ao1UIcB+1dj4SLy8x +RqNFNfogkyLL9cELiKxViRQgNYowv3OsSw30i4eSHMor8m0qfVf38t7gi+m0Ba+T ++RIV5MlZcjG6C3jS8AMpUwXO4hOxGEueuXmZuCDmyypqRpMJc0hu2JA1Q9fztvfn +hnOAv6K++5hB4mIyqUp8/QJjO9X2kKa4+MmbuNTnRuC13s2f7pzHjZR3cFf6QN5b +duxTQZ18xvqXIR6EV4PVwShjCMS6NHmkmumIxMzaDK1mGwL7l7B0ZS+r3i7qwGPw +2iYv +-----END CERTIFICATE----- +-----BEGIN X509 CRL----- +MIIDGTCCAQECAQEwDQYJKoZIhvcNAQELBQAwgbUxCzAJBgNVBAYTAlVTMTQwMgYD +VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u +MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv +cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEPMA0GA1UEBwwGQXJtb25rMRYwFAYD +VQQLDA1JQk0gWiBSb290IENBFw0yMzAzMTkxMTA0NDRaGA8yMzg3MTIzMTExMDQ0 +NFowFTATAgIBTRcNMjMwMzI4MTEwNDQ0WjANBgkqhkiG9w0BAQsFAAOCAgEAzXCT +KYc+J3XC49cCzdJBRzXdVk8AqylNFCBC/Z4a9AaPnFgUHIZqLcucvVTBMlYkzQST +7lu47hksCrlePGeY58goa8rOUuTjH0Gk/809oMNMxyJ3UjuEq/Q45gDeKys8UZqu +qrgHZ1dnBB5ARdPEhkMLzBgizrknhPXcAyg0f4dy8wFPCNJ0T+DiNdqKoQCZNsxD +3p1N3vTMIO20oWbX9cDZoY2Xb0rT9Cbt7ES3JY1DB4Z92zPB5ZxFuCIsxT3Jtszd +fM6YktxJbUvds/mqwmYCbQNZ4veS5YcrFPVVSADjnwP88GMbIQddAvXLOhjrUj4B +QEMndtREs0MvkDZdc/YkTEI/c1QF1xNT+UrMOxC0sEHSvcOQXNtw579QJ0gucA2J +HWWr6p2wTDrIefhppBQS0GSY2n7L1loKAZZNWt56TQoXRFWI4CtLJJTsPKIHWTQz +KLBgv5UlOdbcuh1+foY/XS8prlZvMS22oiDMLIknBR7ywYuYEq9YPKzDXWAL4mHk +DInbBQEYC2ar7wiLLBOM/2c2BmkzDdygChj7/1xvNYMGXEnak0Y/V75uAuWQ1h0T +3e52xW2RzwjYsoM04WBsSJFNd7VYSuSX7SneJywrSBneB+XvB7tcVaycsA1f3BMT +ptsIMqT9/N3++8MGCb/SRWoWFlLjITR9l5y3BUU= +-----END X509 CRL----- diff --git a/rust/pv/tests/assets/cert/root_ca.crt b/rust/pv/tests/assets/cert/root_ca.crt new file mode 100644 index 00000000..24cbb0ce --- /dev/null +++ b/rust/pv/tests/assets/cert/root_ca.crt @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGXzCCBEegAwIBAgIUWAen+5+bRZaL8kVlq51CXPQUdZUwDQYJKoZIhvcNAQEL +BQAwgbUxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEPMA0GA1UEBwwGQXJtb25rMRYwFAYDVQQLDA1JQk0gWiBSb290IENBMCAXDTIz +MDMyOTA5MDQ0NFoYDzIzODcxMjMxMDkwNDQ0WjCBtTELMAkGA1UEBhMCVVMxNDAy +BgNVBAoMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRp +b24xNDAyBgNVBAMMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29y +cG9yYXRpb24xETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBcm1vbmsxFjAU +BgNVBAsMDUlCTSBaIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQD0gnTWPpI/FahyE+PizunOHzNBi88KObopjL6+6p9J10RB82pDxaLoMxPo +6xkm0ZKiFea6ll6sAfcKPzCei+3691sCOiGN/JYdSZR+SynRGeD1o8KJWAwfmZ4w +DNHWHRqUE6u3oqAPDQUqaOnzyGdh2vGLP3HU5F5rIDhYzilm7WuXykYdiF/P6Rjs +xzzQZOxHTnvV8byEDN0Sw0ONsQSFee0uM22e20Kme34A4p+p7QBiRFSJAQyLtp/o +YBY8zeIgekr+BppRbCItEFAgHBS8DLk04jhNfGlU5RteaOJe7D6tg7z9CC8X0dAI +/p1vJkz5zYMPGuoKKaowDAJ+ncwN+UDYhbd4g0ATKC7M5DFmn4Wy60G+3op2BNp4 +PadcsOcXwzKsOSQix0+n4Tfw1QQ07+ilBHBe8fCRpk2Nf/byYuEaBjPBTXUcpTsp +Ydv0s5HjSyRfhEWKMQBOIn6mSORayTJ1/xQECrOfuIopLiZyMCjhixT4PImiGucM +ruCmsHZvfA/8VQxyEcPK5K+XqFaSr5r423C4CzcafHhvUFOYj2WmJyZzWc2X2fjo +l+ZspNpQTlvYgVi2cTeBnbNqCNMcTiGoyie5roHGa4CnigHuBIVSuUVGq/rqFKnT +KgKboI8C3XpQID6rFcYY444wOpfea8LodHTXFF6o8A5m+6QlGwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRJTc+P +Ts7kYFuicyYmOttP7ItAazAdBgNVHQ4EFgQUSU3Pj07O5GBbonMmJjrbT+yLQGsw +DQYJKoZIhvcNAQELBQADggIBAFCGlLEdAsicVPPuekhdfOyoK/P6r5JHnPIpC894 +9TVUp/7LBvDOwcNAvs+34dsbfRtMmwXUCGofrS4S0+zugnpgx6D1ScWC+FNdBJf9 +C6XKBiU4Mxn/mKWTDUMBCxI+RRbpOvOox5h6pQcfo0IYz5okazol1nT40IdvNkZ7 +WDAz6Xqmw8k/+8Y+l1fDSfUqcgPnTBfgX3kkna8VHBx74nSqeFgOVvtd8BJq/xIG +6rAkGsv/PzmQqKpyoD70N/nGALomLlVScX5qIbNrXWW0DWxY+kDwhRlskPdYS7RJ +Qol3NX0t7GZ+l+0W2Kpp7k04n5xrJ7pzTDyltJJEeaLfqrMlF5MCK/UyJqxa3LQr +IkP2wTxM4HR1gsIvr8ei3QFSBtxEGiQl2VWfBs8bwy+KkL1ao1UIcB+1dj4SLy8x +RqNFNfogkyLL9cELiKxViRQgNYowv3OsSw30i4eSHMor8m0qfVf38t7gi+m0Ba+T ++RIV5MlZcjG6C3jS8AMpUwXO4hOxGEueuXmZuCDmyypqRpMJc0hu2JA1Q9fztvfn +hnOAv6K++5hB4mIyqUp8/QJjO9X2kKa4+MmbuNTnRuC13s2f7pzHjZR3cFf6QN5b +duxTQZ18xvqXIR6EV4PVwShjCMS6NHmkmumIxMzaDK1mGwL7l7B0ZS+r3i7qwGPw +2iYv +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/exp/asrcb/assoc_derived_default_cuid_one b/rust/pv/tests/assets/exp/asrcb/assoc_derived_default_cuid_one new file mode 100644 index 0000000000000000000000000000000000000000..dfefab9b2b4f8f5df433ea37a35fb7ca79a96701 GIT binary patch literal 1416 zcmYc+E=o@FWdH(3AZG0dMFJpEC;&-yH;~6%yC;Xaf#ofjd{w4Zru7Pmfn^0$>*`r(V6iauGwiDbFW5CI?Dd> zvDEjTlofhaA=0A40ivIn+EBxg(V-z?`NyS)1EvRCF|D+ye10!|mj?H_=F~S+Htpp2 z+Ie{8j2rvplG(3&J&?NN))fD3cm9GHLHF~I&U{(qZ`*8ptH3cvb?L=97IKNxjUOJ| z{`*GvT^Yk0O;^`G@Se_M6Cm*6K)Lh5kTnXa7NYCTH%?nC_v3|oCQppq&sI&@k9`>h zHx^h-@2xrAx^=Vm6&}%JruIPhF#)jx%$)^yN_z|C-zP*%ON*B*TC%!8K;f0Q&gPk~ z_g-B*U8ZcM_j|QNO`@5{xzAkck3X4I$uBBotbJ?m_S{8L@-H_iJX!sFy3biB)z9W^ twyX9Ju4n)N literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/asrcb/assoc_none_default_cuid_one b/rust/pv/tests/assets/exp/asrcb/assoc_none_default_cuid_one new file mode 100644 index 0000000000000000000000000000000000000000..dd1ac1b7e289fa9dfa6b5bc89e669c10e4646401 GIT binary patch literal 1416 zcmYc+E=o@FWdH(3AZG0dMFJpEC;&-yH;~6%yC;Xaf#ofjd{w4Zru7Pmfn^0$>*`r(V6iauGwiDbFW5CI?Dd> zvDEjTlofhaA=0A40ivIn+EBxg(V-z?`NyS)1EvRCF|D+ye10!|mj?H_=F~S+Htpp2 z+Ie{8j2rvplG(3&J&?NN))fD3cm9GHLHF~I&U{(qZ`*8ptH3cvb?L=97IKNxjUOJ| z{`*GvT^Yk0O;^`G@Se_M6Cm*6K)Lh5kTnXa7NYCTH%?nC_v3|oCQppq&sI&@k9`>h zHx^h-@2xrAx^=Vm6&}%JruIPhF#)jx%$)^yN_z|C-zP*%ON*B*TC%!8K;f0Q&gPk~ z_g-B*U8ZcM_j|QNO`@5{xzAkck3X4I$uBBotbJ?m_S{8L@-H_iJdu%<4w6~-b;69t sh8zrTvyE;XT>G5q`ohHf>cu@S6*-K*zt#m$G_L=f`A0z}VxHF{0MOa0>;M1& literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/asrcb/assoc_simple_default_cuid_one b/rust/pv/tests/assets/exp/asrcb/assoc_simple_default_cuid_one new file mode 100644 index 0000000000000000000000000000000000000000..8d9d4233db6cc1b79ca2165dcc3b3ac04993a235 GIT binary patch literal 1416 zcmYc+E=o@FWdH(3AZG0dMFJpEC;&-yH;~6%yC;Xaf#ofjd{w4Zru7Pmfn^0$>*`r(V6iauGwiDbFW5CI?Dd> zvDEjTlofhaA=0A40ivIn+EBxg(V-z?`NyS)1EvRCF|D+ye10!|mj?H_=F~S+Htpp2 z+Ie{8j2rvplG(3&J&?NN))fD3cm9GHLHF~I&U{(qZ`*8ptH3cvb?L=97IKNxjUOJ| z{`*GvT^Yk0O;^`G@Se_M6Cm*6K)Lh5kTnXa7NYCTH%?nC_v3|oCQppq&sI&@k9`>h zHx^h-@2xrAx^=Vm6&}%JruIPhF#)jx%$)^yN_z|C-zP*%ON*B*TC%!8K;f0Q&gPk~ z_g-B*U8ZcM_j|QNO`@5{xzAkck3X4I$uBBotbJ?m_S{8L@-H_iJmKc!apm6msJ;J- sn!I>mkLvkryFQ2>*j9GJAZ2!Nezn-PyxGm=mX9AES?zcBOmKz*0L`bXGXMYp literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/asrcb/null_derived_default_cuid_one b/rust/pv/tests/assets/exp/asrcb/null_derived_default_cuid_one new file mode 100644 index 0000000000000000000000000000000000000000..686883119bc4d943a052ef7d15aecc4335785d90 GIT binary patch literal 1352 zcmYc+E=o@FWdH(3AZGOlMFJpEC;&-yH;~6%yC;Xaf#ofjd{w4Zru7Pmfn^0$>*`r(V6iauGwiDbFW5CI?Dd> zvDEjTlofhaA=0A40ivIn+EBxg(V-z?`NyS)1EvRCF|D+ye10!|mj?H_=F~S+Htpp2 z+Ie{8j2rvplG(3&J&?NN))fD3cm9GHLHF~I&U{(qZ`*8ptH3cvb?L=97IKNxjUOJ| z{`*GvT^Yk0O;^`G@Se_M6Cm*6K)Lh5kTnXa7NYCTH%?nC_v3|oCQppq&sI&@k9`>h zHx^h-@2xrAx^=Vm6&}%JruIPh!OA$WQ{T$$KI`;i#n!DB8mv-s(nePzq=X}to7#8_ b;u!SeLfiJQFO#Xs3_Uq7f%nu|OAR{!uV;ua literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_one b/rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_one new file mode 100644 index 0000000000000000000000000000000000000000..638815cc2a5380c8c89049167a7b9e3cf5967135 GIT binary patch literal 1352 zcmYc+E=o@FWdH(3AZGOlMFJpEC;&-yH;~6%yC;Xaf#ofjd{w4Zru7Pmfn^0$>*`r(V6iauGwiDbFW5CI?Dd> zvDEjTlofhaA=0A40ivIn+EBxg(V-z?`NyS)1EvRCF|D+ye10!|mj?H_=F~S+Htpp2 z+Ie{8j2rvplG(3&J&?NN))fD3cm9GHLHF~I&U{(qZ`*8ptH3cvb?L=97IKNxjUOJ| z{`*GvT^Yk0O;^`G@Se_M6Cm*6K)Lh5kTnXa7NYCTH%?nC_v3|oCQppq&sI&@k9`>h zHx^h-@2xrAx^=Vm6&}%JruIPh!OA$WQ=|TwU!KfTaZT#(;dy>HiZkR`n3b$fuUS^J c*_-djLc^~tj}9{`aP9goD89yEXVG#V0R2CU0RR91 literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_seven b/rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_seven new file mode 100644 index 0000000000000000000000000000000000000000..65cd17f12c3a2fc5266d3556779dd399cb9e063c GIT binary patch literal 1832 zcmYc+E=o@FWdH(3AZFJHMFJpEC}4-u3NQgi3IP*^fSE$Tf(D$>0L-7G^k@i-h5*Cy zSd*V(LAPD2xfSNPEt$B)@6pCQw2iq}qb40?|M*zy zdr!&=y{ZstQQ-j5PfTs7VaVvv5V8E@(!&AMgRPiWT2wy2m%dAb`&@JCn<<-ia(wMP zymH2keR9d{*S#J{-EnJ*|F%1SL5!gL`A28IEb_N)w!Ky07^AxM;v5UP#OcNl4{rZ` zqx-Im;f?@IdQ^VsO<%yU&Sh3cacjX;I+-(N9cms3tNxG(;@_xb$$q^k6Hdl@^uH@1^h3;6B%!`ew?eog7~~ z53ihYW1n0y`*p7eQg_^%;=k?AUl1ece*V#!FN^$bn{96uIL4?hy*S50E^)f?!-Ly@ z-{`(8V|b(K>e>h1(^+f+1U?)ncRm=hMj_QgbiMh;X=~+vyl~ItiIMx+sww-iFQeeb z0*mRrHK$v*Zq~lSBYMo#9_T(;83%T1)IamflUXXRN!>j>&+kTYh8zpClGW)o%W5`z a^Zi(8__Ze9%l7{2JiR%t;h~}deGdSaNQYto literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/asrcb/null_none_dump_cuid_one b/rust/pv/tests/assets/exp/asrcb/null_none_dump_cuid_one new file mode 100644 index 0000000000000000000000000000000000000000..58cf22a5153b1e3b708808eb33db2e04a5c21c81 GIT binary patch literal 1352 zcmYc+E=o@FWdH(3AZGOlMFJpEC;&-TVamdl8HQ*m5z9X=JsdDS*otYTMdkB*>AN(z&o!sMnX+jo z$JfroD`(u;Czs5A-RptW9k-_VZ@cps#0a{de{|-{B7fUv+gk;WF{(>1&ase7oNoN^ z;P&4)y6?&u-e|hI_JQ|w7MlQp4+qMf4~DE!NVO1MZ@zKbTDc!D+%tJ%DH~AwXg7q9y7HEx(`;yft?!l&;0UamWpdqcMs3=yHT7W$HJ^+b$ZRR dn$6yPKNcE(T`!R?Jn3uZsYJ%IM+Xx!Isua3jNt$P literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/asrcb/null_simple_default_cuid_one b/rust/pv/tests/assets/exp/asrcb/null_simple_default_cuid_one new file mode 100644 index 0000000000000000000000000000000000000000..d4281d60c9478c50638ea39c5f93195b69f51291 GIT binary patch literal 1352 zcmYc+E=o@FWdH(3AZGOlMFJpEC;&-yH;~6%yC;Xaf#ofjd{w4Zru7Pmfn^0$>*`r(V6iauGwiDbFW5CI?Dd> zvDEjTlofhaA=0A40ivIn+EBxg(V-z?`NyS)1EvRCF|D+ye10!|mj?H_=F~S+Htpp2 z+Ie{8j2rvplG(3&J&?NN))fD3cm9GHLHF~I&U{(qZ`*8ptH3cvb?L=97IKNxjUOJ| z{`*GvT^Yk0O;^`G@Se_M6Cm*6K)Lh5kTnXa7NYCTH%?nC_v3|oCQppq&sI&@k9`>h zHx^h-@2xrAx^=Vm6&}%JruIPh!OA$WQ+;159BC2GKgfIPN@wKxlzL8K5mVheyEkSp ciIIH2P3@6qrP{?~3~I0UI2?MYyTA1u0QQ}Xt^fc4 literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/exp/keyslot.bin b/rust/pv/tests/assets/exp/keyslot.bin new file mode 100644 index 00000000..1b63028c --- /dev/null +++ b/rust/pv/tests/assets/exp/keyslot.bin @@ -0,0 +1 @@ +A\%ќ8a3؋1؂խKbR@ZxG$&I)N1w<1Gl8p`=)GtwfW#iiR0ndZ`UpO7CSqo z$*T8g^p6!q(Gl#rbDbiN+I7XQ(EY7A>833E9vRD>vB`>;cZn?zT=w{}z8zztM#N#W zZY{14M_=*Zt7LsGx_-{ZyTyg6lWlG-+7YmA#iI{jbgIqn1~+mhb==|9UDcJp=8dX} zaP>)_72LP{PfynHNO*m|dalar39~AL!`2#|G~1?_I-_g;pKFN)w~tOZWFT;Ts{V?m zi@PgB`)5B`>Urb(O0VY%PY;xFHO0Eme^&LiLNjfdW`BNUMg@;mTKxp zzWjPAo%{WI{piIGa=rI>8SltXm%IPsgpAGGT+0{jO3{9EOtxO#x9`8?dd1f@0gt`8 zCZFflm&)(z>#(Zo`!!+8Bcscu=geyowWe1a9<&g+YEb5THoW@aQ`UkN;;T!aWYyof zf4aVN;j#}4_UzI%I=bT07xu;kwh1D?_Gww2Z9j1F)eBKc!yQiNFF1XOP8()(A*u{B%lLz*(CnJ&!#d`K)(Mvkd?t8m^VgSoDqJ6A+I)wc(W I%Ze%h07?8EqyPW_ literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/keys/host.pem.crt b/rust/pv/tests/assets/keys/host.pem.crt new file mode 100644 index 00000000..3334a811 --- /dev/null +++ b/rust/pv/tests/assets/keys/host.pem.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFDCCAvygAwIBAgIUO+SNGBgRvkmR/lKiZhiN4l4zS70wDQYJKoZIhvcNAQEN +BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu +ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs +IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y +azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl +eSBTaWduaW5nIFNlcnZpY2UwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0 +NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp +bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h +bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv +cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw +EAYHKoZIzj0CAQYFK4EEACMDgYYABAF2iBzYS4tt5xI8fpAO33jn97aEmZFGsSY7 +qLhshEfwirbUacKxKO2eHDUBWAWs09MCM9ORIvfi+KocKxR7eIO0BgDDRVbCXPkv +Kc5mvUQRTWt7PHjhOj+QvbkAQPaHblJi93imGpN9GzSALrJX404Gct1fKjR63aoo +IDUJKnDDXC55c6OBhzCBhDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vMTI3LjAu +MC4xOjEyMzQvY3JsL2libS5jcmwwDgYDVR0PAQH/BAQDAgMIMB8GA1UdIwQYMBaA +FN6M1/Do0NgBB3F9b9loWSA+sGd8MB0GA1UdDgQWBBR6ESQ+J+iXQe0a/KZ2+N+Z +8a9SZjANBgkqhkiG9w0BAQ0FAAOCAgEAVWJHoCxxkqh7b+y93PY4KPy4jJC4gN1S +dAPqttD/yteJ/4mbVel1/KNSoQBk5EpJmRqeBwHCgGJaT/TxYXNw/b8mRPxe/xbb +wieZMqlSmH028UjYDku1eM0IgHISgoCesIR95D5iAOWbMMVUwIHIHTfmhK7DmZVe +SPf7RIkctrpYxZh0Gw8KLZO6Mfy/9tq3dps0A7KS6jjdrF+M9LavPGwFvtfvRMTi +rdteByO2saGAKDvrjtievwlWCNBJlKV1arW9krN7eqJY5YO6eRbX6UjuhbPRgjte +eZ4jL121TBJaKZU7Q/lvYHIWfzstwQdiem2Ua1GyiiEvPZrQlmqQ3gDBtwJQGB4Q +2myP7MY7THiKObjaB8qRsVxKM78ktwAtAYSZv7gZlmSJ/uTMzDV5D2TQxqs7zwCj +sV+psUn4nvh58xP+DW+MYbF/Cpmzvul9FjMKBs270vE1q+gMot27rbQHRRJ4lVN5 +khiG6Oi6blVkPKExIVIiaZ9diXK6NhtWp15PWljNiDxZO+zpkeuw7cKLn/idzmvP +Gcj6m7DqcdsSIHNKbR5iM2VuDhg/j8uBD3uF2Wlymp31TBQgdYWSihJpwZKHNqJD +uq9SmegwI5gYg64KwABZM9hGbl/krXt/0CeCR5HRc+fthanKx/tO2tbCuVT3FR5J +XpKNUy1D78o= +-----END CERTIFICATE----- diff --git a/rust/pv/tests/assets/keys/private_cust.bin b/rust/pv/tests/assets/keys/private_cust.bin new file mode 100644 index 0000000000000000000000000000000000000000..8f4c1fa0e0f772ab306443c58218bac7bdd2bf24 GIT binary patch literal 80 OcmZQzKmm*$6aoNI%_0H- literal 0 HcmV?d00001 diff --git a/rust/pv/tests/assets/keys/public_cust.bin b/rust/pv/tests/assets/keys/public_cust.bin new file mode 100644 index 0000000000000000000000000000000000000000..513e6b42130f2f4fc67c2d0fbfab4a46625afbcb GIT binary patch literal 160 zcmZQzKmo^NO@4|6-FB_!R+!_qWa1LPM;r5$58b%+Q7pYH>66c6qoXt9Ib5^THs)T9 znsk)?<727sJt-^nszRhig#$!CF}0zX#=z*%5V8E@(!&AMgRPiWT2wy2m%dAb`&@JC wn<<-ia(wMPymH2keR9d{*S#J{-EnJ*|F%1SL5!gL`A28IEb_N)w!KvV05#x2bpQYW literal 0 HcmV?d00001