mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Use Self instead of the struct name whenever possible. Automagically replace struct name with Self: `cargo clippy --fix -- -W clippy::use_self` This streamlines the code. Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com> Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
393 lines
13 KiB
Rust
393 lines
13 KiB
Rust
// SPDX-License-Identifier: MIT
|
|
//
|
|
// Copyright IBM Corp. 2023
|
|
//
|
|
//! Functions around handling the pvapconfig configuration file
|
|
//
|
|
|
|
use openssl::sha::sha256;
|
|
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";
|
|
|
|
const RE_EP11_MKVP_32: &str = r"^(0x)?([[:xdigit:]]{32})$";
|
|
const RE_EP11_MKVP_64: &str = r"^(0x)?([[:xdigit:]]{64})$";
|
|
const RE_SERIALNR: &str = r"^(\S{16})$";
|
|
const RE_EP11_GEN: &str = r"^cex(8)$";
|
|
const RE_ACCEL_GEN: &str = r"^cex([4-8])$";
|
|
const RE_SECRETID: &str = r"^(0x)?([[:xdigit:]]{64})$";
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
|
#[serde(default, deny_unknown_fields)]
|
|
pub struct ApConfigEntry {
|
|
pub name: String, // name and description are unmodified from the config file
|
|
pub description: String, // accel after validation ep11 after validation
|
|
pub mode: String, // "accel" "ep11"
|
|
pub mkvp: String, // empty 32 hex lowercase characters
|
|
pub serialnr: String, // empty empty or 16 non-whitespace characters
|
|
pub mingen: String, // empty or "cex4"..."cex8" empty or "cex8"
|
|
pub secretid: String, // empty 64 hex lowercase characters
|
|
}
|
|
|
|
impl ApConfigEntry {
|
|
fn validate_secretid(&mut self) -> Result<(), String> {
|
|
// either secret id or name may be given
|
|
if self.secretid.is_empty() && self.name.is_empty() {
|
|
return Err("Neither secretid nor name given.".to_string());
|
|
}
|
|
// if name is given, calculate sha256 digest for this name
|
|
// test for the hash calculated here can be done with openssl:
|
|
// echo -n "Hello" >in.bin; openssl dgst -sha256 -binary -out out.bin in.bin; hexdump -C
|
|
// out.bin
|
|
if self.name.is_empty() {
|
|
return Ok(());
|
|
}
|
|
let hash = sha256(self.name.as_bytes());
|
|
let hashstr = crate::helper::u8_to_hexstring(&hash);
|
|
// if there is a secretid given, this must match to the hash
|
|
if !self.secretid.is_empty() {
|
|
if self.secretid != hashstr {
|
|
return Err("Mismatch between sha256(name) and secretid.".to_string());
|
|
}
|
|
} else {
|
|
self.secretid = hashstr;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// # Panics
|
|
/// Panics if the compilation of a static regular expression fails.
|
|
fn validate_ep11_entry(&mut self) -> Result<(), String> {
|
|
// mkvp is required
|
|
let mut mkvp = self.mkvp.trim().to_lowercase();
|
|
if mkvp.is_empty() {
|
|
return Err("Mkvp value missing.".to_string());
|
|
}
|
|
// either 64 hex or 32 hex
|
|
if Regex::new(RE_EP11_MKVP_64).unwrap().is_match(&mkvp) {
|
|
// need to cut away the last 32 hex characters
|
|
mkvp = String::from(&mkvp[..mkvp.len() - 32])
|
|
} else if Regex::new(RE_EP11_MKVP_32).unwrap().is_match(&mkvp) {
|
|
// nothing to do here
|
|
} else {
|
|
return Err(format!("Mkvp value '{}' is not valid.", &self.mkvp));
|
|
}
|
|
self.mkvp = match mkvp.strip_prefix("0x") {
|
|
Some(rest) => String::from(rest),
|
|
None => mkvp,
|
|
};
|
|
// serialnr is optional
|
|
let serialnr = self.serialnr.trim().to_string();
|
|
if !serialnr.is_empty() && !Regex::new(RE_SERIALNR).unwrap().is_match(&serialnr) {
|
|
return Err(format!("Serialnr value '{}' is not valid.", &self.serialnr));
|
|
}
|
|
self.serialnr = serialnr;
|
|
// mingen is optional, but if given only CEX8 is valid
|
|
let mingen = self.mingen.trim().to_lowercase();
|
|
if !mingen.is_empty() && !Regex::new(RE_EP11_GEN).unwrap().is_match(&mingen) {
|
|
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
|
|
}
|
|
self.mingen = mingen;
|
|
// secretid or name is required
|
|
let secretid = self.secretid.trim().to_lowercase();
|
|
if !secretid.is_empty() && !Regex::new(RE_SECRETID).unwrap().is_match(&secretid) {
|
|
return Err(format!("Secretid value '{}' is not valid.", &self.secretid));
|
|
}
|
|
self.secretid = match secretid.strip_prefix("0x") {
|
|
Some(rest) => String::from(rest),
|
|
None => secretid,
|
|
};
|
|
// name is optional, ignored here
|
|
// description is optional, ignored here
|
|
// but the secretid needs some more validation
|
|
self.validate_secretid()
|
|
}
|
|
|
|
/// # Panics
|
|
/// Panics if the compilation of a static regular expression fails.
|
|
fn validate_accel_entry(&mut self) -> Result<(), String> {
|
|
// mkvp is ignored
|
|
self.mkvp.clear();
|
|
// serialnr is ignored
|
|
self.serialnr.clear();
|
|
// mingen is optional, but if given must match to CEX4..CEX8
|
|
let mingen = self.mingen.trim().to_lowercase();
|
|
if !mingen.is_empty() && !Regex::new(RE_ACCEL_GEN).unwrap().is_match(&mingen) {
|
|
return Err(format!("Mingen value '{}' is not valid.", &self.mingen));
|
|
}
|
|
self.mingen = mingen;
|
|
// secretid is ignored
|
|
self.secretid.clear();
|
|
// name is optional, ignored here
|
|
// description is optional, ignored here
|
|
Ok(())
|
|
}
|
|
|
|
fn validate(&mut self) -> Result<(), String> {
|
|
// trim name
|
|
self.name = self.name.trim().to_string();
|
|
// mode is always required
|
|
let mode = self.mode.trim().to_lowercase();
|
|
match mode.as_str() {
|
|
STR_MODE_EP11 => {
|
|
self.mode = mode;
|
|
self.validate_ep11_entry()?;
|
|
}
|
|
STR_MODE_ACCEL => {
|
|
self.mode = mode;
|
|
self.validate_accel_entry()?;
|
|
}
|
|
_ => return Err(format!("Unknown or invalid mode '{}'.", mode)),
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Wrapper object around Vector of ApConfigEntry
|
|
pub struct ApConfigList(Vec<ApConfigEntry>);
|
|
|
|
impl ApConfigList {
|
|
#[cfg(test)] // only used in test code
|
|
pub fn from_apconfigentry_vec(apconfigs: Vec<ApConfigEntry>) -> Self {
|
|
Self(apconfigs)
|
|
}
|
|
|
|
pub fn iter(&self) -> Iter<'_, ApConfigEntry> {
|
|
self.0.iter()
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.0.len()
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.0.is_empty()
|
|
}
|
|
|
|
fn read_yaml_file(fname: &str) -> Result<Vec<ApConfigEntry>, String> {
|
|
let file = match File::open(fname) {
|
|
Ok(f) => f,
|
|
Err(err) => {
|
|
return Err(format!(
|
|
"Failure to open AP config file {}: {:?}",
|
|
fname, err
|
|
))
|
|
}
|
|
};
|
|
match serde_yaml::from_reader(file) {
|
|
Ok(cfg) => Ok(cfg),
|
|
Err(err) => Err(format!(
|
|
"Failure parsing AP config file {}: {:?}",
|
|
fname, err
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn validate(config: &mut [ApConfigEntry]) -> Result<(), String> {
|
|
for (i, entry) in config.iter_mut().enumerate() {
|
|
let ename = if !entry.name.trim().is_empty() {
|
|
format!("AP config entry {} '{}'", i, entry.name.trim())
|
|
} else {
|
|
format!("AP config entry {}", i)
|
|
};
|
|
if let Err(err) = &entry.validate() {
|
|
return Err(format!("{}: {}", ename, err));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Read in and validate the yaml configuration from a file.
|
|
/// Returns a Result with Ok(ApConfigList) on success
|
|
/// or an Err(errorstring) on failure.
|
|
pub fn read_and_validate_yaml_file(fname: &str) -> Result<Self, String> {
|
|
let mut apconfig = Self::read_yaml_file(fname)?;
|
|
Self::validate(&mut apconfig)?;
|
|
Ok(Self(apconfig))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::io::Write;
|
|
|
|
const GOOD_CONFIGS: [&str; 8] = [
|
|
"# good test 1
|
|
- name: my Accelerator
|
|
mode: AcCel
|
|
mingen: Cex7\n",
|
|
"# good test 2
|
|
- name: my Accelerator 2
|
|
description: Accelerator entry with description
|
|
mode: Accel\n",
|
|
"# good test 3
|
|
- name: my EP11 APQN 1
|
|
mode: Ep11
|
|
mkvp: 0xDB3C3B3C3F097DD55EC7EB0E7FDBCB93
|
|
serialnr: 93AADFK719460083
|
|
secretid: 0xBC9d46c052BC3574454C5715757274629a283767ed237922cfb8651c0e77320A\n",
|
|
"# good test 4
|
|
- name: my EP11 APQN 2
|
|
mode: EP11
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
|
serialnr: 93aaDHzu42082261
|
|
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
|
|
"# good test 5
|
|
- name: my EP11 APQN 3
|
|
mode: EP11
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93db3c3b3c3f097dd55ec7eb0e7fdbcb93
|
|
serialnr: 93aaDHzu42082261
|
|
secretid: 0xd146c9ae77cdff25fa87a5b3487587dc29a4e391b315c98570e8fa2e2ec91454\n",
|
|
"# no name but secretid given
|
|
- mode: EP11
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
|
"# no secretid but name given
|
|
- mode: EP11
|
|
name: My-EP11-AP-config
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93\n",
|
|
"# secretid and name given
|
|
- mode: EP11
|
|
name: My-EP11-AP-config
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
|
];
|
|
|
|
const BAD_CONFIGS: [&str; 12] = [
|
|
"# mode missing
|
|
- name: bad test 1
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
|
"# invalid mode
|
|
- name: bad test 2
|
|
mode: CCA
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93
|
|
secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n",
|
|
"# Accelerator with wrong CEX3
|
|
- name: bad test 3
|
|
mode: Accel
|
|
mingen: Cex3\n",
|
|
"# Accelerator with wrong CEX9
|
|
- name: bad test 4
|
|
mode: Accel
|
|
mingen: CEX9\n",
|
|
"# EP11 with mkvp missing
|
|
- name: bad test 5
|
|
mode: EP11
|
|
serialnr: 93AADHZU42082261\n",
|
|
"# EP11 with non hex mkvp
|
|
- name: bad test 6
|
|
mode: EP11
|
|
mkvp: 0xabcdefghijklmnopqqponmlkjihgfedcba
|
|
serialnr: 93AADHZU42082261\n",
|
|
"# EP11 with mkvp too big
|
|
- name: bad test 7
|
|
mode: EP11
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93aa
|
|
serialnr: 93AADHZU42082261\n",
|
|
"# EP11 with mkvp too small
|
|
- name: bad test 8
|
|
mode: EP11
|
|
mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb
|
|
serialnr: 93AADHZU42082261\n",
|
|
"# EP11 with invalid CEXx
|
|
- name: bad test 9
|
|
mode: EP11
|
|
mingen: CEX7
|
|
mkvp: 0x00112233445566778899aabbccddeeff
|
|
serialnr: 93AADHZU42082261\n",
|
|
"# EP11 with invalid Serialnr
|
|
- name: bad test 10
|
|
mode: EP11
|
|
mkvp: 0x00112233445566778899aabbccddeeff
|
|
serialnr: 93AADHZU4208226\n",
|
|
"# EP11 with invalid Serialnr
|
|
- name: bad test 11
|
|
mode: EP11
|
|
mkvp: 0x00112233445566778899aabbccddeeff
|
|
serialnr: 93AAD ZU42082261\n",
|
|
"# EP11 with sha256(name) != secretid
|
|
- name: bad test 12
|
|
mode: EP11
|
|
mkvp: 0x00112233445566778899aabbccddeeff
|
|
serialnr: AABBCCDDEEFFGGHH
|
|
secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n",
|
|
];
|
|
|
|
const BAD_DESERIALIZE: [&str; 2] = [
|
|
"/*\ntotal nonsense\n */\n",
|
|
"# wrong/unknown field
|
|
- name: de-serialize failure 1
|
|
type: EP11\n",
|
|
];
|
|
|
|
fn write_yaml_config_to_temp_file(content: &str) -> Result<String, String> {
|
|
let dir = env::temp_dir();
|
|
let rnd = rand::random::<u32>();
|
|
let fname = format!("{}/config-test-{}.yaml", dir.to_str().unwrap(), rnd);
|
|
let mut f = match File::create(&fname) {
|
|
Ok(f) => f,
|
|
Err(_) => return Err(format!("Failure creating temp file '{fname}'.")),
|
|
};
|
|
match f.write_all(content.as_bytes()) {
|
|
Ok(_) => Ok(fname),
|
|
Err(_) => {
|
|
fs::remove_file(&fname).ok();
|
|
Err(format!("Failure writing to temp file '{fname}'."))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_good_yaml() {
|
|
for yaml in GOOD_CONFIGS {
|
|
let f = write_yaml_config_to_temp_file(yaml).unwrap();
|
|
let config = ApConfigList::read_and_validate_yaml_file(&f).unwrap();
|
|
assert!(!config.is_empty());
|
|
fs::remove_file(&f).ok();
|
|
}
|
|
}
|
|
#[test]
|
|
fn test_bad_yaml() {
|
|
for yaml in BAD_CONFIGS {
|
|
let f = write_yaml_config_to_temp_file(yaml).unwrap();
|
|
let r = ApConfigList::read_and_validate_yaml_file(&f);
|
|
assert!(r.is_err());
|
|
fs::remove_file(&f).ok();
|
|
}
|
|
}
|
|
#[test]
|
|
fn test_invalid_deserizalize() {
|
|
for yaml in BAD_DESERIALIZE {
|
|
let f = write_yaml_config_to_temp_file(yaml).unwrap();
|
|
let r = ApConfigList::read_and_validate_yaml_file(&f);
|
|
assert!(r.is_err());
|
|
fs::remove_file(&f).ok();
|
|
}
|
|
}
|
|
#[test]
|
|
fn test_sha256() {
|
|
assert!(
|
|
crate::helper::u8_to_hexstring(&sha256("Hello".as_bytes()))
|
|
== "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"
|
|
);
|
|
assert!(
|
|
crate::helper::u8_to_hexstring(&sha256("SECRET1".as_bytes()))
|
|
== "03153249db7ce46b0330ffb1a760b59710531af08ec4d7f8424a6870fae49360"
|
|
);
|
|
assert!(
|
|
crate::helper::u8_to_hexstring(&sha256("SECRET2".as_bytes()))
|
|
== "258499e710e0bd3bb878d6bac7e478b30f3f3e72566989f638c4143d14f6c0b6"
|
|
);
|
|
}
|
|
}
|