mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
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:
committed by
Jan Höppner
parent
e6add997eb
commit
c6f621d0dc
@@ -8,6 +8,7 @@ Release history for s390-tools (MIT version)
|
|||||||
s390-tools now supports tools written in rust!
|
s390-tools now supports tools written in rust!
|
||||||
|
|
||||||
Add new tools / libraries:
|
Add new tools / libraries:
|
||||||
|
- rust/pv: Library for pv tools written in rust
|
||||||
|
|
||||||
Changes of existing tools:
|
Changes of existing tools:
|
||||||
- genprotimg: add support for add-secret requests
|
- genprotimg: add support for add-secret requests
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../tests/assets
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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))?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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) {}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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![]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(¶m)?;
|
||||||
|
|
||||||
|
Ok(x509store)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify that the given IBM signing keys can be trusted
|
||||||
|
/// -> check the chain: IBMsignKey<-InterCA(s)<-RootCA
|
||||||
|
pub fn verify_chain(
|
||||||
|
store: &X509StoreRef,
|
||||||
|
untrusted_certs: &Stack<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()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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.
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.crl
|
||||||
|
gen_venv/*
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
cert: clean
|
||||||
|
bash create_certs.sh
|
||||||
|
|
||||||
|
clean:
|
||||||
|
bash clean.sh
|
||||||
|
|
||||||
|
.PHONY: clean cert
|
||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
rm -f -- *.key *.crt *.crl
|
||||||
+733
@@ -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))
|
||||||
+17
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
cryptography>=39.0.0
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
A\%¥Ñœ8a—3áÁ·ûØ‹Ý1Ø‚ÕàK—<PðÀwCÁT¬ e8¯7±–øèGi\ù…)ñŽhpØ 8—�|Ë…µ³+ÔÆ5?
|
||||||
Binary file not shown.
@@ -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.
Reference in New Issue
Block a user