rust: Add library for pv tools

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 <seiden@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Steffen Eiden
2023-05-04 14:23:31 +02:00
committed by Jan Höppner
parent e6add997eb
commit c6f621d0dc
71 changed files with 7162 additions and 0 deletions

View File

@@ -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

36
rust/pv/Cargo.toml Normal file
View File

@@ -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

View File

@@ -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"

View File

@@ -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");
}
}
}

View File

@@ -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<Akid>;
}
impl AkidExtension for X509Ref {
fn akid(&self) -> Option<Akid> {
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<Akid> {
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);
}
}

View File

@@ -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<T: ForeignTypeRef>(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<F, T>(
&mut self,
trust: &X509StoreRef,
cert: Option<&X509Ref>,
cert_chain: Option<&StackRef<X509>>,
with_context: F,
) -> Result<T, ErrorStack>
where
F: FnOnce(&mut X509StoreContextRef) -> std::result::Result<T, ErrorStack>;
fn crls(
&mut self,
subj: &X509NameRef,
) -> std::result::Result<Stack<StackableX509Crl>, ErrorStack>;
}
impl X509StoreContextExtension for X509StoreContextRef {
fn init_opt<F, T>(
&mut self,
trust: &X509StoreRef,
cert: Option<&X509Ref>,
cert_chain: Option<&StackRef<X509>>,
with_context: F,
) -> Result<T, ErrorStack>
where
F: FnOnce(&mut X509StoreContextRef) -> std::result::Result<T, ErrorStack>,
{
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<Stack<StackableX509Crl>, 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))
}
}
}
}
}

View File

@@ -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");
}

View File

@@ -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<X509CrlRef> for StackableX509Crl {
fn borrow(&self) -> &X509CrlRef {
&**self
}
}
#[allow(clippy::explicit_auto_deref)]
impl ::std::convert::AsRef<X509CrlRef> 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<MemBioSlice<'a>, 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<Vec<X509Crl>, 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<X509Crl> 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<StackableX509Crl> for X509Crl {
fn from(value: StackableX509Crl) -> Self {
unsafe {
openssl_sys::X509_CRL_up_ref(value.as_ptr());
X509Crl::from_ptr(value.as_ptr())
}
}
}

View File

@@ -0,0 +1 @@
../../tests/assets

247
rust/pv/src/brcb.rs Normal file
View File

@@ -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<R>(img: &mut R) -> Result<bool>
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<R>(img: &mut R) -> Result<Self>
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::<BootHdrHead>()];
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::<BootHdrHead>() 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<BigEndian>,
version: U32<BigEndian>,
size: U32<BigEndian>,
iv: [u8; 12],
res1: u32,
nks: U64<BigEndian>,
sea: U64<BigEndian>,
nep: U64<BigEndian>,
pcf: U64<BigEndian>,
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);
}
}

180
rust/pv/src/cli.rs Normal file
View File

@@ -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<String>,
/// Disable the host-key document verification.
///
/// Does not require the host-key documents to be valid.
/// Do not use for a production request unless you verified the host-key document 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<String>,
/// 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<String>,
/// Make no attempt to download CRLs.
#[arg(long, requires("certs"))]
pub offline: bool,
/// Use FILE as the root-CA certificate for the verification.
///
/// If omitted, the system wide root CAs installed on the system are used.
/// Use this only if you trust the specified certificate.
#[arg(long, requires("certs"))]
pub root_ca: Option<String>,
}
impl CertificateOptions {
/// Returns the verifier of this [`CertificateOptions`] based on the given CLI options.
///
/// # Errors
///
/// This function will return an error if [`crate::request::HkdVerifier`] cannot be created.
pub fn verifier(&self) -> Result<Box<dyn crate::verify::HkdVerifier>> {
use crate::verify::{CertVerifier, NoVerifyHkd};
match self.no_verify {
true => {
log::warn!(
"Host-key document verification is disabled. The secret may not be protected."
);
Ok(Box::new(NoVerifyHkd))
}
false => Ok(Box::new(CertVerifier::new(
&self.certs,
&self.crls,
&self.root_ca,
self.offline,
)?)),
}
}
}
/// stdout
#[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<Box<dyn Write>> {
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<Box<dyn Read>> {
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());
}
}
}

325
rust/pv/src/crypto.rs Normal file
View File

@@ -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<Self> {
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<Aes256Key> 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<const COUNT: usize>(
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<Private>, k2: &PKey<Public>) -> Result<Aes256Key> {
let mut der = Deriver::new(k1)?;
der.set_peer(k2)?;
let mut key = der.derive_to_vec()?;
key.extend([0, 0, 0, 1]);
let secr = Secret::new(key);
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<const COUNT: usize>() -> 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<PKey<Private>> {
let group = EcGroup::from_curve_name(Nid::SECP521R1)?;
let key: EcKey<Private> = EcKey::generate(&group)?;
PKey::from_ec_key(key).map_err(Error::Crypto)
}
/// 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<Vec<u8>> {
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<u8>`] 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<Vec<u8>> {
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<DigestBytes> {
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);
}
}

196
rust/pv/src/error.rs Normal file
View File

@@ -0,0 +1,196 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2023
/// Result type for this crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
/// 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;

206
rust/pv/src/lib.rs Normal file
View File

@@ -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<const N: usize> {
/// 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 <version>`
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))?,
)
};
}

