mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f85c8ebba0 | ||
|
|
e4e455630b | ||
|
|
d82beef937 | ||
|
|
c3e61e5e0f | ||
|
|
f04e46baf0 | ||
|
|
e93bd7e61c | ||
|
|
edf40976f0 | ||
|
|
e557f41c6e | ||
|
|
68fe816da9 | ||
|
|
367ca6e16b | ||
|
|
6c67f05ca7 | ||
|
|
4887e80995 | ||
|
|
74a338edfc | ||
|
|
8878ba4fd2 | ||
|
|
e53f5ccfea | ||
|
|
6e7eb62ea1 | ||
|
|
2bb61f96c0 | ||
|
|
a902fd9afe | ||
|
|
dc42460d82 | ||
|
|
6689e25865 | ||
|
|
20f6791fb4 | ||
|
|
f0a148fd0b | ||
|
|
390e48869e | ||
|
|
3635a19462 | ||
|
|
584e785f5c | ||
|
|
7fffdcfe8c | ||
|
|
44d6020774 | ||
|
|
30ce11b8da | ||
|
|
c7ab70df2c | ||
|
|
c953486dec | ||
|
|
610cc581da | ||
|
|
604c34e66b | ||
|
|
e2cd2e78c5 | ||
|
|
d04d331673 | ||
|
|
27f20495a7 | ||
|
|
679ab77b96 | ||
|
|
8bb20be528 | ||
|
|
d04ea8603d | ||
|
|
51d2c92067 | ||
|
|
7e3b8b8c16 | ||
|
|
f8fb9ce32a | ||
|
|
3d5f75d6c3 | ||
|
|
ddc8248b21 |
@@ -1,13 +1,12 @@
|
||||
edition = "2021"
|
||||
newline_style = "Unix"
|
||||
|
||||
# Unstable options that help catching some mistakes in formatting and that we may want to enable
|
||||
# when they become stable.
|
||||
#
|
||||
# They are kept here since they are useful to run from time to time.
|
||||
#comment_width = 100
|
||||
#format_code_in_doc_comments = true
|
||||
#group_imports = "StdExternalCrate"
|
||||
#normalize_comments = true
|
||||
# Unstable options that help catching some mistakes in formatting
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
use_field_init_shorthand = true
|
||||
comment_width = 100
|
||||
format_code_in_doc_comments = true
|
||||
normalize_comments = true
|
||||
wrap_comments = true
|
||||
#reorder_impl_items = true
|
||||
#wrap_comments = true
|
||||
|
||||
@@ -77,7 +77,6 @@ List of all individuals having contributed content to s390-tools
|
||||
- Jean-Baptiste Joret
|
||||
- Jens Remus
|
||||
- Jochen Roehrig
|
||||
- Joern Siglen
|
||||
- Juergen Christ
|
||||
- Julian Wiedmann
|
||||
- Jörn Siglen
|
||||
@@ -135,6 +134,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Steffen Eiden
|
||||
- Steffen Maier
|
||||
- Steffen Thoss
|
||||
- Stephen Gallagher
|
||||
- Sumanth Korikkar
|
||||
- Susanne Wintenberger
|
||||
- Sven Schnelle
|
||||
@@ -148,6 +148,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Thomas Spatzier
|
||||
- Thomas Weber
|
||||
- Thorsten Winkler
|
||||
- Timo Keller
|
||||
- Tobias Huschle
|
||||
- Tuan Hoang
|
||||
- Ursula Braun
|
||||
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,6 +1,24 @@
|
||||
Release history for s390-tools (MIT version)
|
||||
--------------------------------------------
|
||||
|
||||
* __v2.43.0 (2026-06-25)__
|
||||
|
||||
For Linux kernel version: 7.1
|
||||
|
||||
s390-tools: Set Rust MSRV to 1.85.0
|
||||
|
||||
Changes of existing tools:
|
||||
- dbginfo.sh: Add IBM appliance specific files
|
||||
- lshwc: Show explicitly selected unnamed counters with --hide
|
||||
- pvattest: Add firmware check version 2
|
||||
- zipl: Introduce verbosity levels of zipl session (--debug)
|
||||
- zkey: Remove the use of AF_ALG for calculating key verification patterns
|
||||
|
||||
Bug Fixes:
|
||||
- ebc: implement --version option for pvics
|
||||
- pvebc: Log services to journal+console
|
||||
- pvics: Fix virt-resize permission error
|
||||
|
||||
* __v2.42.1 (2026-05-22)__
|
||||
|
||||
For Linux kernel version: 7.0
|
||||
|
||||
@@ -32,8 +32,8 @@ endif
|
||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||
VERSION := 2
|
||||
RELEASE := 42
|
||||
PATCHLEVEL := 1
|
||||
RELEASE := 43
|
||||
PATCHLEVEL := 0
|
||||
DISTRELEASE := build-$(shell date +%Y%m%d)
|
||||
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||
export S390_TOOLS_RELEASE
|
||||
|
||||
@@ -406,6 +406,38 @@ static void output_times(struct time_formats date)
|
||||
}
|
||||
}
|
||||
|
||||
struct ctr_filter_match {
|
||||
bool selected;
|
||||
bool selected_by_id;
|
||||
};
|
||||
|
||||
static struct ctr_filter_match ctr_match(const char *name, const char *list,
|
||||
size_t id)
|
||||
{
|
||||
struct ctr_filter_match match = { .selected = !list };
|
||||
char *copy, *token;
|
||||
char id_str[32];
|
||||
|
||||
if (!list)
|
||||
return match;
|
||||
|
||||
snprintf(id_str, sizeof(id_str), "%zu", id);
|
||||
copy = util_strdup(list);
|
||||
for (token = strtok(copy, ","); token; token = strtok(NULL, ",")) {
|
||||
if (name && strcmp(token, name) == 0) {
|
||||
match.selected = true;
|
||||
break;
|
||||
}
|
||||
if (strcmp(token, id_str) == 0) {
|
||||
match.selected = true;
|
||||
match.selected_by_id = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
return match;
|
||||
}
|
||||
|
||||
static void prepare_counter(size_t id, unsigned long value)
|
||||
{
|
||||
if (output_format == FMT_CSV) {
|
||||
@@ -438,9 +470,14 @@ static void output_per_cpu(struct time_formats date)
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
struct ctr_filter_match match;
|
||||
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
match = ctr_match(ctrname[i].name, ctrlist, i);
|
||||
if (!match.selected)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name && !match.selected_by_id)
|
||||
continue;
|
||||
prepare_counter(i, ctrname[i].ccv[h]);
|
||||
}
|
||||
@@ -462,9 +499,14 @@ static void output_total(struct time_formats date)
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
struct ctr_filter_match match;
|
||||
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
match = ctr_match(ctrname[i].name, ctrlist, i);
|
||||
if (!match.selected)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name && !match.selected_by_id)
|
||||
continue;
|
||||
prepare_counter(i, ctrname[i].total);
|
||||
ctrname[i].total = 0;
|
||||
|
||||
@@ -495,6 +495,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
|
||||
fprintf(stderr,
|
||||
"Error: Device name was truncated\n");
|
||||
free(path);
|
||||
free(readbuf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -504,6 +505,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
|
||||
|
||||
closedir(directory);
|
||||
free(path);
|
||||
free(readbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,9 @@
|
||||
_x > _y ? _x : _y; \
|
||||
})
|
||||
|
||||
#ifdef __SIZEOF_INT128__
|
||||
typedef unsigned __int128 u128 __aligned(16);
|
||||
#endif
|
||||
typedef unsigned long long u64;
|
||||
typedef signed long long s64;
|
||||
typedef unsigned int u32;
|
||||
|
||||
@@ -43,7 +43,7 @@ detect-openssl-version.dep:
|
||||
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
|
||||
echo "#endif" >> $(TMPFILE)
|
||||
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
|
||||
echo "}" >> $(TMPFILE)
|
||||
mv $(TMPFILE) $@
|
||||
|
||||
|
||||
@@ -229,8 +229,9 @@ int parse_json_web_token(const char *token, json_object **header_obj,
|
||||
size_t *signature_len)
|
||||
{
|
||||
json_object *hdr = NULL, *pld = NULL, *b64_obj = NULL;
|
||||
char *ch, *header, *payload, *json = NULL;
|
||||
size_t header_len, payload_len, json_len;
|
||||
const char *ch, *header, *payload;
|
||||
char *json = NULL;
|
||||
bool b64 = true;
|
||||
int rc = 0;
|
||||
|
||||
@@ -254,7 +255,7 @@ int parse_json_web_token(const char *token, json_object **header_obj,
|
||||
* and the following parts (if any) are used for JWS Signature, or JWE
|
||||
* Encryption (not considered here).
|
||||
*/
|
||||
header = (char *)token;
|
||||
header = token;
|
||||
|
||||
ch = strchr(token, '.');
|
||||
if (ch == NULL) {
|
||||
@@ -653,7 +654,7 @@ int verify_json_web_signature(const char *jws, const unsigned char *payload,
|
||||
BIGNUM *bn_s = NULL;
|
||||
const char *alg;
|
||||
size_t sig_len;
|
||||
char *ch;
|
||||
const char *ch;
|
||||
|
||||
if (jws == NULL || pkey == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -46,7 +46,7 @@ detect-openssl-version.dep:
|
||||
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
|
||||
echo "#endif" >> $(TMPFILE)
|
||||
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
|
||||
echo "}" >> $(TMPFILE)
|
||||
mv $(TMPFILE) $@
|
||||
|
||||
|
||||
@@ -1370,7 +1370,8 @@ int kmip_connection_get_server_cert(const char *server,
|
||||
struct kmip_conn_config config = { 0 };
|
||||
struct kmip_connection *conn = NULL;
|
||||
int rc, numcerts, i, port_found = 0;
|
||||
char *hostname = NULL, *tok, *tok2;
|
||||
const char *tok, *tok2;
|
||||
char *hostname = NULL;
|
||||
STACK_OF(X509) *chain;
|
||||
bool do_verify = true;
|
||||
FILE *fp = NULL;
|
||||
|
||||
@@ -28,7 +28,7 @@ detect-openssl-version.dep:
|
||||
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
|
||||
echo "#endif" >> $(TMPFILE)
|
||||
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
|
||||
echo "}" >> $(TMPFILE)
|
||||
mv $(TMPFILE) $@
|
||||
|
||||
|
||||
577
rust/Cargo.lock
generated
577
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -17,12 +17,11 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.75.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[workspace.lints.rust]
|
||||
missing_docs = { level = "deny", priority = 1 }
|
||||
missing_debug_implementations = "warn"
|
||||
non_ascii_idents = "warn"
|
||||
nonstandard-style = "warn"
|
||||
trivial_numeric_casts = "warn"
|
||||
unstable_features = "warn"
|
||||
|
||||
@@ -6,15 +6,15 @@ license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.95"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
anyhow = "1.0.102"
|
||||
clap = { version = "4.6", features = ["derive"] }
|
||||
libc = "0.2"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
utils = { path = "../utils" }
|
||||
zerocopy = { version="0.8", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.2"
|
||||
clap = { version ="4.5", features = ["derive"]}
|
||||
clap = { version ="4.6", features = ["derive"]}
|
||||
lazy_static = "1.5"
|
||||
|
||||
@@ -7,11 +7,11 @@ mod msa;
|
||||
mod query;
|
||||
mod stfle;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Result as anyhowRes;
|
||||
use clap::Parser;
|
||||
use std::io::ErrorKind;
|
||||
use std::result::Result::Ok;
|
||||
|
||||
use anyhow::{bail, Result as anyhowRes};
|
||||
use clap::Parser;
|
||||
use utils::print_version;
|
||||
|
||||
use crate::cli::{Cli, Format};
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::cli::Cli;
|
||||
use core::fmt::{Display, Formatter, Result};
|
||||
|
||||
use serde::{Serialize, Serializer};
|
||||
use utils::HexSlice;
|
||||
|
||||
use crate::cli::Cli;
|
||||
|
||||
/// Number of total function codes (0 to 127)
|
||||
pub const NUMBER_FUNC_CODES: usize = 128;
|
||||
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Error;
|
||||
use std::io::Read;
|
||||
use std::io::{Error, Read};
|
||||
use std::ops::Index;
|
||||
use std::result::Result;
|
||||
|
||||
use zerocopy::FromBytes;
|
||||
|
||||
use crate::msa::InstructionKind;
|
||||
use crate::msa::QueryAuthInfo;
|
||||
use crate::msa::{InstructionKind, QueryAuthInfo};
|
||||
|
||||
/// Path to sysfs in which the query and qai informations are fetched from
|
||||
const SYSFS_PATH: &str = "/sys/devices/system/cpu/cpacf/";
|
||||
|
||||
@@ -15,14 +15,14 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.5"
|
||||
curl = "0.4.47"
|
||||
curl = "0.4.49"
|
||||
enum_dispatch = "0.3.13"
|
||||
foreign-types = "0.3.2"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.70"
|
||||
openssl-sys = "0.9.105"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
thiserror = "2.0.11"
|
||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.80"
|
||||
openssl-sys = "0.9.116"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
thiserror = "2.0.18"
|
||||
zerocopy = { version="0.8", features = ["derive"] }
|
||||
|
||||
pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.12.0" }
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use pv_core::misc::encode_hex;
|
||||
use s390_pv as pv;
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use pv::misc::{read_certs, read_file};
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use pv::{Error, Result};
|
||||
use pv_core::misc::encode_hex;
|
||||
use s390_pv as pv;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let hkd = args().nth(1).expect("Expect one Host-key document");
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023, 2024
|
||||
|
||||
use std::{
|
||||
io::{Read, Seek, SeekFrom::Current},
|
||||
mem::size_of,
|
||||
};
|
||||
use std::io::SeekFrom::Current;
|
||||
use std::io::{Read, Seek};
|
||||
use std::mem::size_of;
|
||||
|
||||
use log::{debug, warn};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
|
||||
@@ -13,6 +12,13 @@ use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64
|
||||
// (SE) boot request control block aka SE header
|
||||
use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGESIZE};
|
||||
|
||||
/// Version of the Secure Execution header
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SeHdrVersion {
|
||||
/// Secure Execution header v1
|
||||
One = 0x100,
|
||||
}
|
||||
|
||||
/// Struct containing all SE-header tags.
|
||||
///
|
||||
/// Contains:
|
||||
@@ -206,7 +212,7 @@ impl BootHdrTags {
|
||||
///
|
||||
/// This function will return an error if the header could not be found in
|
||||
/// `img` or is invalid.
|
||||
pub fn from_se_image<R>(img: &mut R) -> Result<Self>
|
||||
pub fn from_se_image<R>(img: &mut R) -> Result<(Self, SeHdrVersion)>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
@@ -233,10 +239,13 @@ impl BootHdrTags {
|
||||
};
|
||||
|
||||
// Some sanity checks
|
||||
if hdr_head.version.get() != 0x100 {
|
||||
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
|
||||
return Err(Error::InvBootHdr);
|
||||
}
|
||||
let hdr_version = match hdr_head.version.get() {
|
||||
0x100 => SeHdrVersion::One,
|
||||
_ => {
|
||||
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
|
||||
return Err(Error::InvBootHdr);
|
||||
}
|
||||
};
|
||||
|
||||
// go to the Boot header tag
|
||||
img.seek(Current(
|
||||
@@ -249,12 +258,15 @@ impl BootHdrTags {
|
||||
let mut tag = [0u8; BootHdrHead::TAG_SIZE];
|
||||
img.read_exact(tag.as_mut_slice())?;
|
||||
|
||||
Ok(Self {
|
||||
pld: hdr_head.pld,
|
||||
ald: hdr_head.ald,
|
||||
tld: hdr_head.tld,
|
||||
tag,
|
||||
})
|
||||
Ok((
|
||||
Self {
|
||||
pld: hdr_head.pld,
|
||||
ald: hdr_head.ald,
|
||||
tld: hdr_head.tld,
|
||||
tag,
|
||||
},
|
||||
hdr_version,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +332,7 @@ mod tests {
|
||||
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)).unwrap();
|
||||
assert_eq!(hdr_tags, EXP_HDR);
|
||||
assert_eq!(hdr_tags, (EXP_HDR, SeHdrVersion::One));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -356,18 +368,19 @@ mod tests {
|
||||
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);
|
||||
assert_eq!(hdr_tags, (EXP_HDR, SeHdrVersion::One));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tags_convert_u8() {
|
||||
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
|
||||
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap();
|
||||
let ser: &[u8] = hdr_tags.as_ref();
|
||||
let ser: &[u8] = hdr_tags.0.as_ref();
|
||||
let mut ser = ser.to_vec();
|
||||
|
||||
let der: BootHdrTags = ser.clone().try_into().unwrap();
|
||||
assert_eq!(hdr_tags, der);
|
||||
assert_eq!(hdr_tags.0, der);
|
||||
assert_eq!(hdr_tags.1, SeHdrVersion::One);
|
||||
|
||||
ser.pop();
|
||||
let der: Result<BootHdrTags> = ser.clone().try_into();
|
||||
@@ -380,7 +393,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn se_img_metadata() {
|
||||
fn se_img_metadata_v1() {
|
||||
let metadata = SeImgMetaData::new_v1(0x14000, 0x16000);
|
||||
let data = [
|
||||
83, 101, 73, 109, 103, 76, 110, 120, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 0, 0, 0, 0,
|
||||
|
||||
@@ -2,25 +2,28 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023, 2024
|
||||
|
||||
use std::{convert::TryInto, fmt::Display, ops::Range};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Display;
|
||||
use std::ops::Range;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use openssl::{
|
||||
derive::Deriver,
|
||||
ec::{EcGroup, EcKey},
|
||||
hash::{DigestBytes, MessageDigest},
|
||||
md::MdRef,
|
||||
nid::Nid,
|
||||
pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public},
|
||||
pkey_ctx::{HkdfMode, PkeyCtx},
|
||||
rand::rand_bytes,
|
||||
rsa::Padding,
|
||||
sign::{Signer, Verifier},
|
||||
symm::{decrypt_aead as openssl_decrypt_aead, encrypt_aead as openssl_encrypt_aead, Cipher},
|
||||
use openssl::derive::Deriver;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use openssl::hash::{DigestBytes, MessageDigest};
|
||||
use openssl::md::MdRef;
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public};
|
||||
use openssl::pkey_ctx::{HkdfMode, PkeyCtx};
|
||||
use openssl::rand::rand_bytes;
|
||||
use openssl::rsa::Padding;
|
||||
use openssl::sign::{Signer, Verifier};
|
||||
use openssl::symm::{
|
||||
decrypt_aead as openssl_decrypt_aead, encrypt_aead as openssl_encrypt_aead, Cipher,
|
||||
};
|
||||
use pv_core::request::Confidential;
|
||||
|
||||
use crate::{error::Result, Error};
|
||||
use crate::error::Result;
|
||||
use crate::Error;
|
||||
|
||||
/// An AES256-GCM key that will purge itself out of the memory when going out of scope
|
||||
pub type Aes256GcmKey = Confidential<[u8; SymKeyType::AES_256_GCM_KEY_LEN]>;
|
||||
@@ -159,6 +162,12 @@ impl SymKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SymKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SymKey({:?})", self.key_type())
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an hkdf according to RFC 5869.
|
||||
/// See [`OpenSSL HKDF`]()
|
||||
///
|
||||
@@ -540,7 +549,8 @@ pub(crate) fn verify_signature<T: HasPublic>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{get_test_asset, test_utils::*, PvCoreError};
|
||||
use crate::test_utils::*;
|
||||
use crate::{get_test_asset, PvCoreError};
|
||||
|
||||
#[test]
|
||||
fn sign_ec() {
|
||||
|
||||
@@ -63,13 +63,11 @@ pub mod uv {
|
||||
pub mod attest {
|
||||
pub use pv_core::attest::*;
|
||||
|
||||
pub use crate::uvattest::{
|
||||
additional::AdditionalData,
|
||||
arcb::{
|
||||
AttestationAuthenticated, AttestationFlags, AttestationRequest, AttestationVersion,
|
||||
},
|
||||
attest::{AttestationItems, AttestationMeasurement},
|
||||
pub use crate::uvattest::additional::AdditionalData;
|
||||
pub use crate::uvattest::arcb::{
|
||||
AttestationAuthenticated, AttestationFlags, AttestationRequest, AttestationVersion,
|
||||
};
|
||||
pub use crate::uvattest::attest::{AttestationItems, AttestationMeasurement};
|
||||
}
|
||||
|
||||
/// Definitions and functions to write objects in PEM format
|
||||
@@ -91,27 +89,22 @@ pub use crate::error::HkdVerifyErrorType;
|
||||
|
||||
/// Functionalities to build UV requests
|
||||
pub mod request {
|
||||
pub use crate::{
|
||||
brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData},
|
||||
crypto::{
|
||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
|
||||
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey,
|
||||
SymKeyType, SHA_512_HASH_LEN,
|
||||
},
|
||||
req::{EcPubKeyCoord, Encrypt, Keyslot, ReqEncrCtx, Request},
|
||||
verify::{CertVerifier, HkdVerifier, NoVerifyHkd},
|
||||
pub use crate::brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData};
|
||||
pub use crate::crypto::{
|
||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
|
||||
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, SymKeyType,
|
||||
SHA_512_HASH_LEN,
|
||||
};
|
||||
pub use crate::req::{EcPubKeyCoord, Encrypt, HostKey, Keyslot, ReqEncrCtx, Request};
|
||||
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
|
||||
|
||||
/// Reexports some useful OpenSSL symbols
|
||||
pub mod openssl {
|
||||
pub use openssl::{
|
||||
error::ErrorStack,
|
||||
hash::DigestBytes,
|
||||
nid::Nid,
|
||||
pkey,
|
||||
sha::{Sha256, Sha512},
|
||||
x509,
|
||||
};
|
||||
pub use openssl::error::ErrorStack;
|
||||
pub use openssl::hash::DigestBytes;
|
||||
pub use openssl::nid::Nid;
|
||||
pub use openssl::sha::{Sha256, Sha512};
|
||||
pub use openssl::{pkey, x509};
|
||||
// rust-OpenSSL does not define these NIDs
|
||||
#[allow(missing_docs)]
|
||||
pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519);
|
||||
@@ -120,7 +113,6 @@ pub mod request {
|
||||
}
|
||||
|
||||
pub use pv_core::request::*;
|
||||
|
||||
pub use pv_core::PolicyReference;
|
||||
}
|
||||
|
||||
@@ -128,11 +120,9 @@ pub mod request {
|
||||
pub mod secret {
|
||||
pub use pv_core::secret::*;
|
||||
|
||||
pub use crate::uvsecret::{
|
||||
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
|
||||
ext_secret::ExtSecret,
|
||||
guest_secret::GuestSecret,
|
||||
retr_secret::{IbmProtectedKey, RetrievedSecret},
|
||||
user_data::verify_asrcb_and_get_user_data,
|
||||
};
|
||||
pub use crate::uvsecret::asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion};
|
||||
pub use crate::uvsecret::ext_secret::ExtSecret;
|
||||
pub use crate::uvsecret::guest_secret::GuestSecret;
|
||||
pub use crate::uvsecret::retr_secret::{IbmProtectedKey, RetrievedSecret};
|
||||
pub use crate::uvsecret::user_data::verify_asrcb_and_get_user_data;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::ffi::c_int;
|
||||
use std::fmt;
|
||||
|
||||
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
|
||||
use openssl::x509::{X509CrlRef, X509Ref};
|
||||
use std::ffi::c_int;
|
||||
|
||||
mod ffi {
|
||||
extern "C" {
|
||||
@@ -99,9 +99,8 @@ impl AkidExtension for X509CrlRef {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::test_utils::load_gen_cert;
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::load_gen_cert;
|
||||
|
||||
#[test]
|
||||
fn akid() {
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use core::slice;
|
||||
use std::ffi::c_int;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr;
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
pub use crate::openssl_extensions::stackable_crl::*;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::{Stack, StackRef},
|
||||
x509::{
|
||||
store::{X509StoreBuilderRef, X509StoreRef},
|
||||
X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509,
|
||||
},
|
||||
};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::stack::{Stack, StackRef};
|
||||
use openssl::x509::store::{X509StoreBuilderRef, X509StoreRef};
|
||||
use openssl::x509::{X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509};
|
||||
|
||||
pub use crate::openssl_extensions::stackable_crl::*;
|
||||
|
||||
pub fn opt_to_ptr<T: ForeignTypeRef>(o: Option<&T>) -> *mut T::CType {
|
||||
match o {
|
||||
|
||||
595
rust/pv/src/openssl_extensions/ml_kem.rs
Normal file
595
rust/pv/src/openssl_extensions/ml_kem.rs
Normal file
@@ -0,0 +1,595 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
#![expect(unused)]
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use foreign_types::ForeignType;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::pkey::{KeyType, PKey, Private};
|
||||
use openssl::pkey_ctx::PkeyCtx;
|
||||
|
||||
// automatically generated by rust-bindgen 0.69.5
|
||||
|
||||
mod ffi {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct evp_pkey_ctx_st {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
pub type EvpPkeyCtx = evp_pkey_ctx_st;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ossl_param_st {
|
||||
pub key: *const ::std::os::raw::c_char,
|
||||
pub data_type: ::std::os::raw::c_uint,
|
||||
pub data: *mut ::std::os::raw::c_void,
|
||||
pub data_size: usize,
|
||||
pub return_size: usize,
|
||||
}
|
||||
pub type OsslParam = ossl_param_st;
|
||||
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_CTX_new_from_name(
|
||||
libctx: *mut ::std::os::raw::c_void,
|
||||
name: *const ::std::os::raw::c_char,
|
||||
propquery: *const ::std::os::raw::c_char,
|
||||
) -> *mut EvpPkeyCtx;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_keygen_init(ctx: *mut EvpPkeyCtx) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_generate(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
ppkey: *mut *mut ::std::os::raw::c_void,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_CTX_free(ctx: *mut EvpPkeyCtx);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_encapsulate_init(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
params: *const OsslParam,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_encapsulate(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
wrappedkey: *mut ::std::os::raw::c_uchar,
|
||||
wrappedkeylen: *mut usize,
|
||||
genkey: *mut ::std::os::raw::c_uchar,
|
||||
genkeylen: *mut usize,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_decapsulate_init(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
params: *const OsslParam,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_decapsulate(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
unwrapped: *mut ::std::os::raw::c_uchar,
|
||||
unwrappedlen: *mut usize,
|
||||
wrapped: *const ::std::os::raw::c_uchar,
|
||||
wrappedlen: usize,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
const ML_KEM_512_NAME: &CStr = c"ML-KEM-512";
|
||||
const ML_KEM_768_NAME: &CStr = c"ML-KEM-768";
|
||||
const ML_KEM_1024_NAME: &CStr = c"ML-KEM-1024";
|
||||
|
||||
fn ml_kem_name(key_type: KeyType) -> Result<&'static CStr, ErrorStack> {
|
||||
if key_type == KeyType::ML_KEM_512 {
|
||||
Ok(ML_KEM_512_NAME)
|
||||
} else if key_type == KeyType::ML_KEM_768 {
|
||||
Ok(ML_KEM_768_NAME)
|
||||
} else if key_type == KeyType::ML_KEM_1024 {
|
||||
Ok(ML_KEM_1024_NAME)
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulate a key
|
||||
pub trait PkeyEncapsulateContext {
|
||||
/// Initialize the encapsulation operation.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn encapsulate_init(&mut self) -> Result<(), ErrorStack>;
|
||||
|
||||
/// Perform the encapsulation operation.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrappedkey`: Optional buffer to receive the wrapped key.
|
||||
/// - `genkey`: Optional buffer to receive the generated key.
|
||||
///
|
||||
/// # Returns
|
||||
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn encapsulate(
|
||||
&mut self,
|
||||
wrappedkey: Option<&mut [u8]>,
|
||||
genkey: Option<&mut [u8]>,
|
||||
) -> Result<(usize, usize), ErrorStack>;
|
||||
|
||||
/// Convenience method to encapsulate into vectors.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrappedkey`: Buffer to receive the wrapped key (ciphertext).
|
||||
/// - `genkey`: Buffer to receive the generated key (shared secret).
|
||||
///
|
||||
/// # Returns
|
||||
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn encapsulate_to_vec(
|
||||
&mut self,
|
||||
wrappedkey: &mut Vec<u8>,
|
||||
genkey: &mut Vec<u8>,
|
||||
) -> Result<(usize, usize), ErrorStack> {
|
||||
let wrappedkey_base = wrappedkey.len();
|
||||
let genkey_base = genkey.len();
|
||||
|
||||
// Query the required output buffer sizes.
|
||||
let (wrappedkey_len, genkey_len) = self.encapsulate(None, None)?;
|
||||
|
||||
wrappedkey.resize(wrappedkey_base + wrappedkey_len, 0);
|
||||
genkey.resize(genkey_base + genkey_len, 0);
|
||||
|
||||
let (wrappedkey_len, genkey_len) = self.encapsulate(
|
||||
Some(&mut wrappedkey[wrappedkey_base..]),
|
||||
Some(&mut genkey[genkey_base..]),
|
||||
)?;
|
||||
|
||||
wrappedkey.truncate(wrappedkey_base + wrappedkey_len);
|
||||
genkey.truncate(genkey_base + genkey_len);
|
||||
|
||||
Ok((wrappedkey_len, genkey_len))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PkeyEncapsulateContext for PkeyCtx<T> {
|
||||
#[inline]
|
||||
fn encapsulate_init(&mut self) -> Result<(), ErrorStack> {
|
||||
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
|
||||
// configuration is intentionally unsupported here, so a null params pointer is passed.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_encapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn encapsulate(
|
||||
&mut self,
|
||||
wrappedkey: Option<&mut [u8]>,
|
||||
genkey: Option<&mut [u8]>,
|
||||
) -> Result<(usize, usize), ErrorStack> {
|
||||
let mut wrappedkey_len = wrappedkey.as_ref().map_or(0, |buf| buf.len());
|
||||
let mut genkey_len = genkey.as_ref().map_or(0, |buf| buf.len());
|
||||
|
||||
let wrappedkey_ptr = wrappedkey
|
||||
.map(|buf| buf.as_mut_ptr())
|
||||
.unwrap_or(std::ptr::null_mut());
|
||||
let genkey_ptr = genkey
|
||||
.map(|buf| buf.as_mut_ptr())
|
||||
.unwrap_or(std::ptr::null_mut());
|
||||
|
||||
// SAFETY: All pointers are either valid mutable buffers or null.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_encapsulate(
|
||||
self.as_ptr() as *mut ffi::EvpPkeyCtx,
|
||||
wrappedkey_ptr,
|
||||
&mut wrappedkey_len,
|
||||
genkey_ptr,
|
||||
&mut genkey_len,
|
||||
)
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok((wrappedkey_len, genkey_len))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decapsulate a key
|
||||
pub trait PkeyDecapsulateContext {
|
||||
/// Initialize the decapsulation operation.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn decapsulate_init(&mut self) -> Result<(), ErrorStack>;
|
||||
|
||||
/// Perform the decapsulation operation.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
|
||||
/// - `unwrapped`: Optional buffer to receive the unwrapped shared secret.
|
||||
///
|
||||
/// # Returns
|
||||
/// The length of the unwrapped shared secret on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn decapsulate(
|
||||
&mut self,
|
||||
wrapped: &[u8],
|
||||
unwrapped: Option<&mut [u8]>,
|
||||
) -> Result<usize, ErrorStack>;
|
||||
|
||||
/// Convenience method to decapsulate into a vector.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
|
||||
/// - `unwrapped`: Buffer to receive the unwrapped shared secret.
|
||||
///
|
||||
/// # Returns
|
||||
/// The length of the unwrapped shared secret on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn decapsulate_to_vec(
|
||||
&mut self,
|
||||
wrapped: &[u8],
|
||||
unwrapped: &mut Vec<u8>,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
let unwrapped_base = unwrapped.len();
|
||||
|
||||
// Query the required output buffer size.
|
||||
let unwrapped_len = self.decapsulate(wrapped, None)?;
|
||||
|
||||
unwrapped.resize(unwrapped_base + unwrapped_len, 0);
|
||||
|
||||
let unwrapped_len = self.decapsulate(wrapped, Some(&mut unwrapped[unwrapped_base..]))?;
|
||||
|
||||
unwrapped.truncate(unwrapped_base + unwrapped_len);
|
||||
|
||||
Ok(unwrapped_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PkeyDecapsulateContext for PkeyCtx<T> {
|
||||
#[inline]
|
||||
fn decapsulate_init(&mut self) -> Result<(), ErrorStack> {
|
||||
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
|
||||
// configuration is intentionally unsupported here, so a null params pointer is passed.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_decapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn decapsulate(
|
||||
&mut self,
|
||||
wrapped: &[u8],
|
||||
unwrapped: Option<&mut [u8]>,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
let mut unwrapped_len = unwrapped.as_ref().map_or(0, |buf| buf.len());
|
||||
|
||||
let unwrapped_ptr = unwrapped
|
||||
.map(|buf| buf.as_mut_ptr())
|
||||
.unwrap_or(std::ptr::null_mut());
|
||||
|
||||
// SAFETY: All pointers are either valid mutable buffers or null, and wrapped is a valid
|
||||
// slice.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_decapsulate(
|
||||
self.as_ptr() as *mut ffi::EvpPkeyCtx,
|
||||
unwrapped_ptr,
|
||||
&mut unwrapped_len,
|
||||
wrapped.as_ptr(),
|
||||
wrapped.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok(unwrapped_len)
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ML-KEM key generation context.
|
||||
///
|
||||
/// The inner pointer is guaranteed to be non-null for the lifetime of the context.
|
||||
pub struct MlKemKeyCtx {
|
||||
ctx: NonNull<ffi::EvpPkeyCtx>,
|
||||
}
|
||||
|
||||
impl MlKemKeyCtx {
|
||||
/// Create a new ML-KEM key generation context.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
|
||||
/// `KeyType::ML_KEM_1024`).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the context cannot be created or the key type is unsupported.
|
||||
pub fn new(key_type: KeyType) -> Result<Self, ErrorStack> {
|
||||
let name = ml_kem_name(key_type)?;
|
||||
|
||||
// SAFETY: name is a valid null-terminated C string.
|
||||
let ctx = unsafe {
|
||||
ffi::EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
|
||||
};
|
||||
let Some(ctx) = NonNull::new(ctx) else {
|
||||
return Err(ErrorStack::get());
|
||||
};
|
||||
|
||||
// SAFETY: ctx is a valid non-null pointer.
|
||||
let ret = unsafe { ffi::EVP_PKEY_keygen_init(ctx.as_ptr()) };
|
||||
if ret != 1 {
|
||||
// SAFETY: ctx is a valid non-null pointer.
|
||||
unsafe { ffi::EVP_PKEY_CTX_free(ctx.as_ptr()) };
|
||||
return Err(ErrorStack::get());
|
||||
}
|
||||
|
||||
Ok(Self { ctx })
|
||||
}
|
||||
|
||||
/// Generate an ML-KEM keypair.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `PKey` containing both the private and public key.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if key generation fails.
|
||||
pub fn generate(&mut self) -> Result<PKey<Private>, ErrorStack> {
|
||||
let mut pkey: *mut std::os::raw::c_void = std::ptr::null_mut();
|
||||
|
||||
// SAFETY: self.ctx is valid, and pkey is a valid mutable pointer.
|
||||
let ret = unsafe { ffi::EVP_PKEY_generate(self.ctx.as_ptr(), &mut pkey) };
|
||||
|
||||
if ret != 1 || pkey.is_null() {
|
||||
return Err(ErrorStack::get());
|
||||
}
|
||||
|
||||
// SAFETY: pkey is a valid EVP_PKEY pointer created by OpenSSL.
|
||||
unsafe { Ok(PKey::from_ptr(pkey as *mut openssl_sys::EVP_PKEY)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MlKemKeyCtx {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: self.ctx is guaranteed to be non-null and owned by this context.
|
||||
unsafe {
|
||||
ffi::EVP_PKEY_CTX_free(self.ctx.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate an ML-KEM keypair.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
|
||||
/// `KeyType::ML_KEM_1024`).
|
||||
///
|
||||
/// # Returns
|
||||
/// A `PKey` containing both the private and public key.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if key generation fails.
|
||||
pub fn generate_ml_kem(key_type: KeyType) -> Result<PKey<Private>, ErrorStack> {
|
||||
let mut ctx = MlKemKeyCtx::new(key_type)?;
|
||||
ctx.generate()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn roundtrip_ml_kem(key_type: KeyType, expected_ciphertext_len: usize) {
|
||||
let keypair = generate_ml_kem(key_type).expect("Failed to generate ML-KEM keypair");
|
||||
|
||||
// Extract public key for encapsulation
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Failed to export public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
||||
|
||||
// Encapsulate: generate ciphertext and shared secret using public key
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
ctx_enc
|
||||
.encapsulate_init()
|
||||
.expect("Failed to initialize encapsulation");
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
let mut shared_secret_enc = Vec::new();
|
||||
ctx_enc
|
||||
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret_enc)
|
||||
.expect("Failed to encapsulate");
|
||||
|
||||
assert!(!ciphertext.is_empty(), "Ciphertext should not be empty");
|
||||
assert!(
|
||||
!shared_secret_enc.is_empty(),
|
||||
"Shared secret should not be empty"
|
||||
);
|
||||
|
||||
// Decapsulate: recover shared secret from ciphertext using private key
|
||||
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
||||
ctx_dec
|
||||
.decapsulate_init()
|
||||
.expect("Failed to initialize decapsulation");
|
||||
|
||||
let mut shared_secret_dec = Vec::new();
|
||||
ctx_dec
|
||||
.decapsulate_to_vec(&ciphertext, &mut shared_secret_dec)
|
||||
.expect("Failed to decapsulate");
|
||||
|
||||
// Verify that the shared secrets match
|
||||
assert_eq!(
|
||||
shared_secret_enc, shared_secret_dec,
|
||||
"Shared secrets from encapsulation and decapsulation should match"
|
||||
);
|
||||
assert_eq!(ciphertext.len(), expected_ciphertext_len);
|
||||
assert_eq!(shared_secret_enc.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_decapsulate_roundtrip_ml_kem_512() {
|
||||
roundtrip_ml_kem(KeyType::ML_KEM_512, 768);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_decapsulate_roundtrip_ml_kem_768() {
|
||||
roundtrip_ml_kem(KeyType::ML_KEM_768, 1088);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_decapsulate_roundtrip_ml_kem_1024() {
|
||||
roundtrip_ml_kem(KeyType::ML_KEM_1024, 1568);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ml_kem_key_ctx_new_accepts_supported_key_types() {
|
||||
MlKemKeyCtx::new(KeyType::ML_KEM_512).expect("ML-KEM-512 context creation must succeed");
|
||||
MlKemKeyCtx::new(KeyType::ML_KEM_768).expect("ML-KEM-768 context creation must succeed");
|
||||
MlKemKeyCtx::new(KeyType::ML_KEM_1024).expect("ML-KEM-1024 context creation must succeed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ml_kem_key_ctx_new_rejects_unsupported_key_type() {
|
||||
assert!(
|
||||
MlKemKeyCtx::new(KeyType::RSA).is_err(),
|
||||
"Unsupported key type must fail"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ml_kem_key_ctx_generate_produces_ml_kem_keypair() {
|
||||
let mut ctx = MlKemKeyCtx::new(KeyType::ML_KEM_768)
|
||||
.expect("ML-KEM-768 context creation must succeed");
|
||||
let keypair = ctx
|
||||
.generate()
|
||||
.expect("ML-KEM-768 key generation must succeed");
|
||||
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Generated keypair must export a public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Generated public key must reimport");
|
||||
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
ctx_enc
|
||||
.encapsulate_init()
|
||||
.expect("Failed to initialize encapsulation");
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
let mut shared_secret = Vec::new();
|
||||
ctx_enc
|
||||
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
|
||||
.expect("Generated keypair must support encapsulation");
|
||||
|
||||
assert_eq!(ciphertext.len(), 1088);
|
||||
assert_eq!(shared_secret.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_ml_kem_rejects_unsupported_key_type() {
|
||||
generate_ml_kem(KeyType::RSA).expect_err("Unsupported key type must fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_requires_init() {
|
||||
let keypair =
|
||||
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Failed to export public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
||||
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
let err = ctx_enc
|
||||
.encapsulate_to_vec(&mut Vec::new(), &mut Vec::new())
|
||||
.expect_err("Encapsulation without init must fail");
|
||||
|
||||
assert!(
|
||||
!err.errors().is_empty(),
|
||||
"OpenSSL should report an error when encapsulate_init was not called"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decapsulate_requires_init() {
|
||||
let keypair =
|
||||
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
|
||||
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
||||
let err = ctx_dec
|
||||
.decapsulate_to_vec(&[0_u8; 768], &mut Vec::new())
|
||||
.expect_err("Decapsulation without init must fail");
|
||||
|
||||
assert!(
|
||||
!err.errors().is_empty(),
|
||||
"OpenSSL should report an error when decapsulate_init was not called"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decapsulate_rejects_undersized_output_buffer() {
|
||||
let keypair =
|
||||
generate_ml_kem(KeyType::ML_KEM_768).expect("Failed to generate ML-KEM-768 keypair");
|
||||
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Failed to export public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
||||
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
ctx_enc
|
||||
.encapsulate_init()
|
||||
.expect("Failed to initialize encapsulation");
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
let mut shared_secret = Vec::new();
|
||||
ctx_enc
|
||||
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
|
||||
.expect("Failed to encapsulate");
|
||||
|
||||
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
||||
ctx_dec
|
||||
.decapsulate_init()
|
||||
.expect("Failed to initialize decapsulation");
|
||||
|
||||
let mut undersized = [0_u8; 31];
|
||||
let err = ctx_dec
|
||||
.decapsulate(&ciphertext, Some(&mut undersized))
|
||||
.expect_err("Undersized output buffer must fail");
|
||||
|
||||
assert!(
|
||||
!err.errors().is_empty(),
|
||||
"OpenSSL should report an error for an undersized output buffer"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,11 @@
|
||||
mod akid;
|
||||
mod bio;
|
||||
mod crl;
|
||||
mod ml_kem;
|
||||
mod stackable_crl;
|
||||
|
||||
pub use akid::*;
|
||||
pub use bio::*;
|
||||
pub use crl::*;
|
||||
#[expect(unused_imports)]
|
||||
pub use ml_kem::*;
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::openssl_extensions::bio::BioMemSlice;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::Stackable,
|
||||
x509::{X509Crl, X509CrlRef},
|
||||
};
|
||||
use std::ptr;
|
||||
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::stack::Stackable;
|
||||
use openssl::x509::{X509Crl, X509CrlRef};
|
||||
|
||||
use crate::openssl_extensions::bio::BioMemSlice;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
|
||||
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::Result;
|
||||
use crate::{openssl_extensions::BioMem, Error};
|
||||
use std::ffi::{c_char, CString};
|
||||
use std::fmt::Display;
|
||||
|
||||
use openssl::error::ErrorStack;
|
||||
use pv_core::request::Confidential;
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
use crate::openssl_extensions::BioMem;
|
||||
use crate::{Error, Result};
|
||||
|
||||
mod ffi {
|
||||
use openssl_sys::BIO;
|
||||
use std::ffi::{c_char, c_int, c_long, c_uchar};
|
||||
|
||||
use openssl_sys::BIO;
|
||||
extern "C" {
|
||||
pub fn PEM_write_bio(
|
||||
bio: *mut BIO,
|
||||
@@ -141,8 +142,8 @@ impl Pem {
|
||||
|
||||
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
|
||||
// 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
|
||||
|
||||
@@ -1,692 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
use std::mem::size_of;
|
||||
|
||||
use openssl::{
|
||||
bn::{BigNum, BigNumContext},
|
||||
ec::{EcGroup, EcGroupRef, EcKey, EcPointRef},
|
||||
error::ErrorStack,
|
||||
hash::{DigestBytes, MessageDigest},
|
||||
nid::Nid,
|
||||
pkey::{PKey, PKeyRef, Private, Public},
|
||||
};
|
||||
use pv_core::request::{RequestMagic, RequestVersion};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
crypto::{
|
||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, hash, random_array,
|
||||
AeadEncryptionResult, SymKey, SymKeyType,
|
||||
},
|
||||
misc::to_u32,
|
||||
request::Confidential,
|
||||
Error, Result,
|
||||
};
|
||||
/// 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: &PKeyRef<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: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Types of Authenticated Data
|
||||
#[allow(missing_debug_implementations)]
|
||||
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: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let derived_key = derive_aes256_gcm_key(priv_key, &self.0)?;
|
||||
let mut wrpk_and_kst =
|
||||
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
|
||||
let phk: EcPubKeyCoord = 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 manage 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(Nid::SECP521R1)?);
|
||||
let prot_key = prot_key
|
||||
.into()
|
||||
.unwrap_or(SymKey::random(SymKeyType::Aes256Gcm)?);
|
||||
Ok(Self {
|
||||
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::Aes256Gcm => Self::new_aes_256(None, None, None),
|
||||
SymKeyType::Aes256Xts => Err(Error::NoAeadKey),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(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(|| {
|
||||
pv_core::Error::Specification("Configured request size to large".to_string())
|
||||
})?;
|
||||
let sea = to_u32(encr_size)
|
||||
.ok_or_else(|| pv_core::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[..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<EcPubKeyCoord> {
|
||||
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
|
||||
}
|
||||
|
||||
/// 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(crate) fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AeadEncryptionResult> {
|
||||
encrypt_aead(&self.prot_key, &self.iv, aad, conf)
|
||||
}
|
||||
|
||||
/// Returns a reference to the request protection key of this [`ReqEncrCtx`].
|
||||
pub fn prot_key(&self) -> &SymKey {
|
||||
&self.prot_key
|
||||
}
|
||||
}
|
||||
|
||||
/// Public key components of an [`openssl::ec::EcKey`] key.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EcPubKeyCoord([u8; 160]);
|
||||
impl AsRef<[u8]> for EcPubKeyCoord {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
const ECDH_PUB_KEY_COORD_POINT_SIZE: usize = 0x50;
|
||||
|
||||
impl EcPubKeyCoord {
|
||||
/// Returns the SHA256 hash of the [`EcPubKeyCoord`].
|
||||
///
|
||||
/// If [`EcPubKeyCoord`] was built from a host-key, this value is the public host-key hash.
|
||||
pub fn sha256(&self) -> Result<DigestBytes> {
|
||||
hash(MessageDigest::sha256(), self.as_ref())
|
||||
}
|
||||
|
||||
/// Construct a [`EcPubKeyCoord`]
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is marked unsafe, because data not representing two EC points violates the
|
||||
/// invariant of this struct.
|
||||
pub unsafe fn from_data(data: [u8; 160]) -> Self {
|
||||
EcPubKeyCoord(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
|
||||
/// The two coordinates are padded to 80 bytes each.
|
||||
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
|
||||
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 as i32)?;
|
||||
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?);
|
||||
Ok(coord.try_into().unwrap())
|
||||
}
|
||||
|
||||
impl TryFrom<EcPubKeyCoord> for PKey<Public> {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(value: EcPubKeyCoord) -> Result<Self, Self::Error> {
|
||||
let ecdh = value.as_ref();
|
||||
let grp = EcGroup::from_curve_name(Nid::SECP521R1)?;
|
||||
let x = BigNum::from_slice(&ecdh[..ECDH_PUB_KEY_COORD_POINT_SIZE])?;
|
||||
let y = BigNum::from_slice(&ecdh[ECDH_PUB_KEY_COORD_POINT_SIZE..])?;
|
||||
let ec_key = EcKey::from_public_key_affine_coordinates(&grp, &x, &y)?;
|
||||
Self::from_ec_key(ec_key)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ecdh_from {
|
||||
($type: ty) => {
|
||||
impl TryFrom<&PKeyRef<$type>> for EcPubKeyCoord {
|
||||
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(Self(coord))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PKey<$type>> for EcPubKeyCoord {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(key: PKey<$type>) -> Result<Self, Self::Error> {
|
||||
let key_ref = key.as_ref();
|
||||
key_ref.try_into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ecdh_from!(Private);
|
||||
ecdh_from!(Public);
|
||||
|
||||
/// Representation of the shared parts of the request header.
|
||||
/// Used by [`ReqEncrCtx`]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
|
||||
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>,
|
||||
}
|
||||
assert_size!(RequestHdr, 48);
|
||||
|
||||
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>);
|
||||
}
|
||||
|
||||
/// A struct to represent some parts of a binary/encrypted request.
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub(crate) struct BinReqValues<'a> {
|
||||
iv: &'a [u8],
|
||||
aad: &'a [u8],
|
||||
req_dep_aad: &'a [u8],
|
||||
encr: &'a [u8],
|
||||
tag: &'a [u8],
|
||||
version: u32,
|
||||
len: usize,
|
||||
}
|
||||
impl<'a> BinReqValues<'a> {
|
||||
pub(crate) const TAG_LEN: usize = SymKeyType::AES_256_GCM_TAG_LEN;
|
||||
|
||||
/// Get the locations from this request.
|
||||
///
|
||||
/// Does minimal sanity test, just tests to prevent panics.
|
||||
/// `req` may be larger than the actual request.
|
||||
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
|
||||
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
|
||||
let rql = hdr.rql.get() as usize;
|
||||
let sea = hdr.sea.get() as usize;
|
||||
|
||||
if rql < req.len() || sea + Self::TAG_LEN > rql {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
let aad_size = rql - sea - Self::TAG_LEN;
|
||||
if aad_size < size_of::<RequestHdr>() {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
|
||||
let iv = &req[0x10..0x1c];
|
||||
let aad = &req[..aad_size];
|
||||
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
|
||||
let encr = &req[aad_size..(aad_size + sea)];
|
||||
let tag = &req[rql - Self::TAG_LEN..];
|
||||
|
||||
Ok(Self {
|
||||
iv,
|
||||
aad,
|
||||
req_dep_aad,
|
||||
encr,
|
||||
tag,
|
||||
version: hdr.rqvn.get(),
|
||||
len: rql,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the version of this [`BinReqValues`].
|
||||
pub(crate) fn version(&self) -> u32 {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Returns the length of this [`BinReqValues`].
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns the size of the encrypted area
|
||||
pub(crate) fn sea(&self) -> u32 {
|
||||
self.encr.len() as u32
|
||||
}
|
||||
|
||||
/// Decrypts the encrypted area with the provided key
|
||||
pub(crate) fn decrypt(&self, key: &SymKey) -> Result<Confidential<Vec<u8>>> {
|
||||
let result = decrypt_aead(key, self.iv, self.aad, self.encr, self.tag)?;
|
||||
Ok(result.into_plain())
|
||||
}
|
||||
|
||||
/// Returns a reference to the request dependent authenticated area of this [`BinReqValues`]
|
||||
/// already interpreted.
|
||||
///
|
||||
/// If target struct is larger than the request depended-AAD None is returned. See
|
||||
/// [`FromBytes::ref_from_prefix`]
|
||||
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: FromBytes + Sized + Immutable + KnownLayout,
|
||||
{
|
||||
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
|
||||
}
|
||||
|
||||
/// Returns a reference to the tag of this [`BinReqValues`].
|
||||
pub(crate) fn tag(&self) -> &[u8] {
|
||||
self.tag
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{get_test_asset, request::SymKey, test_utils::*};
|
||||
|
||||
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 = [
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key),
|
||||
];
|
||||
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_mut_bytes();
|
||||
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 ec_pub_ec_coord_from() {
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let pub_key = get_test_asset!("keys/public_cust.bin");
|
||||
assert_eq!(pub_key.len(), 160);
|
||||
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
assert_eq!(ec_coord.as_ref(), pub_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ec_pub_ec_coord_hash() {
|
||||
let exp = [
|
||||
0x5e, 0xe9, 0x05, 0xa9, 0xbe, 0x70, 0x36, 0x68, 0x15, 0xa4, 0x56, 0x41, 0xaf, 0xae,
|
||||
0x00, 0x97, 0x3b, 0x1f, 0x45, 0x29, 0x2f, 0x43, 0xbc, 0xd7, 0x63, 0x8e, 0xe2, 0xa7,
|
||||
0x3f, 0xd7, 0xc4, 0x5e,
|
||||
];
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
let hash = ec_coord.sha256().unwrap();
|
||||
|
||||
assert_eq!(hash.as_ref(), &exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_ecdh_and_vice_versa() {
|
||||
let (_, cust_pub) = get_test_keys();
|
||||
let phk: EcPubKeyCoord = cust_pub.clone().try_into().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
phk.as_ref(),
|
||||
&[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 118, 136, 28, 216, 75, 139, 109, 231,
|
||||
18, 60, 126, 144, 14, 223, 120, 231, 247, 182, 132, 153, 145, 70, 177, 38, 59, 168,
|
||||
184, 108, 132, 71, 240, 138, 182, 212, 105, 194, 177, 40, 237, 158, 28, 53, 1, 88,
|
||||
5, 172, 211, 211, 2, 51, 211, 145, 34, 247, 226, 248, 170, 28, 43, 20, 123, 120,
|
||||
131, 180, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 69, 86, 194, 92,
|
||||
249, 47, 41, 206, 102, 189, 68, 17, 77, 107, 123, 60, 120, 225, 58, 63, 144, 189,
|
||||
185, 0, 64, 246, 135, 110, 82, 98, 247, 120, 166, 26, 147, 125, 27, 52, 128, 46,
|
||||
178, 87, 227, 78, 6, 114, 221, 95, 42, 52, 122, 221, 170, 40, 32, 53, 9, 42, 112,
|
||||
195, 92, 46, 121, 115
|
||||
]
|
||||
);
|
||||
let cust_pub_back: PKey<Public> = phk.try_into().unwrap();
|
||||
assert!(cust_pub.public_eq(&cust_pub_back));
|
||||
}
|
||||
}
|
||||
257
rust/pv/src/req/context.rs
Normal file
257
rust/pv/src/req/context.rs
Normal file
@@ -0,0 +1,257 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Request encryption context for managing UV request encryption.
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use pv_core::request::{RequestMagic, RequestVersion};
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::{Aad, EcPubKeyCoord, Encrypt, RequestHdr};
|
||||
use crate::crypto::{
|
||||
encrypt_aead, gen_ec_key, random_array, AeadEncryptionResult, SymKey, SymKeyType,
|
||||
};
|
||||
use crate::misc::to_u32;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Context used to manage the encryption of requests.
|
||||
/// Intended to be used by [`Request`](super::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`](super::Keyslot)
|
||||
/// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`](super::Keyslot)
|
||||
///
|
||||
/// If an argument is set to `None` a random 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(Nid::SECP521R1)?);
|
||||
let prot_key = prot_key
|
||||
.into()
|
||||
.unwrap_or(SymKey::random(SymKeyType::Aes256Gcm)?);
|
||||
Ok(Self {
|
||||
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(key_tp: SymKeyType) -> Result<Self> {
|
||||
match key_tp {
|
||||
SymKeyType::Aes256Gcm => Self::new_aes_256(None, None, None),
|
||||
SymKeyType::Aes256Xts => Err(Error::NoAeadKey),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(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(|| {
|
||||
pv_core::Error::Specification("Configured request size to large".to_string())
|
||||
})?;
|
||||
let sea = to_u32(encr_size)
|
||||
.ok_or_else(|| pv_core::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[..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<EcPubKeyCoord> {
|
||||
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
|
||||
}
|
||||
|
||||
/// 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(crate) fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AeadEncryptionResult> {
|
||||
encrypt_aead(&self.prot_key, &self.iv, aad, conf)
|
||||
}
|
||||
|
||||
/// Returns a reference to the request protection key of this [`ReqEncrCtx`].
|
||||
pub fn prot_key(&self) -> &SymKey {
|
||||
&self.prot_key
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::req::hostkey::HostKey;
|
||||
use crate::req::keyslot::Keyslot;
|
||||
use crate::request::SymKey;
|
||||
use crate::test_utils::*;
|
||||
|
||||
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(HostKey::V1(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(HostKey::V1(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 host_key = HostKey::V1(host_key);
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let ks = [
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key),
|
||||
];
|
||||
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);
|
||||
}
|
||||
}
|
||||
154
rust/pv/src/req/ec_coord.rs
Normal file
154
rust/pv/src/req/ec_coord.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! EC public key coordinate utilities and conversions.
|
||||
|
||||
use openssl::bn::{BigNum, BigNumContext};
|
||||
use openssl::ec::{EcGroup, EcGroupRef, EcKey, EcPointRef};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::{DigestBytes, MessageDigest};
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
|
||||
use crate::crypto::hash;
|
||||
use crate::Result;
|
||||
|
||||
/// Public key components of an [`openssl::ec::EcKey`] key.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EcPubKeyCoord([u8; 160]);
|
||||
|
||||
impl AsRef<[u8]> for EcPubKeyCoord {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
const ECDH_PUB_KEY_COORD_POINT_SIZE: usize = 0x50;
|
||||
|
||||
impl EcPubKeyCoord {
|
||||
/// Returns the SHA256 hash of the [`EcPubKeyCoord`].
|
||||
///
|
||||
/// If [`EcPubKeyCoord`] was built from a host-key, this value is the public host-key hash.
|
||||
pub fn sha256(&self) -> Result<DigestBytes> {
|
||||
hash(MessageDigest::sha256(), self.as_ref())
|
||||
}
|
||||
|
||||
/// Construct a [`EcPubKeyCoord`]
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is marked unsafe, because data not representing two EC points violates the
|
||||
/// invariant of this struct.
|
||||
pub unsafe fn from_data(data: [u8; 160]) -> Self {
|
||||
EcPubKeyCoord(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
|
||||
/// The two coordinates are padded to 80 bytes each.
|
||||
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
|
||||
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 as i32)?;
|
||||
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?);
|
||||
Ok(coord.try_into().unwrap())
|
||||
}
|
||||
|
||||
impl TryFrom<EcPubKeyCoord> for PKey<Public> {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(value: EcPubKeyCoord) -> Result<Self, Self::Error> {
|
||||
let ecdh = value.as_ref();
|
||||
let grp = EcGroup::from_curve_name(Nid::SECP521R1)?;
|
||||
let x = BigNum::from_slice(&ecdh[..ECDH_PUB_KEY_COORD_POINT_SIZE])?;
|
||||
let y = BigNum::from_slice(&ecdh[ECDH_PUB_KEY_COORD_POINT_SIZE..])?;
|
||||
let ec_key = EcKey::from_public_key_affine_coordinates(&grp, &x, &y)?;
|
||||
Self::from_ec_key(ec_key)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ecdh_from {
|
||||
($type: ty) => {
|
||||
impl TryFrom<&PKeyRef<$type>> for EcPubKeyCoord {
|
||||
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(Self(coord))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PKey<$type>> for EcPubKeyCoord {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(key: PKey<$type>) -> Result<Self, Self::Error> {
|
||||
let key_ref = key.as_ref();
|
||||
key_ref.try_into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ecdh_from!(Private);
|
||||
ecdh_from!(Public);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn ec_pub_ec_coord_from() {
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let pub_key = get_test_asset!("keys/public_cust.bin");
|
||||
assert_eq!(pub_key.len(), 160);
|
||||
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
assert_eq!(ec_coord.as_ref(), pub_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ec_pub_ec_coord_hash() {
|
||||
let exp = [
|
||||
0x5e, 0xe9, 0x05, 0xa9, 0xbe, 0x70, 0x36, 0x68, 0x15, 0xa4, 0x56, 0x41, 0xaf, 0xae,
|
||||
0x00, 0x97, 0x3b, 0x1f, 0x45, 0x29, 0x2f, 0x43, 0xbc, 0xd7, 0x63, 0x8e, 0xe2, 0xa7,
|
||||
0x3f, 0xd7, 0xc4, 0x5e,
|
||||
];
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
let hash = ec_coord.sha256().unwrap();
|
||||
|
||||
assert_eq!(hash.as_ref(), &exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_ecdh_and_vice_versa() {
|
||||
let (_, cust_pub) = get_test_keys();
|
||||
let phk: EcPubKeyCoord = cust_pub.clone().try_into().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
phk.as_ref(),
|
||||
&[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 118, 136, 28, 216, 75, 139, 109, 231,
|
||||
18, 60, 126, 144, 14, 223, 120, 231, 247, 182, 132, 153, 145, 70, 177, 38, 59, 168,
|
||||
184, 108, 132, 71, 240, 138, 182, 212, 105, 194, 177, 40, 237, 158, 28, 53, 1, 88,
|
||||
5, 172, 211, 211, 2, 51, 211, 145, 34, 247, 226, 248, 170, 28, 43, 20, 123, 120,
|
||||
131, 180, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 69, 86, 194, 92,
|
||||
249, 47, 41, 206, 102, 189, 68, 17, 77, 107, 123, 60, 120, 225, 58, 63, 144, 189,
|
||||
185, 0, 64, 246, 135, 110, 82, 98, 247, 120, 166, 26, 147, 125, 27, 52, 128, 46,
|
||||
178, 87, 227, 78, 6, 114, 221, 95, 42, 52, 122, 221, 170, 40, 32, 53, 9, 42, 112,
|
||||
195, 92, 46, 121, 115
|
||||
]
|
||||
);
|
||||
let cust_pub_back: PKey<Public> = phk.try_into().unwrap();
|
||||
assert!(cust_pub.public_eq(&cust_pub_back));
|
||||
}
|
||||
}
|
||||
54
rust/pv/src/req/encrypt.rs
Normal file
54
rust/pv/src/req/encrypt.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Encryption trait and authenticated data types for UV requests.
|
||||
|
||||
use openssl::pkey::{PKeyRef, Private};
|
||||
|
||||
use crate::req::Keyslot;
|
||||
use crate::Result;
|
||||
|
||||
/// 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: &PKeyRef<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: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Types of Authenticated Data
|
||||
#[allow(missing_debug_implementations)]
|
||||
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),
|
||||
}
|
||||
91
rust/pv/src/req/header.rs
Normal file
91
rust/pv/src/req/header.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Request header structure for UV requests.
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U32};
|
||||
|
||||
use crate::assert_size;
|
||||
|
||||
/// Representation of the shared parts of the request header.
|
||||
/// Used by [`ReqEncrCtx`](super::ReqEncrCtx)
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
|
||||
pub struct RequestHdr {
|
||||
magic: [u8; 8],
|
||||
pub(crate) rqvn: U32<BigEndian>,
|
||||
pub(crate) rql: U32<BigEndian>,
|
||||
iv: [u8; 12],
|
||||
reserved1c: [u8; 4],
|
||||
reserved20: [u8; 7],
|
||||
nks: u8,
|
||||
reserved28: u32,
|
||||
pub(crate) sea: U32<BigEndian>,
|
||||
}
|
||||
assert_size!(RequestHdr, 48);
|
||||
|
||||
impl RequestHdr {
|
||||
pub(crate) 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::*;
|
||||
|
||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||
|
||||
#[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_mut_bytes();
|
||||
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);
|
||||
}
|
||||
}
|
||||
30
rust/pv/src/req/hostkey.rs
Normal file
30
rust/pv/src/req/hostkey.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Host key types for UV requests
|
||||
|
||||
use openssl::pkey::{PKey, Public};
|
||||
|
||||
/// Versioned host keys container
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HostKey {
|
||||
/// ECDH public key
|
||||
V1(PKey<Public>),
|
||||
}
|
||||
|
||||
impl HostKey {
|
||||
/// Return the ECDH public key
|
||||
pub fn ec_key(&self) -> &PKey<Public> {
|
||||
match self {
|
||||
HostKey::V1(ec_key) => ec_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<HostKey> for HostKey {
|
||||
fn as_ref(&self) -> &HostKey {
|
||||
self
|
||||
}
|
||||
}
|
||||
150
rust/pv/src/req/keyslot.rs
Normal file
150
rust/pv/src/req/keyslot.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! IBM Z Host key-slot implementations.
|
||||
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
|
||||
use super::ec_coord::EcPubKeyCoord;
|
||||
use super::encrypt::Encrypt;
|
||||
use crate::crypto::{derive_aes256_gcm_key, encrypt_aead, hash};
|
||||
use crate::request::HostKey;
|
||||
use crate::Result;
|
||||
|
||||
/// 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 KeyslotV1(PKey<Public>);
|
||||
|
||||
impl KeyslotV1 {
|
||||
/// Size of a host-key hash
|
||||
pub const PHKH_SIZE: u32 = 0x20;
|
||||
/// Size of complete V1 keyslot in bytes
|
||||
pub const SIZE: usize = 80;
|
||||
|
||||
/// Creates a new Keyslot from the provided public key
|
||||
pub fn new(hostkey: PKey<Public>) -> Self {
|
||||
Self(hostkey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypt for KeyslotV1 {
|
||||
/// 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: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let derived_key = derive_aes256_gcm_key(priv_key, &self.0)?;
|
||||
let mut wrpk_and_kst =
|
||||
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
|
||||
assert_eq!(wrpk_and_kst.len(), 48);
|
||||
let phk: EcPubKeyCoord = self.0.as_ref().try_into()?;
|
||||
|
||||
to.reserve(80);
|
||||
let hash = hash(MessageDigest::sha256(), phk.as_ref())?;
|
||||
assert_eq!(hash.len(), 32);
|
||||
to.extend_from_slice(&hash);
|
||||
to.append(&mut wrpk_and_kst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Versioned keyslot container
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Keyslot {
|
||||
/// V1 key-slots with ECDH keys
|
||||
V1(KeyslotV1),
|
||||
}
|
||||
|
||||
impl Keyslot {
|
||||
/// Return a keyslot with the same key-type as the given host-key
|
||||
pub fn new(hostkey: HostKey) -> Self {
|
||||
match hostkey {
|
||||
HostKey::V1(key) => Keyslot::V1(KeyslotV1::new(key)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the public host key hash size for the given version of the key-slot in bytes
|
||||
pub fn phkh_size(&self) -> u32 {
|
||||
match self {
|
||||
Keyslot::V1(_) => KeyslotV1::PHKH_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the size of the key-slot in bytes
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Keyslot::V1(_) => KeyslotV1::SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return whether the key-slot uses hybrid keys
|
||||
pub fn is_hybrid(&self) -> bool {
|
||||
match self {
|
||||
Keyslot::V1(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypt for Keyslot {
|
||||
fn encrypt_to(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
priv_key: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
Keyslot::V1(ks) => ks.encrypt_to(secret, priv_key, to),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PKey<Public>> for Keyslot {
|
||||
fn from(key: PKey<Public>) -> Self {
|
||||
Keyslot::V1(KeyslotV1::new(key))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn keyslot() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec();
|
||||
|
||||
let keyslot = KeyslotV1(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);
|
||||
}
|
||||
}
|
||||
25
rust/pv/src/req/mod.rs
Normal file
25
rust/pv/src/req/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
//! Request encryption and key management for IBM Z Ultravisor.
|
||||
//!
|
||||
//! This module provides functionality for creating and encrypting requests to the
|
||||
//! IBM Z Ultravisor, including host key management, keyslot encryption, and request
|
||||
//! context handling.
|
||||
|
||||
mod context;
|
||||
mod ec_coord;
|
||||
mod encrypt;
|
||||
mod header;
|
||||
mod hostkey;
|
||||
mod keyslot;
|
||||
mod request;
|
||||
|
||||
// Re-export public types
|
||||
pub use context::ReqEncrCtx;
|
||||
pub use ec_coord::EcPubKeyCoord;
|
||||
pub use encrypt::{Aad, Encrypt};
|
||||
pub use header::RequestHdr;
|
||||
pub use hostkey::HostKey;
|
||||
pub use keyslot::{Keyslot, KeyslotV1};
|
||||
pub use request::{BinReqValues, Request};
|
||||
265
rust/pv/src/req/request.rs
Normal file
265
rust/pv/src/req/request.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
use std::mem::size_of;
|
||||
|
||||
use zerocopy::{FromBytes, Immutable, KnownLayout};
|
||||
|
||||
use super::HostKey;
|
||||
use crate::crypto::{decrypt_aead, SymKey, SymKeyType};
|
||||
use crate::req::context::ReqEncrCtx;
|
||||
use crate::req::header::RequestHdr;
|
||||
use crate::request::Confidential;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// 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`](super::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: HostKey);
|
||||
}
|
||||
|
||||
/// A struct to represent some parts of a binary/encrypted request.
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub struct BinReqValues<'a> {
|
||||
iv: &'a [u8],
|
||||
aad: &'a [u8],
|
||||
req_dep_aad: &'a [u8],
|
||||
encr: &'a [u8],
|
||||
tag: &'a [u8],
|
||||
version: u32,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<'a> BinReqValues<'a> {
|
||||
pub(crate) const TAG_LEN: usize = SymKeyType::AES_256_GCM_TAG_LEN;
|
||||
|
||||
/// Get the locations from this request.
|
||||
///
|
||||
/// Does minimal sanity test, just tests to prevent panics.
|
||||
/// `req` may be larger than the actual request.
|
||||
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
|
||||
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
|
||||
let rql = hdr.rql.get() as usize;
|
||||
let sea = hdr.sea.get() as usize;
|
||||
|
||||
if rql < req.len() || sea + Self::TAG_LEN > rql {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
let aad_size = rql - sea - Self::TAG_LEN;
|
||||
if aad_size < size_of::<RequestHdr>() {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
|
||||
let iv = &req[0x10..0x1c];
|
||||
let aad = &req[..aad_size];
|
||||
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
|
||||
let encr = &req[aad_size..(aad_size + sea)];
|
||||
let tag = &req[rql - Self::TAG_LEN..];
|
||||
|
||||
Ok(Self {
|
||||
iv,
|
||||
aad,
|
||||
req_dep_aad,
|
||||
encr,
|
||||
tag,
|
||||
version: hdr.rqvn.get(),
|
||||
len: rql,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the version of this [`BinReqValues`].
|
||||
pub(crate) fn version(&self) -> u32 {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Returns the length of this [`BinReqValues`].
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns the size of the encrypted area
|
||||
pub(crate) fn sea(&self) -> u32 {
|
||||
self.encr.len() as u32
|
||||
}
|
||||
|
||||
/// Decrypts the encrypted area with the provided key
|
||||
pub(crate) fn decrypt(&self, key: &SymKey) -> Result<Confidential<Vec<u8>>> {
|
||||
let result = decrypt_aead(key, self.iv, self.aad, self.encr, self.tag)?;
|
||||
Ok(result.into_plain())
|
||||
}
|
||||
|
||||
/// Returns a reference to the request dependent authenticated area of this [`BinReqValues`]
|
||||
/// already interpreted.
|
||||
///
|
||||
/// If target struct is larger than the request depended-AAD None is returned. See
|
||||
/// [`FromBytes::ref_from_prefix`]
|
||||
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: FromBytes + Sized + Immutable + KnownLayout,
|
||||
{
|
||||
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
|
||||
}
|
||||
|
||||
/// Returns a reference to the tag of this [`BinReqValues`].
|
||||
pub(crate) fn tag(&self) -> &[u8] {
|
||||
self.tag
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::req::header::RequestHdr;
|
||||
use crate::req::{Aad, HostKey, Keyslot, ReqEncrCtx};
|
||||
use crate::request::SymKey;
|
||||
use crate::test_utils::*;
|
||||
|
||||
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(HostKey::V1(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 host_key = HostKey::V1(host_key);
|
||||
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 host_key = HostKey::V1(host_key);
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let ks = [
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key.clone()),
|
||||
Keyslot::new(host_key),
|
||||
];
|
||||
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_mut_bytes();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,15 @@
|
||||
|
||||
// DO NOT USE ANY OF THESE ITEMS IN PRODUCTION CODE
|
||||
// USED FOR INTERNAL UNIT AND FVT TESTING ONLY!!!
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use openssl::{
|
||||
bn::BigNum,
|
||||
ec::{EcGroup, EcKey},
|
||||
error::ErrorStack,
|
||||
nid::Nid,
|
||||
pkey::{PKey, Private, Public},
|
||||
x509::{X509Crl, X509},
|
||||
};
|
||||
use openssl::bn::BigNum;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, Private, Public};
|
||||
use openssl::x509::{X509Crl, X509};
|
||||
|
||||
/// TEST ONLY! Loads the specified asset into the binary at compile time.
|
||||
///
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::x509::{X509Crl, X509};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
x509::{X509Crl, X509},
|
||||
};
|
||||
|
||||
/// Read all CRLs from the buffer and parse them into a vector.
|
||||
///
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::req::Keyslot;
|
||||
use crate::static_assert;
|
||||
use crate::{Error, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::arcb::AttestationFlags;
|
||||
use crate::req::KeyslotV1;
|
||||
use crate::{static_assert, Error, Result};
|
||||
|
||||
/// Hash for additional-data stuff used for parsing [`AdditionalData`]
|
||||
pub(super) const PHKH_SIZE: u32 = 0x20;
|
||||
static_assert!(Keyslot::PHKH_SIZE == PHKH_SIZE);
|
||||
static_assert!(KeyslotV1::PHKH_SIZE == PHKH_SIZE);
|
||||
pub(super) const SECRET_STORE_HASH_SIZE: u32 = 0x40;
|
||||
pub(super) const FW_STATE_SIZE: u32 = 0x140;
|
||||
|
||||
|
||||
@@ -2,27 +2,19 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use openssl::pkey::{PKey, Public};
|
||||
use std::mem::size_of;
|
||||
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
attest::{AttestationMagic, AttestationMeasAlg},
|
||||
crypto::random_array,
|
||||
misc::Flags,
|
||||
req::{Aad, BinReqValues, Keyslot, ReqEncrCtx},
|
||||
request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize},
|
||||
static_assert,
|
||||
uv::UvFlags,
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
use super::{
|
||||
additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE},
|
||||
AttNonce,
|
||||
};
|
||||
|
||||
use super::additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE};
|
||||
use super::AttNonce;
|
||||
use crate::attest::{AttestationMagic, AttestationMeasAlg};
|
||||
use crate::crypto::random_array;
|
||||
use crate::misc::Flags;
|
||||
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
|
||||
use crate::request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize};
|
||||
use crate::uv::UvFlags;
|
||||
use crate::{assert_size, static_assert, Error, Result};
|
||||
#[cfg(doc)]
|
||||
use crate::{
|
||||
request::SymKeyType,
|
||||
@@ -59,7 +51,7 @@ use crate::{
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use s390_pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion};
|
||||
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx};
|
||||
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx, HostKey};
|
||||
/// # fn main() -> s390_pv::Result<()> {
|
||||
/// let att_version = AttestationVersion::One;
|
||||
/// let meas_alg = AttestationMeasAlg::HmacSha512;
|
||||
@@ -68,11 +60,11 @@ use crate::{
|
||||
/// let hkd = s390_pv::misc::read_certs(&std::fs::read("host-key-document.crt")?)?;
|
||||
/// // IBM issued HKD certificates typically have one X509
|
||||
/// let hkd = hkd.first().unwrap().public_key()?;
|
||||
/// arcb.add_hostkey(hkd);
|
||||
/// arcb.add_hostkey(HostKey::V1(hkd));
|
||||
/// // you can add multiple hostkeys
|
||||
/// // arcb.add_hostkey(another_hkd);
|
||||
/// // encrypt it
|
||||
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256)?;
|
||||
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
|
||||
/// let arcb = arcb.encrypt(&ctx)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
@@ -243,7 +235,7 @@ impl Request for AttestationRequest {
|
||||
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
|
||||
}
|
||||
|
||||
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
|
||||
fn add_hostkey(&mut self, hostkey: HostKey) {
|
||||
self.keyslots.push(Keyslot::new(hostkey))
|
||||
}
|
||||
}
|
||||
@@ -419,8 +411,9 @@ impl Zeroize for ReqConfData {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use crate::{get_test_asset, request::SymKey, test_utils::get_test_keys};
|
||||
use crate::get_test_asset;
|
||||
use crate::request::SymKey;
|
||||
use crate::test_utils::get_test_keys;
|
||||
|
||||
const ARPK: [u8; 32] = [0x17; 32];
|
||||
const NONCE: [u8; 16] = [0xab; 16];
|
||||
@@ -450,7 +443,7 @@ mod test {
|
||||
arcb.conf.value_mut().nonce = NONCE;
|
||||
arcb.conf.value_mut().meas_key = MEAS;
|
||||
|
||||
arcb.add_hostkey(host_key);
|
||||
arcb.add_hostkey(HostKey::V1(host_key));
|
||||
arcb.encrypt(&ctx).unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use super::AttNonce;
|
||||
use crate::{
|
||||
attest::AttestationMeasAlg, brcb::BootHdrTags, crypto::calculate_hmac, request::Confidential,
|
||||
uv::ConfigUid, Result,
|
||||
};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
pkey::{PKeyRef, Private},
|
||||
};
|
||||
use std::mem::size_of;
|
||||
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::{PKeyRef, Private};
|
||||
use zerocopy::{BigEndian, IntoBytes, U16, U32};
|
||||
|
||||
use super::AttNonce;
|
||||
use crate::attest::AttestationMeasAlg;
|
||||
#[cfg(doc)]
|
||||
use crate::attest::AttestationRequest;
|
||||
use crate::brcb::BootHdrTags;
|
||||
use crate::crypto::calculate_hmac;
|
||||
use crate::request::Confidential;
|
||||
use crate::uv::ConfigUid;
|
||||
use crate::Result;
|
||||
|
||||
/// Holds the data to be measured.
|
||||
///
|
||||
@@ -151,9 +152,10 @@ impl From<Vec<u8>> for AttestationMeasurement {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use openssl::pkey::PKey;
|
||||
|
||||
use super::*;
|
||||
|
||||
const M_KEY: [u8; 64] = [0x41; 64];
|
||||
const BOOT_HDR_TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
|
||||
const CUID: [u8; 16] = [5; 16];
|
||||
|
||||
@@ -2,24 +2,23 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::{guest_secret::ListableSecretHdr, user_data::UserData};
|
||||
use crate::{
|
||||
assert_size,
|
||||
crypto::{hkdf_rfc_5869, AeadEncryptionResult},
|
||||
misc::Flags,
|
||||
req::{Aad, BinReqValues, Keyslot, ReqEncrCtx},
|
||||
request::{BootHdrTags, Confidential, Request},
|
||||
secret::{ExtSecret, GuestSecret},
|
||||
uv::{ConfigUid, UvFlags},
|
||||
Result,
|
||||
};
|
||||
use openssl::{
|
||||
md::Md,
|
||||
pkey::{PKey, Private, Public},
|
||||
};
|
||||
use pv_core::{request::RequestVersion, secret::AddSecretMagic, uv::SecretId};
|
||||
use openssl::md::Md;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use pv_core::request::RequestVersion;
|
||||
use pv_core::secret::AddSecretMagic;
|
||||
use pv_core::uv::SecretId;
|
||||
use zerocopy::{Immutable, IntoBytes};
|
||||
|
||||
use super::guest_secret::ListableSecretHdr;
|
||||
use super::user_data::UserData;
|
||||
use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult};
|
||||
use crate::misc::Flags;
|
||||
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
|
||||
use crate::request::{BootHdrTags, Confidential, Request};
|
||||
use crate::secret::{ExtSecret, GuestSecret};
|
||||
use crate::uv::{ConfigUid, UvFlags};
|
||||
use crate::{assert_size, Result};
|
||||
|
||||
/// Authenticated data w/o user data
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
|
||||
@@ -310,7 +309,7 @@ impl Request for AddSecretRequest {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
|
||||
fn add_hostkey(&mut self, hostkey: HostKey) {
|
||||
self.keyslots.push(Keyslot::new(hostkey))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,25 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#[allow(unused_imports)] // used for more convenient docstring
|
||||
use super::asrcb::AddSecretRequest;
|
||||
use crate::{
|
||||
assert_size,
|
||||
crypto::{hash, random_array, SymKeyType},
|
||||
request::{
|
||||
openssl::{NID_ED25519, NID_ED448},
|
||||
Confidential,
|
||||
},
|
||||
uv::{
|
||||
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
||||
RetrieveCmd, SecretId,
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
nid::Nid,
|
||||
pkey::{Id, PKey, PKeyRef, Private},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{Id, PKey, PKeyRef, Private};
|
||||
use pv_core::static_assert;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use zerocopy::{BigEndian, KnownLayout};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, U16, U32};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
|
||||
|
||||
#[allow(unused_imports)] // used for more convenient docstring
|
||||
use super::asrcb::AddSecretRequest;
|
||||
use crate::crypto::{hash, random_array, SymKeyType};
|
||||
use crate::request::openssl::{NID_ED25519, NID_ED448};
|
||||
use crate::request::Confidential;
|
||||
use crate::uv::{
|
||||
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
||||
RetrieveCmd, SecretId,
|
||||
};
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
const ASSOC_SECRET_SIZE: usize = 32;
|
||||
const CCK_SIZE: usize = 32;
|
||||
@@ -476,13 +470,13 @@ impl UpdateCckHdr {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::HmacShaSizes as HmacSizes;
|
||||
use super::RetrievableSecret::*;
|
||||
use super::*;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use pv_core::uv::AesSizes;
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
use super::RetrievableSecret::*;
|
||||
use super::{HmacShaSizes as HmacSizes, *};
|
||||
|
||||
#[test]
|
||||
fn association() {
|
||||
let secret_value = [0x11; 32];
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{crypto::SymKeyType, pem::Pem, uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD, Result};
|
||||
|
||||
use log::warn;
|
||||
use pv_core::{
|
||||
request::Confidential,
|
||||
uv::{ListableSecretType, RetrievableSecret, RetrieveCmd},
|
||||
};
|
||||
use zerocopy::BigEndian;
|
||||
use zerocopy::{FromBytes, U16};
|
||||
use pv_core::request::Confidential;
|
||||
use pv_core::uv::{ListableSecretType, RetrievableSecret, RetrieveCmd};
|
||||
use zerocopy::{BigEndian, FromBytes, U16};
|
||||
|
||||
use crate::crypto::SymKeyType;
|
||||
use crate::pem::Pem;
|
||||
use crate::uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD;
|
||||
use crate::Result;
|
||||
|
||||
/// An IBM Protected Key
|
||||
///
|
||||
@@ -72,8 +72,8 @@ impl From<RetrieveCmd> for RetrievedSecret {
|
||||
|
||||
match kind {
|
||||
ListableSecretType::Retrievable(RetrievableSecret::PlainText) => {
|
||||
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is the
|
||||
// minimum size
|
||||
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is
|
||||
// the minimum size
|
||||
let len = U16::<BigEndian>::read_from_prefix(key.value())
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
@@ -81,8 +81,8 @@ impl From<RetrieveCmd> for RetrievedSecret {
|
||||
|
||||
// Test if the plain text secret has a size:
|
||||
// 1. len <= 8190
|
||||
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the
|
||||
// block length
|
||||
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the block
|
||||
// length
|
||||
// 3. bytes after len + 2 are zero
|
||||
match len <= MAX_SIZE_PLAIN_PAYLOAD
|
||||
&& key.value().len() - (len + 2) < SymKeyType::AES_256_GCM_BLOCK_LEN
|
||||
@@ -148,9 +148,10 @@ impl RetrievedSecret {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pv_core::uv::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn mk_retr(secret: &[u8]) -> RetrievedSecret {
|
||||
let entry = SecretEntry::new(
|
||||
0,
|
||||
|
||||
@@ -2,21 +2,17 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::assert_size;
|
||||
use crate::{
|
||||
crypto::{sign_msg, verify_signature},
|
||||
req::BinReqValues,
|
||||
request::{
|
||||
openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public},
|
||||
RequestMagic,
|
||||
},
|
||||
secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType},
|
||||
Error, Result,
|
||||
};
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::nid::Nid;
|
||||
use zerocopy::{BigEndian, FromBytes, IntoBytes, KnownLayout, U16};
|
||||
|
||||
use crate::crypto::{sign_msg, verify_signature};
|
||||
use crate::req::BinReqValues;
|
||||
use crate::request::openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public};
|
||||
use crate::request::RequestMagic;
|
||||
use crate::secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType};
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
/// User data.
|
||||
///
|
||||
/// User defined data can be:
|
||||
@@ -380,7 +376,8 @@ impl From<VerifiedUserData> for Vec<u8> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{get_test_asset, test_utils::get_test_keys};
|
||||
use crate::get_test_asset;
|
||||
use crate::test_utils::get_test_keys;
|
||||
|
||||
#[test]
|
||||
fn sign_null() {
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
|
||||
use core::slice;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use helper::download_first_crl_from_x509;
|
||||
use log::{debug, trace};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::stack::Stack;
|
||||
use openssl::x509::store::X509Store;
|
||||
use openssl::x509::{CrlStatus, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, X509};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use helper::download_first_crl_from_x509;
|
||||
#[cfg(test)]
|
||||
use test::download_first_crl_from_x509;
|
||||
|
||||
use crate::error::bail_hkd_verify;
|
||||
use crate::misc::{read_certs, read_file};
|
||||
use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
|
||||
use crate::Result;
|
||||
|
||||
mod helper;
|
||||
@@ -167,7 +167,8 @@ impl CertVerifier {
|
||||
/// * `cert_paths` - Paths to certificates for the chain of trust
|
||||
/// * `crl_paths` - Paths to certificate revocation lists for the chain of trust
|
||||
/// * `root_ca_path` - Path to the root of trust
|
||||
/// * `offline` - if set to true the verification process will not try to download CRLs from the internet.
|
||||
/// * `offline` - if set to true the verification process will not try to download CRLs from the
|
||||
/// internet.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
||||
@@ -2,27 +2,28 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::c_int;
|
||||
use std::path::Path;
|
||||
use std::str::from_utf8;
|
||||
|
||||
use log::debug;
|
||||
use openssl::asn1::{Asn1Time, Asn1TimeRef};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::nid::Nid;
|
||||
use openssl::ssl::SslFiletype;
|
||||
use openssl::stack::Stack;
|
||||
use openssl::x509::store::{File, X509Lookup, X509StoreBuilder, X509StoreRef};
|
||||
use openssl::x509::verify::{X509VerifyFlags, X509VerifyParam};
|
||||
use openssl::x509::{
|
||||
X509CrlRef, X509Name, X509NameRef, X509PurposeId, X509Ref, X509StoreContext,
|
||||
X509StoreContextRef, X509VerifyResult, X509,
|
||||
};
|
||||
|
||||
use crate::error::bail_hkd_verify;
|
||||
use crate::openssl_extensions::{AkidCheckResult, AkidExtension};
|
||||
use crate::HkdVerifyErrorType::*;
|
||||
use crate::{Error, Result};
|
||||
use log::debug;
|
||||
use openssl::{
|
||||
asn1::{Asn1Time, Asn1TimeRef},
|
||||
error::ErrorStack,
|
||||
nid::Nid,
|
||||
ssl::SslFiletype,
|
||||
stack::Stack,
|
||||
x509::{
|
||||
store::{File, X509Lookup, X509StoreBuilder, X509StoreRef},
|
||||
verify::{X509VerifyFlags, X509VerifyParam},
|
||||
X509CrlRef, X509Name, X509NameRef, X509PurposeId, X509Ref, X509StoreContext,
|
||||
X509StoreContextRef, X509VerifyResult, X509,
|
||||
},
|
||||
};
|
||||
use std::path::Path;
|
||||
use std::str::from_utf8;
|
||||
use std::{cmp::Ordering, ffi::c_int};
|
||||
|
||||
/// Minimum security level for the keys/certificates used to establish a chain of
|
||||
/// trust (see <https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html>
|
||||
@@ -306,9 +307,11 @@ pub fn x509_dist_points(cert: &X509Ref) -> Vec<String> {
|
||||
/// CRL not available at all URIs and unexpected format at all URIs are mapped to Ok(None)
|
||||
#[cfg(not(test))]
|
||||
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<openssl::x509::X509Crl>>> {
|
||||
use crate::utils::read_crls;
|
||||
use curl::easy::{Easy2, Handler, WriteError};
|
||||
use std::time::Duration;
|
||||
|
||||
use curl::easy::{Easy2, Handler, WriteError};
|
||||
|
||||
use crate::utils::read_crls;
|
||||
const CRL_TIMEOUT_MAX: Duration = Duration::from_secs(3);
|
||||
struct Buf(Vec<u8>);
|
||||
|
||||
@@ -397,9 +400,10 @@ pub fn armonk_locality_fixup(subject: &X509NameRef) -> Option<X509Name> {
|
||||
/// tests for some private functions
|
||||
mod test {
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
fn sys_to_asn1_time(syst: SystemTime) -> Asn1Time {
|
||||
let secs = syst
|
||||
|
||||
@@ -4,12 +4,17 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::{helper, helper::*, *};
|
||||
use crate::{utils::read_crls, Error, HkdVerifyErrorType::*};
|
||||
use openssl::{stack::Stack, x509::X509Crl};
|
||||
use std::path::Path;
|
||||
|
||||
use openssl::stack::Stack;
|
||||
use openssl::x509::X509Crl;
|
||||
|
||||
use super::helper::*;
|
||||
use super::{helper, *};
|
||||
use crate::test_utils::*;
|
||||
use crate::utils::read_crls;
|
||||
use crate::Error;
|
||||
use crate::HkdVerifyErrorType::*;
|
||||
|
||||
// Mock function
|
||||
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<X509Crl>>> {
|
||||
|
||||
@@ -3,25 +3,18 @@
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(missing_docs)]
|
||||
use openssl::{
|
||||
ec::{EcGroup, EcKey},
|
||||
nid::Nid,
|
||||
pkey::Private,
|
||||
};
|
||||
use s390_pv::{
|
||||
get_test_asset,
|
||||
request::{
|
||||
openssl::pkey::{PKey, Public},
|
||||
BootHdrTags, ReqEncrCtx, Request, SymKey,
|
||||
},
|
||||
secret::{
|
||||
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
|
||||
ExtSecret, GuestSecret,
|
||||
},
|
||||
test_utils::get_test_keys,
|
||||
uv::ConfigUid,
|
||||
Result,
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::Private;
|
||||
use s390_pv::request::openssl::pkey::{PKey, Public};
|
||||
use s390_pv::request::{BootHdrTags, HostKey, ReqEncrCtx, Request, SymKey};
|
||||
use s390_pv::secret::{
|
||||
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret,
|
||||
GuestSecret,
|
||||
};
|
||||
use s390_pv::test_utils::get_test_keys;
|
||||
use s390_pv::uv::ConfigUid;
|
||||
use s390_pv::{get_test_asset, Result};
|
||||
|
||||
const TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
|
||||
const CUID: ConfigUid = [0x42u8; 16];
|
||||
@@ -33,7 +26,7 @@ fn create_asrcb(
|
||||
ext_secret: Option<ExtSecret>,
|
||||
flags: AddSecretFlags,
|
||||
cuid: Option<ConfigUid>,
|
||||
hkd: PKey<Public>,
|
||||
hkd: HostKey,
|
||||
ctx: &ReqEncrCtx,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags);
|
||||
@@ -74,7 +67,14 @@ where
|
||||
true => Some(CUID),
|
||||
false => None,
|
||||
};
|
||||
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
|
||||
create_asrcb(
|
||||
guest_secret,
|
||||
ext_secret.into(),
|
||||
flags,
|
||||
cuid,
|
||||
HostKey::V1(host_key),
|
||||
&ctx,
|
||||
)
|
||||
}
|
||||
|
||||
fn association() -> GuestSecret {
|
||||
@@ -98,7 +98,7 @@ fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
|
||||
let mut asrcb =
|
||||
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
|
||||
|
||||
asrcb.add_hostkey(host_key);
|
||||
asrcb.add_hostkey(HostKey::V1(host_key));
|
||||
asrcb.set_user_data(user_data, Some(skey)).unwrap();
|
||||
asrcb.encrypt(&ctx).unwrap()
|
||||
}
|
||||
@@ -110,7 +110,7 @@ fn null_none_default_ncuid_one_user_unsgn() {
|
||||
let mut asrcb =
|
||||
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
|
||||
|
||||
asrcb.add_hostkey(host_key);
|
||||
asrcb.add_hostkey(HostKey::V1(host_key));
|
||||
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
|
||||
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
||||
|
||||
@@ -243,7 +243,7 @@ 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()));
|
||||
(0..7).for_each(|_| asrcb.add_hostkey(HostKey::V1(hkd.clone())));
|
||||
asrcb.set_cuid(CUID);
|
||||
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(missing_docs)]
|
||||
use std::ffi::c_int;
|
||||
|
||||
use s390_pv::request::CertVerifier;
|
||||
use s390_pv::test_utils::*;
|
||||
use s390_pv::{Error, HkdVerifyErrorType::*};
|
||||
use std::ffi::c_int;
|
||||
use s390_pv::Error;
|
||||
use s390_pv::HkdVerifyErrorType::*;
|
||||
|
||||
#[track_caller]
|
||||
fn verify_sign_error(exp_raw: c_int, obs: Error) {
|
||||
|
||||
@@ -16,13 +16,13 @@ readme = "README.md"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.169"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
thiserror = "2.0.11"
|
||||
libc = "0.2.186"
|
||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
thiserror = "2.0.18"
|
||||
zerocopy = {version = "0.8", features = ["derive"]}
|
||||
serde = { version = "1.0.217", features = ["derive"]}
|
||||
serde = { version = "1.0.228", features = ["derive"]}
|
||||
byteorder = "1.5"
|
||||
regex = "1.10"
|
||||
regex = "1.12"
|
||||
sha2 = "0.10.9"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
//! AP support functions
|
||||
//
|
||||
|
||||
use crate::{
|
||||
utils::{pv_guest_bit_set, read_file_string, write_file},
|
||||
Error, Result,
|
||||
};
|
||||
use std::{fmt, thread, time};
|
||||
|
||||
use regex::Regex;
|
||||
use std::fmt;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
use crate::utils::{pv_guest_bit_set, read_file_string, write_file};
|
||||
use crate::{Error, Result};
|
||||
|
||||
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
|
||||
|
||||
|
||||
@@ -82,7 +82,8 @@ impl<T: Default> Zeroize for Vec<T> {
|
||||
|
||||
impl Zeroize for String {
|
||||
fn zeroize(&mut self) {
|
||||
// SAFETY: The Vec<u8> zerorize function overwrites memory with the zero byte -> still valid UTF-8
|
||||
// SAFETY: The Vec<u8> zerorize function overwrites memory with the zero byte -> still valid
|
||||
// UTF-8
|
||||
unsafe { self.as_mut_vec().zeroize() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ mod uvdevice;
|
||||
mod uvsecret;
|
||||
|
||||
pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result};
|
||||
|
||||
/// Early Boot Customization (EBC) utilities.
|
||||
///
|
||||
/// This module provides types and functions for working with Early Boot
|
||||
@@ -28,13 +27,11 @@ pub mod attest {
|
||||
|
||||
/// Miscellaneous functions and definitions
|
||||
pub mod misc {
|
||||
pub use crate::utils::pv_guest_bit_set;
|
||||
pub use crate::utils::{create_file, open_file};
|
||||
pub use crate::utils::{decode_hex, encode_hex, parse_hex};
|
||||
pub use crate::utils::{read, write};
|
||||
pub use crate::utils::{read_exact_file, read_file, read_file_string, write_file};
|
||||
pub use crate::utils::{to_u16, to_u32, try_parse_u128, try_parse_u64};
|
||||
pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64};
|
||||
pub use crate::utils::{
|
||||
create_file, decode_hex, encode_hex, open_file, parse_hex, pv_guest_bit_set, read,
|
||||
read_exact_file, read_file, read_file_string, to_u16, to_u32, try_parse_u128,
|
||||
try_parse_u64, write, write_file, Flags, Lsb0Flags64, Msb0Flags64,
|
||||
};
|
||||
}
|
||||
|
||||
/// Definitions and functions for interacting with the Ultravisor
|
||||
@@ -43,8 +40,9 @@ pub mod misc {
|
||||
/// [`crate::uv::UvCmd`]
|
||||
pub mod uv {
|
||||
pub use crate::uvdevice::attest::AttestationCmd;
|
||||
pub use crate::uvdevice::retr_secret::RetrievableSecret;
|
||||
pub use crate::uvdevice::retr_secret::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes};
|
||||
pub use crate::uvdevice::retr_secret::{
|
||||
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, RetrievableSecret,
|
||||
};
|
||||
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd, RetrieveCmd};
|
||||
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
|
||||
pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess};
|
||||
@@ -77,15 +75,12 @@ pub mod request {
|
||||
|
||||
/// Functionalities for reading add-secret requests
|
||||
pub mod secret {
|
||||
pub use crate::uvsecret::AddSecretMagic;
|
||||
pub use crate::uvsecret::UserDataType;
|
||||
pub use crate::uvsecret::{AddSecretMagic, UserDataType};
|
||||
}
|
||||
|
||||
/// Functionalities for the AP bus
|
||||
pub mod ap {
|
||||
pub use crate::apdevice::Apqn;
|
||||
pub use crate::apdevice::RE_QUEUE_DIR;
|
||||
pub use crate::apdevice::{get_apqn_bind_state, set_apqn_bind_state};
|
||||
pub use crate::apdevice::{get_apqn_bind_state, set_apqn_bind_state, Apqn, RE_QUEUE_DIR};
|
||||
/// AP modes
|
||||
pub mod apqn_mode {
|
||||
pub use crate::apdevice::ApqnMode::{self, *};
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
use std::fmt::{Display, Formatter, Result as Resfmt};
|
||||
use std::fs::File;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8;
|
||||
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes};
|
||||
|
||||
use crate::misc::encode_hex;
|
||||
use crate::utils::open_file;
|
||||
use crate::{Error, Result};
|
||||
use std::{
|
||||
fmt::{Display, Formatter, Result as Resfmt},
|
||||
fs::File,
|
||||
os::unix::ffi::OsStrExt,
|
||||
path::{Path, PathBuf},
|
||||
str::from_utf8,
|
||||
};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes};
|
||||
|
||||
const HASH_LEN: usize = 32;
|
||||
// UserDataType::Unsigned.max() returns 512
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U64};
|
||||
|
||||
use crate::{
|
||||
macros::{bail_spec, file_error},
|
||||
Error, FileAccessErrorType, Result,
|
||||
};
|
||||
use crate::macros::{bail_spec, file_error};
|
||||
use crate::{Error, FileAccessErrorType, Result};
|
||||
|
||||
/// Trait that describes bitflags, represented by `T`.
|
||||
pub trait Flags<T>: From<T> + for<'a> From<&'a T> {
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{request::MagicValue, Error};
|
||||
use zerocopy::U32;
|
||||
use zerocopy::{BigEndian, ByteOrder};
|
||||
use zerocopy::{BigEndian, ByteOrder, U32};
|
||||
|
||||
use crate::request::MagicValue;
|
||||
use crate::Error;
|
||||
|
||||
/// The magic value used to identify an attestation request
|
||||
///
|
||||
|
||||
@@ -3,20 +3,18 @@
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::FileAccessErrorType;
|
||||
use crate::{Error, Result};
|
||||
use log::debug;
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fs::File,
|
||||
os::unix::prelude::{AsRawFd, RawFd},
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||
|
||||
#[cfg(not(test))]
|
||||
use ::libc::ioctl;
|
||||
use log::debug;
|
||||
#[cfg(test)]
|
||||
use test::mock_libc::ioctl;
|
||||
|
||||
use crate::{Error, FileAccessErrorType, Result};
|
||||
|
||||
/// Contains the rust representation of asm/uvdevice.h
|
||||
/// from kernel version: 6.5 verify
|
||||
mod ffi;
|
||||
@@ -55,7 +53,7 @@ fn ioctl_raw(raw_fd: RawFd, cmd: u64, cb: &mut IoctlCb) -> Result<()> {
|
||||
// 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.try_into().unwrap(), cb.as_ptr_mut());
|
||||
rc = ioctl(raw_fd, cmd, cb.as_ptr_mut());
|
||||
}
|
||||
|
||||
// NOTE io::Error handles all errnos ioctl uses
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
use std::ptr;
|
||||
|
||||
use zerocopy::{FromZeros, IntoBytes};
|
||||
|
||||
use super::{ffi, AttestationUserData, ConfigUid, UvCmd};
|
||||
use crate::{Error, Result};
|
||||
use std::ptr;
|
||||
use zerocopy::{FromZeros, IntoBytes};
|
||||
|
||||
/// _Retrieve Attestation Measurement_ UVC
|
||||
///
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::{assert_size, static_assert};
|
||||
use zerocopy::{FromBytes, IntoBytes};
|
||||
|
||||
use crate::{assert_size, static_assert};
|
||||
|
||||
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;
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::ffi::{self, uvio_uvdev_info};
|
||||
use crate::{
|
||||
misc::{Flags, Lsb0Flags64},
|
||||
uv::{UvCmd, UvDevice},
|
||||
Result,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
use zerocopy::{FromZeros, IntoBytes};
|
||||
|
||||
use super::ffi::{self, uvio_uvdev_info};
|
||||
use crate::misc::{Flags, Lsb0Flags64};
|
||||
use crate::uv::{UvCmd, UvDevice};
|
||||
use crate::Result;
|
||||
|
||||
/// Information of supported functions by the uvdevice
|
||||
///
|
||||
/// * `supp_uvio_cmds` - supported IOCTLs by this device
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::uv::{ListableSecretType, RetrieveCmd};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::uv::{ListableSecretType, RetrieveCmd};
|
||||
|
||||
/// Allowed sizes for AES keys
|
||||
#[non_exhaustive]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
@@ -335,8 +337,8 @@ mod test {
|
||||
assert_tokens(&retr, &[Token::Str("19 (EC-SECP521R1-PRIVATE-KEY)")]);
|
||||
}
|
||||
|
||||
// Ensure that the string representation of the retrievable types stay constant, or PEM will have
|
||||
// different, incompatible types
|
||||
// Ensure that the string representation of the retrievable types stay constant, or PEM will
|
||||
// have different, incompatible types
|
||||
#[test]
|
||||
fn stable_type_names() {
|
||||
assert_eq!("PLAINTEXT", RetrievableSecret::PlainText.to_string());
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::ffi;
|
||||
use crate::{
|
||||
request::{Confidential, MagicValue},
|
||||
uv::{SecretEntry, UvCmd},
|
||||
uvsecret::AddSecretMagic,
|
||||
Error, Result, PAGESIZE,
|
||||
};
|
||||
use std::io::Read;
|
||||
use std::mem::size_of_val;
|
||||
|
||||
use log::debug;
|
||||
use std::{io::Read, mem::size_of_val};
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::ffi;
|
||||
use crate::request::{Confidential, MagicValue};
|
||||
use crate::uv::{SecretEntry, UvCmd};
|
||||
use crate::uvsecret::AddSecretMagic;
|
||||
use crate::{Error, Result, PAGESIZE};
|
||||
|
||||
/// _List Secrets_ Ultravisor command.
|
||||
///
|
||||
/// The List Secrets Ultravisor call is used to list the
|
||||
|
||||
@@ -2,24 +2,20 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret},
|
||||
uvdevice::UvCmd,
|
||||
Error, Result,
|
||||
};
|
||||
use std::cmp::min;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::{Debug, Display, LowerHex, UpperHex};
|
||||
use std::io::{Cursor, Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
use std::slice::Iter;
|
||||
use std::vec::IntoIter;
|
||||
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use std::{
|
||||
cmp::min,
|
||||
ffi::CStr,
|
||||
fmt::{Debug, Display, LowerHex, UpperHex},
|
||||
io::{Cursor, Read, Seek, Write},
|
||||
mem::size_of,
|
||||
slice::Iter,
|
||||
vec::IntoIter,
|
||||
};
|
||||
use zerocopy::{BigEndian, ByteOrder};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
|
||||
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
|
||||
|
||||
use crate::uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret};
|
||||
use crate::uvdevice::UvCmd;
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
/// The 32 byte long ID of an UV secret
|
||||
///
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::{
|
||||
ffi::{c_int, c_ulong},
|
||||
sync::{Mutex, MutexGuard},
|
||||
};
|
||||
use std::ffi::{c_int, c_ulong};
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use super::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
/// needed to serialize all tests as tests operate on static data required by the mock
|
||||
@@ -129,8 +128,8 @@ impl UvCmd for TestCmd {
|
||||
}
|
||||
|
||||
impl UvDevice {
|
||||
/// Use this file as backing file for `uvdevice`. This is OK, as the ioctl is mocked and never touches the
|
||||
/// passed file
|
||||
/// Use this file as backing file for `uvdevice`. This is OK, as the ioctl is mocked and never
|
||||
/// touches the passed file
|
||||
fn test_dev() -> Self {
|
||||
Self(File::open(".").unwrap())
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::assert_size;
|
||||
use crate::{
|
||||
request::{MagicValue, RequestMagic},
|
||||
Error, Result,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::ByteOrder;
|
||||
use std::{fmt::Display, mem::size_of};
|
||||
use zerocopy::{BigEndian, Immutable, IntoBytes, U16};
|
||||
|
||||
use crate::request::{MagicValue, RequestMagic};
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
/// The magic value used to identify an `AddSecretRequest`.
|
||||
///
|
||||
/// The magic value is ASCII:
|
||||
@@ -145,11 +145,9 @@ impl From<UserDataType> for AddSecretMagic {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
request::MagicValue,
|
||||
secret::{AddSecretMagic, UserDataType},
|
||||
Error,
|
||||
};
|
||||
use crate::request::MagicValue;
|
||||
use crate::secret::{AddSecretMagic, UserDataType};
|
||||
use crate::Error;
|
||||
|
||||
#[test]
|
||||
fn convert_user_data() {
|
||||
|
||||
@@ -8,17 +8,17 @@ license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
clap = { version ="4.5", features = ["derive", "wrap_help"]}
|
||||
clap = { version ="4.6", features = ["derive", "wrap_help"]}
|
||||
lazy_static = "1.5"
|
||||
openssl = { version = "0.10.70" }
|
||||
openssl = { version = "0.10.80" }
|
||||
pv_core = { path = "../pv_core", package = "s390_pv_core"}
|
||||
rand = "0.9"
|
||||
regex = "1.11"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
regex = "1.12"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
utils = { path = "../utils" }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version ="4.5", features = ["derive", "wrap_help"]}
|
||||
clap_complete = "4.5"
|
||||
clap = { version ="4.6", features = ["derive", "wrap_help"]}
|
||||
clap_complete = "4.6"
|
||||
lazy_static = "1.5"
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
// Copyright IBM Corp. 2024
|
||||
// it under the terms of the MIT license. See LICENSE for details.
|
||||
|
||||
use clap::{CommandFactory, ValueEnum};
|
||||
use clap_complete::{generate_to, Shell};
|
||||
use std::env;
|
||||
use std::io::Error;
|
||||
|
||||
use clap::{CommandFactory, ValueEnum};
|
||||
use clap_complete::{generate_to, Shell};
|
||||
|
||||
include!("src/cli.rs");
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
//! AP support functions for pvapconfig
|
||||
//
|
||||
|
||||
use crate::helper::*;
|
||||
use pv_core::ap::*;
|
||||
use pv_core::misc::read_file_string;
|
||||
use std::path::Path;
|
||||
use std::slice::Iter;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use std::{thread, time};
|
||||
|
||||
use pv_core::ap::*;
|
||||
use pv_core::misc::read_file_string;
|
||||
|
||||
use crate::helper::*;
|
||||
|
||||
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
|
||||
|
||||
@@ -173,7 +174,7 @@ impl ApqnList {
|
||||
/// Sort this Apqnlist by card generation:
|
||||
/// newest generation first, older generations last.
|
||||
pub fn sort_by_gen(&mut self) {
|
||||
self.0.sort_unstable_by(|a, b| b.gen.cmp(&a.gen));
|
||||
self.0.sort_unstable_by_key(|b| std::cmp::Reverse(b.gen));
|
||||
}
|
||||
|
||||
/// Check MK restriction
|
||||
@@ -191,10 +192,12 @@ impl ApqnList {
|
||||
&& a1.info.is_some()
|
||||
&& a2.info.is_some()
|
||||
{
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
let i1 = match a1.info.as_ref().unwrap() {
|
||||
apqn_info::Ep11(i) => i,
|
||||
_ => continue,
|
||||
};
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
let i2 = match a2.info.as_ref().unwrap() {
|
||||
apqn_info::Ep11(i) => i,
|
||||
_ => continue,
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
//! Functions around handling the pvapconfig configuration file
|
||||
//
|
||||
|
||||
use std::fs::File;
|
||||
use std::slice::Iter;
|
||||
|
||||
use openssl::sha::sha256;
|
||||
use pv_core::misc::encode_hex;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::{self};
|
||||
use std::fs::File;
|
||||
use std::slice::Iter;
|
||||
|
||||
pub const STR_MODE_EP11: &str = "ep11";
|
||||
pub const STR_MODE_ACCEL: &str = "accel";
|
||||
@@ -209,10 +210,10 @@ impl ApConfigList {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::{env, fs};
|
||||
|
||||
use super::*;
|
||||
|
||||
const GOOD_CONFIGS: [&str; 8] = [
|
||||
"# good test 1
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
//! Collection of helper functions for pvapconfig
|
||||
//
|
||||
|
||||
use regex::Regex;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock";
|
||||
|
||||
/// For a given (sysfs) directory construct a list of all subdirs
|
||||
@@ -129,9 +130,10 @@ impl Drop for LockFile {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use utils::TemporaryDirectory;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Only very simple tests
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -12,6 +12,8 @@ mod config;
|
||||
mod helper;
|
||||
mod uv;
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
use ap::ApqnList;
|
||||
use cli::ARGS;
|
||||
use config::{ApConfigEntry, ApConfigList};
|
||||
@@ -19,7 +21,6 @@ use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
|
||||
use pv_core::ap::{self as pvap, Apqn};
|
||||
use pv_core::misc::encode_hex;
|
||||
use pv_core::uv::{ListableSecretType, SecretList};
|
||||
use std::process::ExitCode;
|
||||
use utils::print_version;
|
||||
|
||||
/// Simple macro for
|
||||
@@ -50,8 +51,8 @@ macro_rules! println_and_exit_failure {
|
||||
/// returns with exit failure.
|
||||
macro_rules! on_error_print_and_exit {
|
||||
($r:expr) => {
|
||||
if $r.is_err() {
|
||||
eprintln!("{}", $r.unwrap_err());
|
||||
if let Err(e) = $r {
|
||||
eprintln!("{}", e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
@@ -456,8 +457,10 @@ fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use pv_core::misc::decode_hex;
|
||||
use pv_core::uv::SecretEntry;
|
||||
|
||||
use super::*;
|
||||
use pv_core::{misc::decode_hex, uv::SecretEntry};
|
||||
|
||||
// This is more or less only a test for the do_ap_config() function
|
||||
// However, this is THE main functionality of the whole application.
|
||||
|
||||
@@ -9,14 +9,14 @@ rust-version.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.95", features = ["std"] }
|
||||
anyhow = { version = "1.0.102", features = ["std"] }
|
||||
base64 = "0.22.1"
|
||||
byteorder = "1.5"
|
||||
clap = { version ="4.5", features = ["derive", "wrap_help"]}
|
||||
curl = "0.4.47"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.70"
|
||||
serde = { version = "1.0.217", features = ["derive"]}
|
||||
clap = { version ="4.6", features = ["derive", "wrap_help"]}
|
||||
curl = "0.4.49"
|
||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.80"
|
||||
serde = { version = "1.0.228", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
zerocopy = { version="0.8", features = ["derive"] }
|
||||
@@ -25,8 +25,8 @@ pv = { path = "../pv", package = "s390_pv" }
|
||||
utils = { path = "../utils" }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version ="4.5", features = ["derive", "wrap_help"]}
|
||||
clap_complete = "4.5"
|
||||
clap = { version ="4.6", features = ["derive", "wrap_help"]}
|
||||
clap_complete = "4.6"
|
||||
log = { version = "0.4", features = ["std", "release_max_level_debug"] }
|
||||
|
||||
utils = { path = "../utils" }
|
||||
|
||||
@@ -365,6 +365,16 @@ Check whether the firmware is supported by IBM. Requires internet access.
|
||||
</ul>
|
||||
|
||||
|
||||
`--firmware-check-version <FIRMWARE_CHECK_VERSION>`
|
||||
<ul>
|
||||
Specify the firmware verification request version.
|
||||
Default value: '1'
|
||||
Possible values:
|
||||
- **1**: Use firmware verification API request version 1.0.
|
||||
- **2**: Use firmware verification API request version 2.0.
|
||||
</ul>
|
||||
|
||||
|
||||
`--firmware-verify-url <URL>`
|
||||
<ul>
|
||||
Specify the endpoint to use for firmware version verification. Use an endpoint
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
// it under the terms of the MIT license. See LICENSE for details.
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use clap::CommandFactory;
|
||||
use clap_complete::{generate_to, Shell};
|
||||
use std::env;
|
||||
use std::io::Error;
|
||||
|
||||
use clap::CommandFactory;
|
||||
use clap_complete::{generate_to, Shell};
|
||||
|
||||
include!("src/cli.rs");
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\"
|
||||
|
||||
.TH "PVATTEST-CHECK" "1" "2026-05-19" "s390-tools" "Attestation Manual"
|
||||
.TH "PVATTEST-CHECK" "1" "2026-06-23" "s390-tools" "Attestation Manual"
|
||||
.nh
|
||||
.ad l
|
||||
.SH NAME
|
||||
@@ -112,6 +112,20 @@ Required if add\-secret\-requests are specified.
|
||||
\-\-firmware
|
||||
.RS 4
|
||||
Check whether the firmware is supported by IBM. Requires internet access.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\-\-firmware\-check\-version, \-\-fw\-ver <FIRMWARE_CHECK_VERSION>
|
||||
.RS 4
|
||||
Specify the firmware verification request version.
|
||||
[default: '1']
|
||||
|
||||
Possible values:
|
||||
.RS 4
|
||||
\- \fB1\fP: Use firmware verification API request version 1.0.
|
||||
|
||||
\- \fB2\fP: Use firmware verification API request version 2.0.
|
||||
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::exchange::ExchangeFormatResponse;
|
||||
use std::fmt::Display;
|
||||
|
||||
use anyhow::Result;
|
||||
use pv::attest::{AdditionalData, AttestationFlags};
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use utils::HexSlice;
|
||||
|
||||
use crate::exchange::ExchangeFormatResponse;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AttestationResult<'a> {
|
||||
pub cuid: HexSlice<'a>,
|
||||
|
||||
@@ -58,7 +58,8 @@ pub enum Command {
|
||||
|
||||
/// Check if the attestation result matches defined policies.
|
||||
///
|
||||
/// After the attestation verification, check whether the attestation result complies with user-defined policies.
|
||||
/// After the attestation verification, check whether the attestation result complies with
|
||||
/// user-defined policies.
|
||||
Check(CheckOpt),
|
||||
|
||||
/// Print version information and exit.
|
||||
@@ -103,7 +104,8 @@ pub enum AttAddFlags {
|
||||
/// additional-data.
|
||||
PhkhAtt,
|
||||
|
||||
/// Request a hash over all successful Add-secret requests and the lock state as additional-data.
|
||||
/// Request a hash over all successful Add-secret requests and the lock state as
|
||||
/// additional-data.
|
||||
SecretStoreHash,
|
||||
|
||||
/// Request the state of the firmware as additional-data.
|
||||
@@ -214,6 +216,17 @@ pub enum OutputType {
|
||||
Yaml,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)]
|
||||
pub enum FirmwareCheckVersion {
|
||||
/// Use firmware check API version 1.0.
|
||||
#[default]
|
||||
#[value(name = "1")]
|
||||
V1,
|
||||
/// Use firmware check API version 2.0.
|
||||
#[value(name = "2")]
|
||||
V2,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct CheckOpt {
|
||||
/// Specify the attestation response to check whether the policies are validated.
|
||||
@@ -301,6 +314,16 @@ pub struct CheckOpt {
|
||||
#[arg(long)]
|
||||
pub firmware: bool,
|
||||
|
||||
/// Specify the firmware verification request version.
|
||||
#[arg(
|
||||
long,
|
||||
visible_alias("fw-ver"),
|
||||
requires("firmware"),
|
||||
value_enum,
|
||||
default_value_t
|
||||
)]
|
||||
pub firmware_check_version: FirmwareCheckVersion,
|
||||
|
||||
/// Specify the endpoint to use for firmware version verification.
|
||||
///
|
||||
/// Use an endpoint you trust. Requires the --firmware option.
|
||||
|
||||
@@ -6,28 +6,22 @@ mod firmware;
|
||||
mod host_key;
|
||||
mod secret_store;
|
||||
|
||||
use self::{
|
||||
firmware::firmware_check,
|
||||
host_key::{host_key_check, HostKeyCheck},
|
||||
secret_store::secret_store_check,
|
||||
secret_store::SecretStoreCheck,
|
||||
};
|
||||
use crate::{
|
||||
additional::AttestationResult,
|
||||
cli::{CheckOpt, CheckOptIO},
|
||||
exchange::ExchangeFormatResponse,
|
||||
};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, info, warn};
|
||||
use pv::{
|
||||
attest::AttestationRequest,
|
||||
misc::{create_file, open_file, read_file},
|
||||
};
|
||||
use pv::attest::AttestationRequest;
|
||||
use pv::misc::{create_file, open_file, read_file};
|
||||
use serde::Serialize;
|
||||
use std::process::ExitCode;
|
||||
use utils::HexSlice;
|
||||
|
||||
use self::firmware::{firmware_check_v1, firmware_check_v2};
|
||||
use self::host_key::{host_key_check, HostKeyCheck};
|
||||
use self::secret_store::{secret_store_check, SecretStoreCheck};
|
||||
use crate::additional::AttestationResult;
|
||||
use crate::cli::{CheckOpt, CheckOptIO, FirmwareCheckVersion};
|
||||
use crate::exchange::ExchangeFormatResponse;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
enum CheckState<T> {
|
||||
#[default]
|
||||
@@ -126,7 +120,10 @@ pub fn check(opt: &CheckOpt) -> Result<ExitCode> {
|
||||
let user_data = user_data_check(opt, &att_res)?.check(&mut issues);
|
||||
let secret_store = secret_store_check(opt, &att_res)?.check(&mut issues);
|
||||
|
||||
let firmware_check = firmware_check(opt, &att_res)?;
|
||||
let firmware_check = match opt.firmware_check_version {
|
||||
FirmwareCheckVersion::V1 => firmware_check_v1(opt, &att_res)?,
|
||||
FirmwareCheckVersion::V2 => firmware_check_v2(opt, &att_res)?,
|
||||
};
|
||||
let valid_firmware = match firmware_check {
|
||||
CheckState::None => None,
|
||||
CheckState::Data(_) => Some(true),
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::{fmt::Display, time::Duration};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use base64::prelude::*;
|
||||
@@ -11,44 +12,114 @@ use log::{debug, info, log_enabled};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{bail_check, CheckState};
|
||||
use crate::{additional::AttestationResult, cli::CheckOpt};
|
||||
use crate::additional::AttestationResult;
|
||||
use crate::cli::CheckOpt;
|
||||
|
||||
const CHECK_DEFAULT_ENDP: &str = "https://esupport.ibm.com/eccedge/ent/z";
|
||||
const VERIFY_API: &str = "hmrs/firmware/attestation/v1/verify";
|
||||
const TIMEOUT_MAX: Duration = Duration::from_secs(3);
|
||||
const TIMEOUT_MAX: Duration = Duration::from_secs(120);
|
||||
const USER_AGENT: &str = "s390-tools-pvattest";
|
||||
const CONTENT_TYPE: &str = "Content-Type: application/json";
|
||||
const CLIENT_ID: &str = "x-client-id: X";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Request {
|
||||
version: String,
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
enum Version {
|
||||
#[serde(rename = "1.0")]
|
||||
V1,
|
||||
#[serde(rename = "2.0")]
|
||||
V2,
|
||||
}
|
||||
|
||||
trait Request: Serialize + Debug {
|
||||
type Response: Response;
|
||||
|
||||
fn new(firmware_hash: &[u8]) -> Self;
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RequestV1_1 {
|
||||
version: Version,
|
||||
payload: String,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
const VERSION_ONE: &'static str = "1.0";
|
||||
impl Request for RequestV1_1 {
|
||||
type Response = ResponseV1;
|
||||
|
||||
fn new_v1(firmware_hash: &[u8]) -> Self {
|
||||
fn new(firmware_hash: &[u8]) -> Self {
|
||||
Self {
|
||||
version: Self::VERSION_ONE.to_string(),
|
||||
version: Version::V1,
|
||||
payload: BASE64_STANDARD.encode(firmware_hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RequestV1_2 {
|
||||
version: Version,
|
||||
payload: String,
|
||||
}
|
||||
|
||||
impl Request for RequestV1_2 {
|
||||
type Response = ResponseV2;
|
||||
|
||||
fn new(firmware_hash: &[u8]) -> Self {
|
||||
Self {
|
||||
version: Version::V1,
|
||||
payload: BASE64_STANDARD.encode(firmware_hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for firmware verification response types.
|
||||
///
|
||||
/// This trait defines the interface for handling responses from the IBM firmware
|
||||
/// verification API.
|
||||
trait Response: serde::de::DeserializeOwned + Debug + Display {
|
||||
/// The API version constant for this response type.
|
||||
const VERSION: Version;
|
||||
|
||||
/// Returns whether the firmware verification was successful.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `true` if the firmware is in a valid state, `false` otherwise.
|
||||
fn valid(&self) -> bool;
|
||||
|
||||
/// Constructs the verification API endpoint URL for this response version.
|
||||
///
|
||||
/// * `endp` - The base endpoint URL (e.g., "<https://esupport.ibm.com/eccedge/ent/z>")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The complete API endpoint URL for firmware verification, including the version path.
|
||||
fn verify_api(endp: &str) -> String {
|
||||
let ver = match Self::VERSION {
|
||||
Version::V1 => "v1",
|
||||
Version::V2 => "v2",
|
||||
};
|
||||
format!("{endp}/hmrs/firmware/attestation/{ver}/verify",)
|
||||
}
|
||||
}
|
||||
|
||||
// allow unused because all fields are provided by the REST API but may be unused by this toolk
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Response {
|
||||
version: String,
|
||||
struct ResponseV1 {
|
||||
version: Version,
|
||||
valid: bool,
|
||||
reference_id: String,
|
||||
#[serde(default)]
|
||||
reason: Option<String>,
|
||||
}
|
||||
|
||||
impl Display for Response {
|
||||
impl Response for ResponseV1 {
|
||||
const VERSION: Version = Version::V1;
|
||||
fn valid(&self) -> bool {
|
||||
self.valid
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ResponseV1 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
@@ -62,6 +133,44 @@ impl Display for Response {
|
||||
}
|
||||
}
|
||||
|
||||
// allow unused because all fields are provided by the REST API but may be unused by this toolk
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct VerifiedHashV2 {
|
||||
hash: String,
|
||||
signature: String,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ResponseV2 {
|
||||
version: Version,
|
||||
valid: bool,
|
||||
reason: String,
|
||||
verified_hashes: Vec<VerifiedHashV2>,
|
||||
}
|
||||
|
||||
impl Response for ResponseV2 {
|
||||
const VERSION: Version = Version::V2;
|
||||
fn valid(&self) -> bool {
|
||||
self.valid
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ResponseV2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"The firmware is {}in a valid state",
|
||||
if self.valid { "" } else { "not " }
|
||||
)?;
|
||||
let json = serde_json::to_string_pretty(self).map_err(|_| std::fmt::Error)?;
|
||||
f.write_str(&json)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Buf(Vec<u8>);
|
||||
impl Handler for Buf {
|
||||
@@ -71,10 +180,14 @@ impl Handler for Buf {
|
||||
}
|
||||
}
|
||||
|
||||
fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
|
||||
let req = serde_json::to_vec(&Request::new_v1(fw_hash.as_ref()))?;
|
||||
fn check<Q, U>(fw_hash: &U, endp: &str) -> Result<CheckState<()>>
|
||||
where
|
||||
Q: Request,
|
||||
U: AsRef<[u8]>,
|
||||
{
|
||||
let req = serde_json::to_vec(&Q::new(fw_hash.as_ref()))?;
|
||||
|
||||
let url = format!("{endp}/{VERIFY_API}");
|
||||
let url = Q::Response::verify_api(endp);
|
||||
debug!("POST {url}");
|
||||
|
||||
let mut http_header = List::new();
|
||||
@@ -103,7 +216,7 @@ fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
|
||||
);
|
||||
}
|
||||
|
||||
let resp: Response = match serde_json::from_slice(&handle.get_ref().0) {
|
||||
let resp: Q::Response = match serde_json::from_slice(&handle.get_ref().0) {
|
||||
Ok(res) => res,
|
||||
Err(e) => bail!(
|
||||
"Unexpected response from server: {} \n (\"{}\")",
|
||||
@@ -115,7 +228,7 @@ fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
|
||||
|
||||
debug!("Firmware check {resp:?}");
|
||||
|
||||
match resp.valid {
|
||||
match resp.valid() {
|
||||
true => info!("✓ {resp}"),
|
||||
false => bail_check!(&format!("{resp}")),
|
||||
}
|
||||
@@ -123,7 +236,10 @@ fn check<U: AsRef<[u8]>>(fw_hash: &U, endp: &str) -> Result<CheckState<()>> {
|
||||
Ok(CheckState::Data(()))
|
||||
}
|
||||
|
||||
pub fn firmware_check(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>> {
|
||||
fn firmware_check<Q>(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>>
|
||||
where
|
||||
Q: Request,
|
||||
{
|
||||
if !opt.firmware {
|
||||
return Ok(None.into());
|
||||
}
|
||||
@@ -138,7 +254,7 @@ pub fn firmware_check(opt: &CheckOpt, att_res: &AttestationResult) -> Result<Che
|
||||
.as_ref()
|
||||
.and_then(|add| add.firmware_state())
|
||||
{
|
||||
Some(hash) => check(hash, endp),
|
||||
Some(hash) => check::<Q, _>(hash, endp),
|
||||
None => {
|
||||
bail_check!(
|
||||
"The Attestation response contains no firmware hash, but checking was enabled"
|
||||
@@ -146,3 +262,144 @@ pub fn firmware_check(opt: &CheckOpt, att_res: &AttestationResult) -> Result<Che
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn firmware_check_v1(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>> {
|
||||
firmware_check::<RequestV1_1>(opt, att_res)
|
||||
}
|
||||
|
||||
pub fn firmware_check_v2(opt: &CheckOpt, att_res: &AttestationResult) -> Result<CheckState<()>> {
|
||||
firmware_check::<RequestV1_2>(opt, att_res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_request_v1_1() {
|
||||
let payload = BASE64_STANDARD.encode([42u8; 320]);
|
||||
let req = RequestV1_1::new(&[42u8; 320]);
|
||||
|
||||
assert!(matches!(req.version, Version::V1));
|
||||
assert_eq!(req.payload, payload);
|
||||
|
||||
let json = serde_json::to_string(&req).unwrap();
|
||||
let expected = format!(r#"{{"version":"1.0","payload":"{payload}"}}"#);
|
||||
assert_eq!(json, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_request_v1_2() {
|
||||
let payload = BASE64_STANDARD.encode([42u8; 320]);
|
||||
let req = RequestV1_2::new(&[42u8; 320]);
|
||||
|
||||
assert!(matches!(req.version, Version::V1));
|
||||
assert_eq!(req.payload, payload);
|
||||
|
||||
let json = serde_json::to_string(&req).unwrap();
|
||||
let expected = format!(r#"{{"version":"1.0","payload":"{payload}"}}"#);
|
||||
assert_eq!(json, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_response_v1() {
|
||||
let json = r#"{
|
||||
"version": "1.0",
|
||||
"valid": true,
|
||||
"referenceId": "ref-1",
|
||||
"reason": "string"
|
||||
}"#;
|
||||
|
||||
let resp: ResponseV1 = serde_json::from_str(json).unwrap();
|
||||
assert!(resp.valid());
|
||||
assert!(matches!(resp.version, Version::V1));
|
||||
assert_eq!(resp.reference_id, "ref-1");
|
||||
assert_eq!(resp.reason.as_deref(), Some("string"));
|
||||
|
||||
let display = resp.to_string();
|
||||
let expected = "The firmware is in a valid state\n Reason: string\n ReferenceId: ref-1";
|
||||
assert_eq!(display, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_response_v1_without_reason() {
|
||||
let json = r#"{
|
||||
"version": "1.0",
|
||||
"valid": true,
|
||||
"referenceId": "ref-1"
|
||||
}"#;
|
||||
|
||||
let resp: ResponseV1 = serde_json::from_str(json).unwrap();
|
||||
assert!(resp.valid());
|
||||
assert!(matches!(resp.version, Version::V1));
|
||||
assert_eq!(resp.reference_id, "ref-1");
|
||||
assert_eq!(resp.reason, None);
|
||||
|
||||
let display = resp.to_string();
|
||||
let expected = "The firmware is in a valid state";
|
||||
assert_eq!(display, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_response_v2() {
|
||||
let verified_hashes: Vec<_> = (0u8..4)
|
||||
.map(|i| {
|
||||
(
|
||||
BASE64_STANDARD.encode([42u8 + i; 256]),
|
||||
BASE64_STANDARD.encode([17u8 + i; 256]),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let json = format!(
|
||||
r#"{{
|
||||
"version": "2.0",
|
||||
"valid": true,
|
||||
"reason": "string",
|
||||
"verifiedHashes": [
|
||||
{{
|
||||
"hash": "{}",
|
||||
"signature": "{}"
|
||||
}},
|
||||
{{
|
||||
"hash": "{}",
|
||||
"signature": "{}"
|
||||
}},
|
||||
{{
|
||||
"hash": "{}",
|
||||
"signature": "{}"
|
||||
}},
|
||||
{{
|
||||
"hash": "{}",
|
||||
"signature": "{}"
|
||||
}}
|
||||
]
|
||||
}}"#,
|
||||
verified_hashes[0].0,
|
||||
verified_hashes[0].1,
|
||||
verified_hashes[1].0,
|
||||
verified_hashes[1].1,
|
||||
verified_hashes[2].0,
|
||||
verified_hashes[2].1,
|
||||
verified_hashes[3].0,
|
||||
verified_hashes[3].1,
|
||||
);
|
||||
|
||||
let resp: ResponseV2 = serde_json::from_str(&json).unwrap();
|
||||
assert!(resp.valid());
|
||||
assert!(matches!(resp.version, Version::V2));
|
||||
assert_eq!(resp.reason, "string");
|
||||
assert_eq!(resp.verified_hashes.len(), 4);
|
||||
|
||||
for (verified_hash, (hash, signature)) in
|
||||
resp.verified_hashes.iter().zip(verified_hashes.iter())
|
||||
{
|
||||
assert_eq!(&verified_hash.hash, hash);
|
||||
assert_eq!(&verified_hash.signature, signature);
|
||||
}
|
||||
|
||||
let display = resp.to_string();
|
||||
let expected = format!("The firmware is in a valid state\n{json}");
|
||||
assert_eq!(display, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, info};
|
||||
use pv::{
|
||||
misc::{read_certs, read_file},
|
||||
request::{openssl::DigestBytes, EcPubKeyCoord},
|
||||
};
|
||||
use pv::misc::{read_certs, read_file};
|
||||
use pv::request::openssl::DigestBytes;
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use serde::Serialize;
|
||||
use std::{fmt::Display, path::Path};
|
||||
use utils::HexSlice;
|
||||
|
||||
use super::CheckState;
|
||||
use crate::{
|
||||
additional::AttestationResult,
|
||||
cli::{CheckOpt, HostKeyCheckPolicy},
|
||||
};
|
||||
use crate::additional::AttestationResult;
|
||||
use crate::cli::{CheckOpt, HostKeyCheckPolicy};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum HkCheck {
|
||||
|
||||
@@ -6,13 +6,14 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use log::info;
|
||||
use openssl::hash::DigestBytes;
|
||||
use openssl::hash::{hash, MessageDigest};
|
||||
use pv::{misc::read_file, secret::AddSecretRequest};
|
||||
use openssl::hash::{hash, DigestBytes, MessageDigest};
|
||||
use pv::misc::read_file;
|
||||
use pv::secret::AddSecretRequest;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{bail_check, CheckState};
|
||||
use crate::{additional::AttestationResult, cli::CheckOpt};
|
||||
use crate::additional::AttestationResult;
|
||||
use crate::cli::CheckOpt;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SecretStoreCheck<'a> {
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{
|
||||
cli::{AttAddFlags, CreateAttOpt},
|
||||
exchange::{ExchangeFormatRequest, ExchangeFormatVersion},
|
||||
};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use log::{debug, warn};
|
||||
use pv::{
|
||||
attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion},
|
||||
misc::{create_file, write_file},
|
||||
request::{ReqEncrCtx, Request, SymKey, SymKeyType},
|
||||
};
|
||||
use std::process::ExitCode;
|
||||
use pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion};
|
||||
use pv::misc::{create_file, write_file};
|
||||
use pv::request::{HostKey, ReqEncrCtx, Request, SymKey, SymKeyType};
|
||||
|
||||
use crate::cli::{AttAddFlags, CreateAttOpt};
|
||||
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatVersion};
|
||||
|
||||
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
|
||||
let mut att_flags = AttestationFlags::default();
|
||||
@@ -39,7 +37,7 @@ pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
|
||||
opt.certificate_args
|
||||
.get_verified_hkds("attestation request")?
|
||||
.into_iter()
|
||||
.for_each(|k| arcb.add_hostkey(k));
|
||||
.for_each(|k| arcb.add_hostkey(HostKey::V1(k)));
|
||||
debug!("Added all host-keys");
|
||||
|
||||
let encr_ctx =
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{
|
||||
cli::PerformAttOptComb,
|
||||
exchange::{ExchangeFormatRequest, ExchangeFormatResponse, ExchangeFormatVersion},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use pv::{
|
||||
misc::{create_file, open_file, read_file},
|
||||
uv::{AttestationCmd, UvDevice},
|
||||
};
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::Result;
|
||||
use pv::misc::{create_file, open_file, read_file};
|
||||
use pv::uv::{AttestationCmd, UvDevice};
|
||||
|
||||
use crate::cli::PerformAttOptComb;
|
||||
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatResponse, ExchangeFormatVersion};
|
||||
|
||||
pub fn perform<'a, P>(opt: P) -> Result<ExitCode>
|
||||
where
|
||||
P: Into<PerformAttOptComb<'a>>,
|
||||
|
||||
@@ -2,22 +2,20 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, warn};
|
||||
use pv::{
|
||||
attest::{AttestationItems, AttestationMeasurement, AttestationRequest},
|
||||
misc::{create_file, open_file, read_exact_file, write_file},
|
||||
request::{openssl::pkey::PKey, BootHdrTags, Confidential, SymKey},
|
||||
};
|
||||
use std::process::ExitCode;
|
||||
use pv::attest::{AttestationItems, AttestationMeasurement, AttestationRequest};
|
||||
use pv::misc::{create_file, open_file, read_exact_file, write_file};
|
||||
use pv::request::openssl::pkey::PKey;
|
||||
use pv::request::{BootHdrTags, Confidential, SymKey};
|
||||
use utils::HexSlice;
|
||||
|
||||
use crate::{
|
||||
additional::AttestationResult,
|
||||
cli::{OutputType, VerifyOpt},
|
||||
exchange::ExchangeFormatResponse,
|
||||
EXIT_CODE_ATTESTATION_FAIL,
|
||||
};
|
||||
use crate::additional::AttestationResult;
|
||||
use crate::cli::{OutputType, VerifyOpt};
|
||||
use crate::exchange::ExchangeFormatResponse;
|
||||
use crate::EXIT_CODE_ATTESTATION_FAIL;
|
||||
|
||||
pub fn verify(opt: &VerifyOpt) -> Result<ExitCode> {
|
||||
let mut input = open_file(&opt.input)?;
|
||||
@@ -26,7 +24,7 @@ pub fn verify(opt: &VerifyOpt) -> Result<ExitCode> {
|
||||
let arpk = SymKey::Aes256(
|
||||
read_exact_file(&opt.arpk, "Attestation request protection key").map(Confidential::new)?,
|
||||
);
|
||||
let tags = BootHdrTags::from_se_image(&mut img)?;
|
||||
let (tags, _) = BootHdrTags::from_se_image(&mut img)?;
|
||||
let exchange = ExchangeFormatResponse::read(&mut input)?;
|
||||
|
||||
let (auth, conf) = AttestationRequest::decrypt_bin(exchange.arcb(), &arpk)?;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
#![allow(unused)]
|
||||
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use anyhow::{anyhow, bail, Error, Result};
|
||||
use pv::{assert_size, request::MagicValue, uv::AttestationCmd, uv::ConfigUid};
|
||||
use std::{
|
||||
io::{ErrorKind, Read, Seek, SeekFrom, Write},
|
||||
mem::size_of,
|
||||
};
|
||||
use zerocopy::ByteOrder;
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
|
||||
use pv::assert_size;
|
||||
use pv::request::MagicValue;
|
||||
use pv::uv::{AttestationCmd, ConfigUid};
|
||||
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
|
||||
|
||||
const INV_EXCHANGE_FMT_ERROR_TEXT: &str = "The input has not the correct format:";
|
||||
|
||||
@@ -583,9 +583,10 @@ mod test {
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use super::*;
|
||||
use pv::misc::read_file;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn exchange_from_slice() {
|
||||
let val = &[0; 17];
|
||||
|
||||
@@ -8,10 +8,11 @@ mod cli;
|
||||
mod cmd;
|
||||
mod exchange;
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use cli::{CliOptions, Command};
|
||||
use log::trace;
|
||||
use std::process::ExitCode;
|
||||
use utils::{print_cli_error, print_error, print_version, PvLogger};
|
||||
|
||||
use crate::cmd::{check, create, perform, verify, CMD_FN, UV_CMD_FN};
|
||||
|
||||
@@ -28,8 +28,8 @@ RemainAfterExit=yes
|
||||
FailureAction=poweroff-immediate
|
||||
# boot partition is unencrypted and contains SICS so we can get logs out this way
|
||||
# logs do not leek any sensitive information
|
||||
StandardOutput=console
|
||||
StandardError=console
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
|
||||
[Install]
|
||||
RequiredBy=sel-ebc.target
|
||||
|
||||
@@ -20,8 +20,8 @@ FailureAction=poweroff-immediate
|
||||
RemainAfterExit=yes
|
||||
|
||||
# logs do not leek any sensitive information
|
||||
StandardOutput=console
|
||||
StandardError=console
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
|
||||
[Install]
|
||||
RequiredBy=sel-ebc.target
|
||||
|
||||
@@ -29,8 +29,8 @@ RemainAfterExit=yes
|
||||
FailureAction=poweroff-immediate
|
||||
# boot partition is unencrypted and contains SICS so we can get logs out this way
|
||||
# logs do not leek any sensitive information
|
||||
StandardOutput=console
|
||||
StandardError=console
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
|
||||
[Install]
|
||||
RequiredBy=sel-ebc.target
|
||||
|
||||
@@ -8,15 +8,15 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
clap = { version = "4.6", features = ["derive"] }
|
||||
pv_core = { path = "../pv_core" , package = "s390_pv_core" }
|
||||
utils = { path = "../utils" }
|
||||
anyhow = { version = "1.0.95", features = ["std"] }
|
||||
zerocopy = { version = "0.8.27", features = ["derive"] }
|
||||
anyhow = { version = "1.0.102", features = ["std"] }
|
||||
zerocopy = { version = "0.8.48", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version ="4.5", features = ["derive"]}
|
||||
clap_complete = "4.5"
|
||||
clap = { version ="4.6", features = ["derive"]}
|
||||
clap_complete = "4.6"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
use std::{path::PathBuf, sync::OnceLock};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use clap::{ArgAction, Parser, ValueHint};
|
||||
|
||||
@@ -21,8 +22,8 @@ pub struct Cli {
|
||||
/// Specifies the toc.asr which is the root of the EBC resources
|
||||
///
|
||||
/// Specify the add-secret request file toc.asr that serves as the root of the EBC resources.
|
||||
/// Its user data must contain a reference to toc.pol as generated by pvsecret with the --policy
|
||||
/// option.
|
||||
/// Its user data must contain a reference to toc.pol as generated by pvsecret with the
|
||||
/// --policy option.
|
||||
#[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath)]
|
||||
pub toc: PathBuf,
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use pv_core::misc::open_file;
|
||||
use std::{io::Read, path::Path};
|
||||
|
||||
/// Length of the MAC tag in bytes (last 16 bytes of AddSecretRequest files)
|
||||
pub const MAC_TAG_LEN: usize = 16;
|
||||
@@ -20,7 +22,7 @@ pub fn get_reader(filepath: &Path) -> Result<Box<dyn Read>> {
|
||||
.with_context(|| format!("unable to get reader from {:?}", filepath))
|
||||
}
|
||||
|
||||
/// Read all data from a reader into a Vec<u8>
|
||||
/// Read all data from a reader into a `Vec<u8>`
|
||||
pub fn get_data(rd_in: &mut Box<dyn Read>) -> Result<Vec<u8>> {
|
||||
let mut data_in = Vec::new();
|
||||
rd_in
|
||||
|
||||
@@ -20,27 +20,23 @@
|
||||
mod cli;
|
||||
mod ebc_utils;
|
||||
|
||||
#[cfg(target_arch = "s390x")]
|
||||
use pv_core::uv::{AddCmd, UvDevice};
|
||||
#[cfg(target_arch = "s390x")]
|
||||
use utils::get_reader_from_cli_file_arg;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufRead, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::str::from_utf8;
|
||||
|
||||
use anyhow::{bail, Context, Error, Result};
|
||||
use clap::Parser;
|
||||
use pv_core::{
|
||||
misc::{decode_hex, encode_hex},
|
||||
PolicyReference,
|
||||
};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{BufRead, Read},
|
||||
path::{Path, PathBuf},
|
||||
process::ExitCode,
|
||||
str::from_utf8,
|
||||
};
|
||||
use pv_core::misc::{decode_hex, encode_hex};
|
||||
#[cfg(target_arch = "s390x")]
|
||||
use pv_core::uv::{AddCmd, UvDevice};
|
||||
use pv_core::PolicyReference;
|
||||
// Don't use openssl here because this tool is intended to run in the initramfs
|
||||
// phase of the boot and there we don't want to dynamically link against a C lib
|
||||
use sha2::{self, Digest};
|
||||
#[cfg(target_arch = "s390x")]
|
||||
use utils::get_reader_from_cli_file_arg;
|
||||
use zerocopy::TryFromBytes;
|
||||
|
||||
use crate::cli::Cli;
|
||||
|
||||
@@ -9,29 +9,29 @@ rust-version.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.95", features = ["std"] }
|
||||
anyhow = { version = "1.0.102", features = ["std"] }
|
||||
base64 = "0.22.1"
|
||||
clap = { version ="4.5", features = ["derive", "wrap_help"]}
|
||||
clap = { version ="4.6", features = ["derive", "wrap_help"]}
|
||||
deku = "0.18"
|
||||
deku_derive = "0.18"
|
||||
enum_dispatch = "0.3.13"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.70"
|
||||
serde = { version = "1.0.217", features = ["derive"]}
|
||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.80"
|
||||
serde = { version = "1.0.228", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
thiserror = "2.0.11"
|
||||
thiserror = "2.0.18"
|
||||
|
||||
pv = { path = "../pv", package = "s390_pv" }
|
||||
utils = { path = "../utils" }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { version = "1.0.95", features = ["std"] }
|
||||
clap = { version ="4.5", features = ["derive", "wrap_help"]}
|
||||
clap_complete = "4.5"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
anyhow = { version = "1.0.102", features = ["std"] }
|
||||
clap = { version ="4.6", features = ["derive", "wrap_help"]}
|
||||
clap_complete = "4.6"
|
||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
|
||||
pv = { path = "../pv", package = "s390_pv" }
|
||||
utils = { path = "../utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.6"
|
||||
proptest = "1.11"
|
||||
|
||||
@@ -3,27 +3,21 @@
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
#![allow(missing_docs)]
|
||||
use std::{
|
||||
fmt::Display,
|
||||
fs::{File, OpenOptions},
|
||||
io::{BufReader, Read, Write},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use clap::{Parser, ValueHint};
|
||||
use log::{info, warn};
|
||||
use pv::{
|
||||
misc::{decode_hex, open_file, read_certs, read_file, try_parse_u64},
|
||||
request::SymKeyType,
|
||||
Error as PvError, Result,
|
||||
};
|
||||
use pvimg::{
|
||||
misc::PSW,
|
||||
secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder},
|
||||
uvdata::{BuilderTrait, SeHdrBuilder, SeHdrVersion},
|
||||
};
|
||||
use pv::misc::{decode_hex, open_file, read_certs, read_file, try_parse_u64};
|
||||
use pv::request::SymKeyType;
|
||||
use pv::{Error as PvError, Result};
|
||||
use pvimg::misc::PSW;
|
||||
use pvimg::secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder};
|
||||
use pvimg::uvdata::{BuilderTrait, SeHdrBuilder, SeHdrVersion};
|
||||
use utils::{AtomicFile, AtomicFileOperation, HexSlice, PvLogger, VerbosityOptions};
|
||||
|
||||
/// Converts the hexstring into a byte vector.
|
||||
@@ -182,7 +176,7 @@ fn main() -> anyhow::Result<()> {
|
||||
let mut secure_comp_builer = SecuredComponentBuilder::new_v1(false)?;
|
||||
|
||||
// Sort components by address in ascending order
|
||||
args.components.sort_by(|a, b| a.addr.cmp(&b.addr));
|
||||
args.components.sort_by_key(|a| a.addr);
|
||||
for component_arg in args.components {
|
||||
info!("## Preparing {}", component_arg);
|
||||
let mut comp = Comp {
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{fs::File, io::Write, path::PathBuf};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::{Parser, ValueEnum, ValueHint};
|
||||
use log::info;
|
||||
use pv::{
|
||||
misc::{open_file, parse_hex, read_file},
|
||||
request::SymKey,
|
||||
};
|
||||
use pvimg::{
|
||||
error::Result,
|
||||
uvdata::{
|
||||
KeyExchangeTrait, SeHdr, SeHdrBinV1, SeHdrData, SeHdrDataV1, SeHdrVersioned,
|
||||
UvDataPlainTrait, UvDataTrait,
|
||||
},
|
||||
use pv::misc::{open_file, parse_hex, read_file};
|
||||
use pv::request::SymKey;
|
||||
use pvimg::error::Result;
|
||||
use pvimg::uvdata::{
|
||||
KeyExchangeTrait, SeHdr, SeHdrBinV1, SeHdrData, SeHdrDataV1, SeHdrVersioned, UvDataPlainTrait,
|
||||
UvDataTrait,
|
||||
};
|
||||
use utils::{PvLogger, VerbosityOptions};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user