mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
rust/pv: Support for writing data in PEM format
Use existing OpenSSL functionalities to create PEM files containing arbitrary data. Acked-by: Marc Hartmayer <marc@linux.ibm.com> Acked-by: Christoph Schlameuss <schlameuss@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
1e44ace41d
commit
d1636168b2
@@ -106,6 +106,9 @@ pub enum Error {
|
||||
)]
|
||||
AddDataMissing(&'static str),
|
||||
|
||||
#[error("An ASCII string was expected, but non-ASCII characters were received.")]
|
||||
NonAscii,
|
||||
|
||||
// errors from other crates
|
||||
#[error(transparent)]
|
||||
PvCore(#[from] pv_core::Error),
|
||||
|
||||
@@ -37,6 +37,7 @@ mod brcb;
|
||||
mod crypto;
|
||||
mod error;
|
||||
mod openssl_extensions;
|
||||
mod pem_utils;
|
||||
mod req;
|
||||
mod utils;
|
||||
mod uvattest;
|
||||
@@ -71,6 +72,11 @@ pub mod attest {
|
||||
};
|
||||
}
|
||||
|
||||
/// Definitions and functions to write objects in PEM format
|
||||
pub mod pem {
|
||||
pub use crate::pem_utils::Pem;
|
||||
}
|
||||
|
||||
/// Miscellaneous functions and definitions
|
||||
pub mod misc {
|
||||
pub use pv_core::misc::*;
|
||||
|
||||
85
rust/pv/src/openssl_extensions/bio.rs
Normal file
85
rust/pv/src/openssl_extensions/bio.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use core::slice;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl_sys::BIO_new_mem_buf;
|
||||
use std::ffi::c_int;
|
||||
use std::{marker::PhantomData, ptr};
|
||||
|
||||
pub struct BioMem(*mut openssl_sys::BIO);
|
||||
|
||||
impl Drop for BioMem {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: Pointer is valid. The pointer value is dropped after the free.
|
||||
unsafe {
|
||||
openssl_sys::BIO_free_all(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BioMem {
|
||||
pub fn new() -> Result<Self, ErrorStack> {
|
||||
openssl_sys::init();
|
||||
|
||||
// SAFETY: Returns a valid pointer or null. null-case is tested right after this.
|
||||
let bio = unsafe { openssl_sys::BIO_new(openssl_sys::BIO_s_mem()) };
|
||||
match bio.is_null() {
|
||||
true => Err(ErrorStack::get()),
|
||||
false => Ok(Self(bio)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Copies the content of this slice into a Vec
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
let buf;
|
||||
// SAFTEY: BIO provides a continuous memory that can be used to build a slice.
|
||||
unsafe {
|
||||
let mut ptr = ptr::null_mut();
|
||||
let len = openssl_sys::BIO_get_mem_data(self.0, &mut ptr);
|
||||
buf = slice::from_raw_parts(ptr as *const _ as *const _, len as usize)
|
||||
}
|
||||
buf.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BioMemSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
|
||||
impl Drop for BioMemSlice<'_> {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: Pointer is valid. The pointer value is dropped after the free.
|
||||
unsafe {
|
||||
openssl_sys::BIO_free_all(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BioMemSlice<'a> {
|
||||
pub fn new(buf: &'a [u8]) -> Result<BioMemSlice<'a>, ErrorStack> {
|
||||
openssl_sys::init();
|
||||
|
||||
// SAFETY: `buf` is a slice (i.e. pointer+size) pointing to a valid memory region.
|
||||
// So the resulting bio is valid. Lifetime of the slice is connected by this Rust
|
||||
// structure.
|
||||
assert!(buf.len() <= c_int::MAX as usize);
|
||||
let bio = unsafe {
|
||||
{
|
||||
let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
|
||||
match r.is_null() {
|
||||
true => Err(ErrorStack::get()),
|
||||
false => Ok(r),
|
||||
}
|
||||
}?
|
||||
};
|
||||
|
||||
Ok(BioMemSlice(bio, PhantomData))
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
/// Extensions to the rust-openssl crate
|
||||
mod akid;
|
||||
mod bio;
|
||||
mod crl;
|
||||
mod stackable_crl;
|
||||
|
||||
pub use akid::*;
|
||||
pub use bio::*;
|
||||
pub use crl::*;
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::{marker::PhantomData, ptr};
|
||||
|
||||
use crate::openssl_extensions::bio::BioMemSlice;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::Stackable,
|
||||
x509::{X509Crl, X509CrlRef},
|
||||
};
|
||||
use openssl_sys::BIO_new_mem_buf;
|
||||
use std::ffi::c_int;
|
||||
use std::ptr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
|
||||
@@ -62,44 +60,11 @@ impl Stackable for StackableX509Crl {
|
||||
type StackType = openssl_sys::stack_st_X509_CRL;
|
||||
}
|
||||
|
||||
pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
|
||||
impl Drop for MemBioSlice<'_> {
|
||||
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 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 bio = BioMemSlice::new(pem)?;
|
||||
|
||||
let mut crls = vec![];
|
||||
loop {
|
||||
|
||||
222
rust/pv/src/pem_utils.rs
Normal file
222
rust/pv/src/pem_utils.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::Result;
|
||||
use crate::{openssl_extensions::BioMem, Error};
|
||||
use openssl::error::ErrorStack;
|
||||
use pv_core::request::Confidential;
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
mod ffi {
|
||||
use openssl_sys::BIO;
|
||||
use std::ffi::{c_char, c_int, c_long, c_uchar};
|
||||
extern "C" {
|
||||
pub fn PEM_write_bio(
|
||||
bio: *mut BIO,
|
||||
name: *const c_char,
|
||||
header: *const c_char,
|
||||
data: *const c_uchar,
|
||||
len: c_long,
|
||||
) -> c_int;
|
||||
}
|
||||
}
|
||||
|
||||
/// Thin wrapper around [`CString`] only containing ASCII chars.
|
||||
#[derive(Debug)]
|
||||
struct AsciiCString(CString);
|
||||
|
||||
impl AsciiCString {
|
||||
/// Convert from string
|
||||
///
|
||||
/// # Returns
|
||||
/// Error if string is not ASCII or contains null chars
|
||||
pub(crate) fn from_str(s: &str) -> Result<Self> {
|
||||
match s.is_ascii() {
|
||||
true => Ok(Self(CString::new(s).map_err(|_| Error::NonAscii)?)),
|
||||
false => Err(Error::NonAscii),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const c_char {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to construct the PEM format
|
||||
#[derive(Debug)]
|
||||
struct InnerPem<'d> {
|
||||
name: AsciiCString,
|
||||
header: Option<AsciiCString>,
|
||||
data: &'d [u8],
|
||||
}
|
||||
|
||||
impl<'d> InnerPem<'d> {
|
||||
fn new(name: &str, header: Option<String>, data: &'d [u8]) -> Result<Self> {
|
||||
Ok(Self {
|
||||
name: AsciiCString::from_str(name)?,
|
||||
header: match header {
|
||||
Some(h) => Some(AsciiCString::from_str(&h)?),
|
||||
None => None,
|
||||
},
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate PEM representation of the data
|
||||
fn to_pem(&self) -> Result<Vec<u8>> {
|
||||
let bio = BioMem::new()?;
|
||||
let hdr_ptr = match self.header {
|
||||
// avoid moving variable -> use reference
|
||||
Some(ref h) => h.as_ptr(),
|
||||
None => std::ptr::null(),
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// All pointers point to valid C strings or memory regions
|
||||
let rc = unsafe {
|
||||
ffi::PEM_write_bio(
|
||||
bio.as_ptr(),
|
||||
self.name.as_ptr(),
|
||||
hdr_ptr,
|
||||
self.data.as_ptr(),
|
||||
self.data.len() as std::ffi::c_long,
|
||||
)
|
||||
};
|
||||
|
||||
match rc {
|
||||
1 => Err(Error::InternalSsl("Could not write PEM", ErrorStack::get())),
|
||||
_ => Ok(bio.to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data in PEM format
|
||||
///
|
||||
/// Displays into a printable PEM structure.
|
||||
/// Must be constructed from another structure in this library.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let pem: Pem = ...;
|
||||
/// println!("PEM {pem}");
|
||||
/// ```
|
||||
/// ```PEM
|
||||
///-----BEGIN <name>-----
|
||||
///<header>
|
||||
///
|
||||
///<Base64 formatted binary data>
|
||||
///-----END <name>-----
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pem {
|
||||
pem: Confidential<String>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl Pem {
|
||||
/// Create a new PEM structure.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if name or header contain non-ASCII chars, or OpenSSL
|
||||
/// could not generate the PEM (very likely due to OOM).
|
||||
pub(crate) fn new<D, H>(name: &str, header: H, data: D) -> Result<Self>
|
||||
where
|
||||
D: AsRef<[u8]>,
|
||||
H: Into<Option<String>>,
|
||||
{
|
||||
let mut header = header.into();
|
||||
let header = match header {
|
||||
Some(h) if h.ends_with('\n') => Some(h),
|
||||
Some(h) if h.is_empty() => None,
|
||||
Some(mut h) => {
|
||||
h.push('\n');
|
||||
Some(h)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let inner_pem = InnerPem::new(name, header, data.as_ref())?;
|
||||
|
||||
// Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or OpenSSL Errors
|
||||
// Both error should be very unlikely
|
||||
// OpenSSL should be able to create PEM if there is enough memory and produce a non-null
|
||||
// terminated ASCII-string
|
||||
// Unwrap succeeds it's all ASCII
|
||||
// Std lib implements all the conversations without a copy
|
||||
let pem = CString::new(inner_pem.to_pem()?)
|
||||
.map_err(|_| Error::NonAscii)?
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
Ok(Self { pem })
|
||||
}
|
||||
|
||||
/// Converts the PEM-data into a byte vector.
|
||||
///
|
||||
/// This consumes the `PEM`.
|
||||
#[inline]
|
||||
#[must_use = "`self` will be dropped if the result is not used"]
|
||||
pub fn into_bytes(self) -> Confidential<Vec<u8>> {
|
||||
self.pem.into_inner().into_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Pem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.pem.value().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_data() {
|
||||
const EXP: &str =
|
||||
"-----BEGIN PEM test-----\ntest hdr value: 17\n\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new("PEM test", "test hdr value: 17".to_string(), []).unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_hdr() {
|
||||
const EXP: &str =
|
||||
"-----BEGIN PEM test-----\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new("PEM test", None, "very secret key").unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_data() {
|
||||
const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new(
|
||||
"PEM test",
|
||||
"test hdr value: 17".to_string(),
|
||||
"very secret key",
|
||||
)
|
||||
.unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_linebreak() {
|
||||
const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new(
|
||||
"PEM test",
|
||||
"test hdr value: 17\n".to_string(),
|
||||
"very secret key",
|
||||
)
|
||||
.unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user