48
rust/pv/src/log.rs Normal file
View File

@@ -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) {}
}

528
rust/pv/src/req.rs Normal file
View File

@@ -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<Private>) -> Result<Vec<u8>> {
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<Private>, to: &mut Vec<u8>) -> 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<Public>);
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<Public>) -> 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<Private>,
to: &mut Vec<u8>,
) -> 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<Private>,
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<I, P, S>(iv: I, priv_key: P, prot_key: S) -> Result<Self>
where
I: Into<Option<[u8; 12]>>,
P: Into<Option<PKey<Private>>>,
S: Into<Option<SymKey>>,
{
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<Self> {
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<O>(
&self,
version: RequestVersion,
aad: &Vec<Aad>,
encr_size: usize,
magic: O,
) -> Result<Vec<u8>>
where
O: Into<Option<RequestMagic>>,
{
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<Aad>,
encr_size: usize,
magic: Option<RequestMagic>,
) -> Result<Vec<u8>> {
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<u8> = Vec::with_capacity(2048);
//reserve space for the request header
auth_data.resize(std::mem::size_of::<RequestHdr>(), 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::<RequestHdr>()].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<EcdhPubkeyCoord> {
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<Vec<u8>> {
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<u8>`] 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<Vec<u8>> {
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<u8> = 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<Self, Self::Error> {
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<BigEndian>,
rql: U32<BigEndian>,
iv: [u8; 12],
reserved1c: [u8; 4],
reserved20: [u8; 7],
nks: u8,
reserved28: u32,
sea: U32<BigEndian>,
}
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<Vec<u8>>;
/// 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<Public>);
}
#[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::<Aad>::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<Keyslot> = (0..257).map(|_| Keyslot::new(host_key.clone())).collect();
let mut aad = Vec::<Aad>::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::<Aad>::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);
}
}

117
rust/pv/src/secret.rs Normal file
View File

@@ -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<const COUNT: usize> 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<u8> {
/// Reliably overwrites the given buffer with zeros,
/// by overwriting the whole vector's capacity with zeros.
fn zeroize(&mut self) {
//TODO use `volatile_set_memory` when stabilized
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: Zeroize>(C);
impl<C: Zeroize> Secret<C> {
/// 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<C: Zeroize + Debug> Debug for Secret<C> {
#[allow(unreachable_code)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// do NOT leak secrets in production builds
#[cfg(not(debug_assertions))]
return write!(f, "Secret(***)");
let mut b = f.debug_tuple("Secret");
b.field(&self.0);
b.finish()
}
}
impl<C: Zeroize> From<C> for Secret<C> {
fn from(v: C) -> Secret<C> {
Secret(v)
}
}
impl<C: Zeroize> Zeroize for Secret<C> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<C: Zeroize> Drop for Secret<C> {
fn drop(&mut self) {
self.0.zeroize();
}
}

120
rust/pv/src/test_utils.rs Normal file
View File

@@ -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<P: AsRef<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<u8> {
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<Private>, PKey<Public>) {
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<PKey<Public>, 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<PKey<Private>, 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)
}

658
rust/pv/src/utils.rs Normal file
View File

@@ -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<T>: From<T> + 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<BigEndian>);
impl Flags<u64> 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<u64> 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<BigEndian>);
impl Flags<u64> 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<u64> 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<dyn Error>> {
/// 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<dyn Error>> {
/// 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<u64> {
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<P: AsRef<Path>, 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<P: AsRef<Path>>(path: P, ctx: &str) -> Result<Vec<u8>> {
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<R: Read>(rd: &mut R, path: &str, ctx: &str) -> Result<Vec<u8>> {
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<D: AsRef<[u8]>>(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<D: AsRef<[u8]>, 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<Vec<X509Crl>> {
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<Vec<X509>> {
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<u8> {
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![]));
}
}

208
rust/pv/src/uvdevice.rs Normal file
View File

@@ -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<C: UvCmd>(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<Self> {
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<Self> {
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<C: UvCmd>(&self, cmd: &mut C) -> Result<UvcSuccess> {
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),
}),
}
}
}

128
rust/pv/src/uvdevice/ffi.rs Normal file
View File

@@ -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::<uvio_ioctl_cb>())
}
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)
}

View File

@@ -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<Lsb0Flags64>,
}
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<Self> {
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<uvio_uvdev_info> 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"),
}
}
}

View File

@@ -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<IoctlCtx> = Mutex::new(IoctlCtx::new());
}
fn get_lock<T>(m: &'static Mutex<T>) -> MutexGuard<'static, T> {
match m.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
struct IoctlCtx {
modify: Box<dyn FnMut(&mut ffi::uvio_ioctl_cb) -> 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<F>(&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<Vec<u8>>);
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);
}

80
rust/pv/src/uvsecret.rs Normal file
View File

@@ -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<UserDataType> for AddSecretMagic {
fn from(tp: UserDataType) -> Self {
Self {
magic: Self::MAGIC,
tp,
}
}
}
const SECRET_ID_SIZE: usize = 32;
fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
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)
}

View File

@@ -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<u8> {
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<GuestSecret> 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<F: Into<UvFlags>>(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<Vec<u8>> {
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<AddSecretFlags> 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<AddSecretVersion> 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<Keyslot>,
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<Vec<u8>> {
let cust_pub_key = ctx.key_coords()?;
let secr_auth = self.conf.secret.dump_auth();
let mut aad: Vec<Aad> = 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<u8>, Vec<u8>)> {
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<Vec<u8>> {
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<Vec<u8>> {
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<Public>) {
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::<u8>::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());
}
}

View File

@@ -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
}

View File

@@ -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<O>(name: &str, secret: O) -> Result<GuestSecret>
where
O: Into<Option<[u8; SECRET_SIZE]>>,
{
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<E>(self, s: &str) -> Result<Self::Value, E>
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,
],
);
}
}

View File

@@ -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<SecretEntry>,
}
impl SecretList {
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
w.write_u16::<BigEndian>(num_s)?;
w.write_u16::<BigEndian>(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: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
let num_s = r.read_u16::<BigEndian>()?;
let total_num_secrets = r.read_u16::<BigEndian>()?;
let mut v: Vec<SecretEntry> = 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<ListCmd> for SecretList {
type Error = Error;
fn try_from(mut list: ListCmd) -> Result<SecretList> {
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<S: Serializer>(v: &U32<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_u32(v.get())
}
fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error> {
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<BigEndian>,
#[serde(serialize_with = "ser_u16")]
stype: U16<BigEndian>,
#[serde(serialize_with = "ser_u32")]
len: U32<BigEndian>,
#[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::<SecretEntry>(), 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);
}
}

124
rust/pv/src/uvsecret/uvc.rs Normal file
View File

@@ -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<u8>);
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<u8>);
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<R: Read>(bin_add_secret_req: &mut R) -> Result<Self> {
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,
}
}
}

158
rust/pv/src/verify.rs Normal file
View File

@@ -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<Stack<StackableX509Crl>> {
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<String>,
offline: bool,
) -> Result<Self> {
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,
})
}
}

View File

@@ -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<String>,
crl_paths: &[String],
cert_w_crl_paths: &[String],
) -> Result<X509StoreBuilder> {
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(&param)?;
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<X509>,
sign_keys: &[X509],
) -> Result<()> {
fn verify_fun(ctx: &mut X509StoreContextRef) -> std::result::Result<bool, ErrorStack> {
// 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<X509>) -> Result<(X509, Stack<X509>)> {
let ibm_z_sign_key = get_ibm_z_sign_key(&certs)?;
let mut chain = Stack::<X509>::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<X509> {
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>::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>::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<String> {
let mut res = Vec::<String>::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<Option<Vec<X509Crl>>> {
struct Buf(Vec<u8>);
impl Handler for Buf {
fn write(&mut self, data: &[u8]) -> std::result::Result<usize, WriteError> {
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<X509Name, ErrorStack> {
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<T: Stackable>(
e: ErrorStack,
) -> std::result::Result<Stack<T>, openssl::error::ErrorStack> {
match e.errors().len() {
0 => Stack::<T>::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()
));
}
}

282
rust/pv/src/verify/test.rs Normal file
View File

@@ -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::<X509>::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::<X509>::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);
}

View File

@@ -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<ExtSecret>,
flags: AddSecretFlags,
cuid: Option<ConfigUid>,
hkd: PKey<Public>,
ctx: &ReqEncrCtx,
) -> Result<Vec<u8>> {
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<Public>, 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<E>(
guest_secret: GuestSecret,
ext_secret: E,
flags: AddSecretFlags,
cuid: bool,
) -> Result<Vec<u8>>
where
E: Into<Option<ExtSecret>>,
{
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);
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,19 @@
-----BEGIN X509 CRL-----
MIIDITCCAQkCAQEwDQYJKoZIhvcNAQELBQAwgb0xCzAJBgNVBAYTAlVTMTQwMgYD
VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u
MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv
cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEPMA0GA1UEBwwGQXJtb25rMR4wHAYD
VQQLDBVJQk0gWiBJbnRlcm1lZGlhdGUgQ0EXDTIzMDMxOTExMDQ0N1oYDzIzODcx
MjMxMTEwNDQ3WjAVMBMCAgG8Fw0yMzAzMjgxMTA0NDdaMA0GCSqGSIb3DQEBCwUA
A4ICAQA+KQrjx/6nKzPggDpKEAzH6XxhUU4CZmyUKFirUdenQBjYLag2Nono75o8
9DVK3vuK7aeg4tIkUYOBcOEgYx0wEPU6PEE+yOO6KErn7qilN9gRnDHOTrfT0iNY
Rabyeat256gyfrsS84ZB7MnbhecWwC6sP2NiH18VCprH865X6sm5SxAfez1zITV3
YVtudX4UczqbfpDgP4BU5ERMI71tqj4gKjaHFkC0TGizSphiINDKPmUMbd1w7FHg
Ilj+7pNZS377GCX9JzoTaKLuMBiblkwSTUJic7Z2BJZlTgm18hhfT3AZQDVvkq0A
AEPxQzm3be3ZUb+zJvueTeizVHkd3Eufnk69p7w4wNRQMwfj/icm27RZa3bBpF2o
esr2Ptik9ZBN81oMAakONZ4Wxuf0n/KBd6VBjy6WkbalKGVoZn70Nke3+9HGWSIh
bgbuHt7XAlvsgVChtZWsGsyxYw4p2ku4T2ajUfpxqQY1DDCAThweuHl2FND87Kr1
5sblLLhA3QdjQ0EavsCV1646xorvoyw7YdHkqCPjRb1FsPWm/IePbtu+w9/VjDRF
KcHgBZBWmmQHj/9ykSI9pA5J7R7Nij6sX6Iu1g2yKiPnXeRQFiwhgsxslNk8eJfq
cK4c4HhnNtXa/c8jHcbymwqkF8Qltz0cbEW1usxZ2u6153pyPQ==
-----END X509 CRL-----

View File

@@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIGpjCCBI6gAwIBAgIUMp+RLATMshrQnbOfPkwKoDNyYhcwDQYJKoZIhvcNAQEL
BQAwgbUxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEPMA0GA1UEBwwGQXJtb25rMRYwFAYDVQQLDA1JQk0gWiBSb290IENBMCAXDTIz
MDMyOTA5MDQ0N1oYDzIzODcxMjMxMDkwNDQ3WjCBvTELMAkGA1UEBhMCVVMxNDAy
BgNVBAoMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29ycG9yYXRp
b24xNDAyBgNVBAMMK0ludGVybmF0aW9uYWwgQnVzaW5lc3MgTWFjaGluZXMgQ29y
cG9yYXRpb24xETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBcm1vbmsxHjAc
BgNVBAsMFUlCTSBaIEludGVybWVkaWF0ZSBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAK75zkJO8mpqPrD9vlSsfJgW7hbioQpuphSo1+3q9cAAWKFg
TYUcGBUNR/lUVdYqgzo3AglUwldWfeO9mBCIGNSN/heLFt1KzNutBsnE3YEeGKpM
7nHhMzh41otFdpZEZfrsGXGok07dy2mEV0mx72e9ALWbXFhxYsdWdSSVTlBH8xcd
38rAzfAiTbjgAUnTIdCPjAJKbXSDBXGXZ3+iuhFxNtSWyJr1AsxPzESErCPzUQjr
m8TM24lKq69zimTEkN4uwP5U8s2JPzbKosg2k24RbpDgkjO8iNK7RL9SMRUE8daP
+eru5EwN4BlZfsNpZDFbILxbt/2sxqmdsx/Nupa5ZAfcHRs88p4l1D3QIiZzaSEc
nCotM/kmnHWbgeJbkGbC9fD23dNJ29uqZU0fbRnG4HpSutrYD6lPg7PXnMt5tT+f
0+wQds38woXT9qW/kN/2WtkVYDhyVjxCgD8iHOZpz2LUmvJfi7Gz9B/DeW1dzgbo
cGxz9ee+R+T5KcKg+XvHD6slk82GrSM21b7zJeK92bJtjkqxBtQf+YgcKtOO7QX7
37C1XvSHFnKvyyRJrldJFGEKfK2C66hdASHRdbUhWHFo1AA7VqzKB1fU9M2+ltUZ
zRpYD7X36OtRY1KsHHn+SVvsn404hWwgPblZ04nsMPanj+jsN//6M9r5lMezAgMB
AAGjgaEwgZ4wOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovLzEyNy4wLjAuMToxMjM0
L2NybC9mYWtlX3Jvb3RfY2EuY3JsMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgEGMB8GA1UdIwQYMBaAFNnX872GsEyiVMz7up1p/Nq/K+0VMB0GA1UdDgQW
BBT2zEtzeetYaOXAVw0Z9Pda1V9n1DANBgkqhkiG9w0BAQsFAAOCAgEAb7xPI93b
gqpXi4E/hAl7Vh354kKV+1smd1Omf7mQZbTrKBo6o1s0ZpXCbC9+WzK47R21VPNe
EgZ3uzTkURu4AX+5OnAMiWDFAGRSDt8ZXk6ZukWP7OmHsnLCu2strdhrvC4EhMF5
G9VPIQsTx16CpprrVjzVzJg4i/X+U9dypvnAQeneyz4Ul/kPr6di8bOB3FiBeEDu
dOkVTbnlDa+wMQlCvqlFroFjEHZBKK/+PVIx9cJYj1grmzgqzm2FGUs5Wvcixpgb
uSHCQY9JP8Hy0xl3wx58VaymUK4EMfs612CfzOMClaiooDYuZgzVfhalU6g268nc
PQ9RCRJtJuda4mJ2H3Rag79sIiCVV31tE6tLXjOGebuO0vEB8wOjJc4YW1gtrZFy
GltT+HMdFgjO2c6HynpkmtqS8axQAw2hVaOpdJbDW+R0jHihO98FAgXR27TqTFp9
sjBacfITeYRXGjQTDU1qxuEfoLnTZRIct1TjRTI1HBT8fl1exxgKUyEawH3MUCo7
LsTPSNASKkJH3Rp29be9xTejUx2HUUwOE/DIF0HKaN+aAc8TR31/4HvC3bf5VPyD
wIwazpjVDZlQ2w+Wry1zNezNCPKiWtkfkj+TkT32h4ZfQEX8t0MUpKnL1LOSV/8h
PV357EenQb+f9DeC4BIbmipovLaUWmml10c=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,4 @@
*.key
*.crt
*.crl
gen_venv/*

View File

@@ -0,0 +1,7 @@
cert: clean
bash create_certs.sh
clean:
bash clean.sh
.PHONY: clean cert

View File

@@ -0,0 +1,2 @@
#!/bin/bash
rm -f -- *.key *.crt *.crl

View File

@@ -0,0 +1,733 @@
#!/bin/env python3
import datetime
import os.path
from enum import Enum
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import rsa
ONE_DAY = datetime.timedelta(1, 0, 0)
def createEcKeyPair(curve=ec.SECP521R1):
return ec.generate_private_key(curve=curve, backend=default_backend())
def createRSAKeyPair(size=4096):
return rsa.generate_private_key(
public_exponent=65537, key_size=size, backend=default_backend()
)
def createCRL(pkey, issuer, serial_numbers=None, last_update=None, next_update=None):
serial_numbers = [333] if serial_numbers is None else serial_numbers
builder = x509.CertificateRevocationListBuilder()
builder = builder.issuer_name(issuer)
last_update = last_update or datetime.datetime.today() - 10 * ONE_DAY
next_update = next_update or datetime.datetime.today() + 365 * 365 * ONE_DAY
builder = builder.last_update(last_update)
builder = builder.next_update(next_update)
for sn in serial_numbers:
revoked_cert = (
x509.RevokedCertificateBuilder()
.serial_number(sn)
.revocation_date(
datetime.datetime.today() - ONE_DAY,
)
.build(default_backend())
)
builder = builder.add_revoked_certificate(revoked_cert)
crl = builder.sign(
private_key=pkey, algorithm=hashes.SHA256(), backend=default_backend()
)
return crl
def createRootCA(pkey, subject):
issuer = subject
ca = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(pkey.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365 * 365)
)
.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
# Sign our certificate with our private key
)
.add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=True,
crl_sign=True,
),
critical=True,
)
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(pkey.public_key()),
critical=False,
)
.sign(pkey, hashes.SHA512(), default_backend())
)
return ca
class CertType(Enum):
ROOT_CA = 1
INTER_CA = 2
SIGNING_CERT = 3
HOST_CERT = 4
def createCert(
pkey,
subject,
crl_uri,
issuer_crt=None,
issuer_pkey=None,
t=CertType.ROOT_CA,
not_before=None,
not_after=None,
pub_key=None,
):
sha = hashes.SHA256
not_before = not_before or datetime.datetime.utcnow()
not_after = not_after or datetime.datetime.utcnow() + datetime.timedelta(
days=365 * 365
)
crl_dp = None
if crl_uri is not None:
crl_dp = x509.DistributionPoint(
[x509.UniformResourceIdentifier(crl_uri)],
relative_name=None,
reasons=None,
crl_issuer=None,
)
cert_builder = x509.CertificateBuilder().subject_name(subject)
if t == CertType.ROOT_CA:
cert_builder = cert_builder.issuer_name(subject)
issuer_pub_key = pkey.public_key()
else:
cert_builder = cert_builder.issuer_name(issuer_crt.subject)
issuer_pub_key = issuer_crt.public_key()
if pub_key is None:
pub_key = pkey.public_key()
cert_builder = (
cert_builder.public_key(pub_key)
.serial_number(x509.random_serial_number())
.not_valid_before(not_before)
.not_valid_after(not_after)
)
if crl_dp is not None:
cert_builder = cert_builder.add_extension(
x509.CRLDistributionPoints([crl_dp]),
critical=False,
)
if t == CertType.ROOT_CA:
cert_builder = cert_builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=True,
crl_sign=True,
),
critical=True,
)
elif t == CertType.INTER_CA:
cert_builder = cert_builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None),
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=True,
crl_sign=True,
),
critical=True,
)
elif t == CertType.SIGNING_CERT:
cert_builder = (
cert_builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
)
.add_extension(
x509.KeyUsage(
digital_signature=True,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
key_cert_sign=False,
crl_sign=False,
),
critical=True,
)
.add_extension(
x509.ExtendedKeyUsage(
[x509.oid.ExtendedKeyUsageOID.CODE_SIGNING]
),
critical=False,
)
)
else:
sha = hashes.SHA512
cert_builder = cert_builder.add_extension(
x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
content_commitment=False,
data_encipherment=False,
key_agreement=True,
encipher_only=False,
decipher_only=False,
key_cert_sign=False,
crl_sign=False,
),
critical=True,
)
cert_builder = cert_builder.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(issuer_pub_key),
critical=False,
).add_extension(
x509.SubjectKeyIdentifier.from_public_key(pkey.public_key()),
critical=False,
)
return cert_builder.sign(issuer_pkey, sha(), default_backend())
def getPrivKey(path, create_priv_key):
pkey = None
if os.path.isfile(path):
with open(path, "rb") as f:
pkey = serialization.load_pem_private_key(
f.read(), password=None, backend=default_backend()
)
if not pkey:
pkey = create_priv_key()
with open(path, "wb") as f:
f.write(
pkey.private_bytes(
serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
)
return pkey
if __name__ == "__main__":
MOCKUP_CRL_DIST = "http://127.0.0.1:1234/crl/"
# create root CA
root_ca_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Root CA"),
]
)
root_ca_pkey = getPrivKey("root_ca.key", createRSAKeyPair)
root_ca_crl = createCRL(root_ca_pkey, root_ca_subject, [333])
root_ca_crt = createCert(
pkey=root_ca_pkey,
subject=root_ca_subject,
issuer_pkey=root_ca_pkey,
crl_uri=None,
t=CertType.ROOT_CA,
)
fake_root_ca_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Root CA"),
]
)
fake_root_ca_pkey = getPrivKey("fake_root_ca.key", createRSAKeyPair)
fake_root_ca_crl = createCRL(fake_root_ca_pkey, fake_root_ca_subject, [333])
fake_root_ca_valid_crl = createCRL(root_ca_pkey, fake_root_ca_subject, [333])
fake_root_ca_crt = createCert(
pkey=fake_root_ca_pkey,
subject=fake_root_ca_subject,
issuer_pkey=fake_root_ca_pkey,
crl_uri=None,
t=CertType.ROOT_CA,
)
fake_root_ca_crt = createRootCA(fake_root_ca_pkey, fake_root_ca_subject)
# create intermediate CA
inter_ca_pkey = getPrivKey("inter_ca.key", createRSAKeyPair)
inter_ca_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Intermediate CA"
),
]
)
inter_ca_crt = createCert(
pkey=inter_ca_pkey,
subject=inter_ca_subject,
issuer_crt=root_ca_crt,
issuer_pkey=root_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "root_ca.crl",
t=CertType.INTER_CA,
)
fake_inter_ca_pkey = getPrivKey("fake_inter_ca.key", createRSAKeyPair)
fake_inter_ca_crt = createCert(
pkey=fake_inter_ca_pkey,
subject=inter_ca_subject,
issuer_crt=fake_root_ca_crt,
issuer_pkey=fake_root_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "fake_root_ca.crl",
t=CertType.INTER_CA,
)
fake_inter_ca_crl = createCRL(fake_inter_ca_pkey, inter_ca_subject, [444])
# create ibm certificate
ibm_pkey = getPrivKey("ibm.key", createRSAKeyPair)
ibm_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Poughkeepsie"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key Signing Service"
),
]
)
ibm_crt = createCert(
pkey=ibm_pkey,
subject=ibm_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
)
ibm_expired_crt = createCert(
pkey=ibm_pkey,
subject=ibm_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
not_before=datetime.datetime.today() - 2 * 365 * ONE_DAY,
not_after=datetime.datetime.today() - 1 * 365 * ONE_DAY,
)
#create revoked ibm certificate
ibm_rev_pkey = getPrivKey("ibm.key", createRSAKeyPair)
ibm_rev_crt = createCert(
pkey=ibm_rev_pkey,
subject=ibm_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
)
# create inter CLRs
inter_ca_crl = createCRL(inter_ca_pkey, inter_ca_subject, [444, ibm_rev_crt.serial_number])
inter_ca_invalid_signer_crl = createCRL(root_ca_pkey, inter_ca_subject, [444])
inter_ca_invalid_date_crl = createCRL(
inter_ca_pkey,
inter_ca_subject,
[444],
last_update=datetime.datetime.today() - 2 * ONE_DAY,
next_update=datetime.datetime.today() - 1 * ONE_DAY,
)
# create signing key using wrong OU in subject
ibm_wrong_subject_pkey = getPrivKey("ibm_wrong_subject.key", createRSAKeyPair)
ibm_wrong_subject_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Poughkeepsie"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"Key Signing Service Invalid"
),
]
)
ibm_wrong_subject_crl = createCRL(ibm_wrong_subject_pkey, ibm_wrong_subject_subject, [555])
ibm_wrong_subject_crt = createCert(
pkey=ibm_wrong_subject_pkey,
subject=ibm_wrong_subject_subject,
issuer_crt=inter_ca_crt,
issuer_pkey=inter_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "inter_ca.crl",
t=CertType.SIGNING_CERT,
)
fake_ibm_pkey = getPrivKey("fake_ibm.key", createRSAKeyPair)
fake_ibm_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Poughkeepsie"),
x509.NameAttribute(
NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key Signing Service"
),
]
)
fake_ibm_crl = createCRL(fake_ibm_pkey, fake_ibm_subject, [555])
fake_ibm_crt = createCert(
pkey=fake_ibm_pkey,
subject=fake_ibm_subject,
issuer_crt=fake_root_ca_crt,
issuer_pkey=fake_root_ca_pkey,
crl_uri=MOCKUP_CRL_DIST + "fake_root_ca.crl",
t=CertType.SIGNING_CERT,
)
def host_subj():
return x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
# create host certificate
host_pkey = getPrivKey("host.key", createEcKeyPair)
host_subject = host_subj()
host_crt = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
host_crt_expired = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
not_before=datetime.datetime.today() - 2 * 365 * ONE_DAY,
not_after=datetime.datetime.today() - 1 * 365 * ONE_DAY,
)
host_uri_na_crt = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "notavailable",
t=CertType.HOST_CERT,
)
host_pkey = getPrivKey("host.key", createEcKeyPair)
host_subject = host_subj()
host_crt = createCert(
pkey=host_pkey,
subject=host_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
host_rev_pkey = getPrivKey("host_rev.key", createEcKeyPair)
host_rev_subject = host_subj()
host_rev_crt = createCert(
pkey=host_rev_pkey,
subject=host_rev_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
# some IBM revocation lists
ibm_crl = createCRL(ibm_pkey, ibm_subject, [555, host_rev_crt.serial_number])
ibm_outdated_early_crl = createCRL(
ibm_pkey,
ibm_subject,
[],
last_update=datetime.datetime.today() + 1000 * 365 * ONE_DAY,
next_update=datetime.datetime.today() + 1001 * 365 * ONE_DAY,
)
ibm_outdated_late_crl = createCRL(
ibm_pkey,
ibm_subject,
[],
last_update=datetime.datetime.today() - 2 * 365 * ONE_DAY,
next_update=datetime.datetime.today() - 1 * 365 * ONE_DAY,
)
ibm_wrong_issuer_crl = createCRL(ibm_pkey, inter_ca_subject, [])
ibm_invalid_hash_crl = createCRL(
inter_ca_pkey, ibm_subject, [555, host_crt.serial_number]
)
# create host certificate issued by a non-valid signing key
host_invalid_signing_key_pkey = getPrivKey("host_invalid_signing_key.key", createEcKeyPair)
host_invalid_signing_key_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
host_invalid_signing_key_crt = createCert(
pkey=host_invalid_signing_key_pkey,
subject=host_invalid_signing_key_subject,
issuer_crt=ibm_wrong_subject_crt,
issuer_pkey=ibm_wrong_subject_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm_wrong_subject.crl",
t=CertType.HOST_CERT,
)
host2_pkey = getPrivKey("host2.key", createEcKeyPair)
host2_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
host2_crt = createCert(
pkey=host2_pkey,
subject=host2_subject,
issuer_crt=ibm_crt,
issuer_pkey=ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "ibm.crl",
t=CertType.HOST_CERT,
)
fake_host_pkey = getPrivKey("fake_host.key", createEcKeyPair)
fake_host_subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(
NameOID.ORGANIZATION_NAME,
u"International Business Machines Corporation",
),
x509.NameAttribute(
NameOID.COMMON_NAME, u"International Business Machines Corporation"
),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"New York"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"Armonk"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u"IBM Z Host Key"),
]
)
fake_host_crt = createCert(
pkey=fake_host_pkey,
subject=fake_host_subject,
issuer_crt=fake_ibm_crt,
issuer_pkey=fake_ibm_pkey,
crl_uri=MOCKUP_CRL_DIST + "fake_ibm.crt",
t=CertType.HOST_CERT,
)
#TODO DER chain
# store CA
with open("root_ca.crt", "wb") as f:
f.write(root_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("root_ca.crl", "wb") as f:
f.write(root_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("root_ca.chained.crt", "wb") as f:
f.write(root_ca_crt.public_bytes(serialization.Encoding.PEM))
f.write(root_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_root_ca.crt", "wb") as f:
f.write(fake_root_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("fake_root_ca.crl", "wb") as f:
f.write(fake_root_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_root_ca_valid.crl", "wb") as f:
f.write(fake_root_ca_valid_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.crt", "wb") as f:
f.write(inter_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.crl", "wb") as f:
f.write(inter_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.invalid_date.crl", "wb") as f:
f.write(inter_ca_invalid_date_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.invalid_signer.crl", "wb") as f:
f.write(inter_ca_invalid_signer_crl.public_bytes(serialization.Encoding.PEM))
with open("inter_ca.chained.crt", "wb") as f:
f.write(inter_ca_crt.public_bytes(serialization.Encoding.PEM))
f.write(inter_ca_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_inter_ca.crt", "wb") as f:
f.write(fake_inter_ca_crt.public_bytes(serialization.Encoding.PEM))
with open("fake_inter_ca.crl", "wb") as f:
f.write(fake_inter_ca_crl.public_bytes(serialization.Encoding.PEM))
# store IBM
with open("ibm.crt", "wb") as f:
f.write(ibm_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_rev.crt", "wb") as f:
f.write(ibm_rev_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_expired.crt", "wb") as f:
f.write(ibm_expired_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm.crl", "wb") as f:
f.write(ibm_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm.chained.crt", "wb") as f:
f.write(ibm_crl.public_bytes(serialization.Encoding.PEM))
f.write(ibm_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_outdated_early.crl", "wb") as f:
f.write(ibm_outdated_early_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_outdated_late.crl", "wb") as f:
f.write(ibm_outdated_late_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_wrong_issuer.crl", "wb") as f:
f.write(ibm_wrong_issuer_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_invalid_hash.crl", "wb") as f:
f.write(ibm_invalid_hash_crl.public_bytes(serialization.Encoding.PEM))
with open("ibm_wrong_subject.crt", "wb") as f:
f.write(ibm_wrong_subject_crt.public_bytes(serialization.Encoding.PEM))
with open("ibm_wrong_subject.crl", "wb") as f:
f.write(ibm_wrong_subject_crl.public_bytes(serialization.Encoding.PEM))
with open("fake_ibm.crt", "wb") as f:
f.write(fake_ibm_crt.public_bytes(serialization.Encoding.PEM))
with open("fake_ibm.crl", "wb") as f:
f.write(fake_ibm_crl.public_bytes(serialization.Encoding.PEM))
# store host
with open("host.crt", "wb") as f:
f.write(host_crt.public_bytes(serialization.Encoding.PEM))
with open("host_uri_na.crt", "wb") as f:
f.write(host_uri_na_crt.public_bytes(serialization.Encoding.PEM))
# store host key issued by a signing key using the wrong subject OU
with open("host_invalid_signing_key.crt", "wb") as f:
f.write(host_invalid_signing_key_crt.public_bytes(serialization.Encoding.PEM))
# store revoked host
with open("host_rev.crt", "wb") as f:
f.write(host_rev_crt.public_bytes(serialization.Encoding.PEM))
# store host2
with open("host2.crt", "wb") as f:
f.write(host2_crt.public_bytes(serialization.Encoding.PEM))
# store fake host
with open("fake_host.crt", "wb") as f:
f.write(fake_host_crt.public_bytes(serialization.Encoding.PEM))
with open("host_crt_expired.crt", "wb") as f:
f.write(host_crt_expired.public_bytes(serialization.Encoding.PEM))
# store a DER cert and crl
with open("der.crt", "wb") as f:
f.write(ibm_crt.public_bytes(serialization.Encoding.DER))
with open("der.crl", "wb") as f:
f.write(ibm_crl.public_bytes(serialization.Encoding.DER))

View File

@@ -0,0 +1,17 @@
#!/bin/bash
if [ $# -eq 0 ]; then
path="."
else
path="$1"
fi
if test -f "${path}"/host.crt; then
exit 0
fi
python -m venv "${path}"/gen_venv
source "${path}"/gen_venv/bin/activate
pip3 install -r "${path}"/requirements.txt
cd "${path}" || exit 2
python3 ./create_certs.py
deactivate

View File

@@ -0,0 +1 @@
cryptography>=39.0.0

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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-----

Binary file not shown.

View File

@@ -0,0 +1 @@
A\%<25>ќ8a<>3<EFBFBD><33><EFBFBD><EFBFBD>؋<EFBFBD>1؂խ<D882>K<EFBFBD><P<10><>wC<77>T<EFBFBD> e8<15>7<EFBFBD><37><EFBFBD><1E><>Gi \<1E><>)<1D><>hpؠ8<D8A0><38><><CB85>+<2B> <15>5?

Binary file not shown.

View File

@@ -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-----

Binary file not shown.

Binary file not shown.