diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 8248ecab..eccb998f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -160,6 +160,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "cpacfinfo" +version = "0.1.0" +dependencies = [ + "anyhow", + "cc", + "clap", + "lazy_static", + "libc", + "serde", + "serde_json", + "utils", + "zerocopy", +] + [[package]] name = "curl" version = "0.4.44" @@ -600,6 +615,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_test" version = "1.0.176" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0657649a..b4249086 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "cpacfinfo", "pv", "pv_core", "pvapconfig", diff --git a/rust/Makefile b/rust/Makefile index 03858a97..c61ece8b 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -17,6 +17,12 @@ ifneq (${HAVE_CARGO},0) INSTALL_TARGETS := install-rust-tools install-man CARGO_TEST_TARGETS = $(addsuffix .test, $(CARGO_TARGETS)) +ifeq ($(HOST_ARCH),s390x) + CARGO_TARGETS += cpacfinfo +else + BUILD_TARGETS += skip-cpacfinfo +endif #HOSTARCH + ifneq (${HAVE_OPENSSL},0) ifneq (${HAVE_LIBCURL},0) PV_TARGETS := pvsecret pvattest @@ -65,6 +71,9 @@ skip-pv-build: skip-pvapconfig: echo " SKIP pvapconfig due to unsupported architecture (s390x only)" +skip-cpacfinfo: + echo " SKIP cpacfinfo due to unsupported architecture (s390x only)" + all: $(BUILD_TARGETS) install: $(INSTALL_TARGETS) $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) diff --git a/rust/cpacfinfo/Cargo.toml b/rust/cpacfinfo/Cargo.toml new file mode 100644 index 00000000..1f58bcab --- /dev/null +++ b/rust/cpacfinfo/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cpacfinfo" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow = "1.0.71" +clap = { version = "4.1", features = ["derive"] } +libc = "0.2" +serde = { version = "1.0.164", features = ["derive"] } +serde_json = "1.0" +utils = { path = "../utils" } +zerocopy = { version="0.7", features = ["derive"] } + +[build-dependencies] +cc = "1.0" +clap = { version ="4.1", features = ["derive"]} +lazy_static = "1.1" diff --git a/rust/cpacfinfo/build.rs b/rust/cpacfinfo/build.rs new file mode 100644 index 00000000..6112ba34 --- /dev/null +++ b/rust/cpacfinfo/build.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +fn main() { + #[cfg(target_arch = "s390x")] + cc::Build::new().file("src/stfle.c").compile("stfle"); + #[cfg(not(target_arch = "s390x"))] + { + println!("cargo:warning=cpacfinfo will have no functionality on non s390x architectures!"); + cc::Build::new().file("src/noop.c").compile("stfle"); + } + println!("cargo:rerun-if-changed=src/stfle.c") +} diff --git a/rust/cpacfinfo/man/cpacfinfo.1 b/rust/cpacfinfo/man/cpacfinfo.1 new file mode 100644 index 00000000..3ca2b882 --- /dev/null +++ b/rust/cpacfinfo/man/cpacfinfo.1 @@ -0,0 +1,215 @@ +.\" Copyright 2024, 2024 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH CPACFINFO 1 "AUG 2024" "s390-tools" +.SH NAME +.B cpacfinfo +\- tool to provide information about CPACF + +.SH SYNOPSIS +.B cpacfinfo +[OPTIONS] [,...] [--format ] + +.SH DESCRIPTION +.B cpacfinfo +is able to provide information about the MSA levels supported by the system as well as which +CPACF instructions and functions are available. + +.SH OPTIONS +.TP 8 +.B \-m/\-\-msa +Displays which MSA levels are enabled and how many functions of the ones introduced by +this level are available. Can be combined with +.B \-f/\-\-functions +to list all functions under the corresponding MSA level. Can be combined with +.B \-i/\-\-instructions +to provide only specific instructions. Can be combined with +.B \-a/\-\-available -n/\-\-not-available +to filter the displayed function. Ignores option +.B \-q/\-\-quiet. +Can be combined with +.B \-\-format +to convert to JSON format. + +.TP 8 +.B \-f/\-\-functions +Displays subfunctions of CPACF instructions. Can be combined with +.B \-a/\-\-available +and +.B \-n/\-\-not-available +to filter what functions are displayed. By default +.B \-f/\-\-functions +will only provide information about available functions. Therefore +.B cpacfinfo \-f/\-\-functions +and +.B cpacfinfo \-f/\-\-functions \-\-available +show the same output. Use options +.B \-f/\-\-functions \-a/\-\-available \-n/\-\-not-available +together to show information about every function known by the tool. Additionally +.B cpacfinfo +shows available functions not known to the tool as "UNKNOWN". + +.TP 8 +.B \-i/\-\-instructions ,... +Restricts output of CPACF instructions to be listed. +Multiple instructions can be supplied separated by "," to only show the supplied +instructions in the output. Other options like +.B \-\-quiet \-f/\-\-functions \-m/\-\-msa +or filters like +.B \-a/\-\-available \-n/\-\-not-available +can still be used to further limit output. + +.nr PI 2n +Possible values for +.B +: +.RS +.IP \[bu] 2 +.B km +: introduced with MSA + +.IP \[bu] 2 +.B kmc +: introduced with MSA + +.IP \[bu] 2 +.B kimd +: introduced with MSA + +.IP \[bu] 2 +.B klmd +: introduced with MSA + +.IP \[bu] 2 +.B kmac +: introduced with MSA + +.IP \[bu] 2 +.B pckmo +: introduced with MSA 3 + +.IP \[bu] 2 +.B kmf +: introduced with MSA 4 + +.IP \[bu] 2 +.B kmctr +: introduced with MSA 4 + +.IP \[bu] 2 +.B kmo +: introduced with MSA 4 + +.IP \[bu] 2 +.B pcc +: introduced with MSA 4 + +.IP \[bu] 2 +.B prno +: introduced with MSA 5 + +.IP \[bu] 2 +.B kma +: introduced with MSA 8 + +.IP \[bu] 2 +.B kdsa +: introduced with MSA 9 +.RE + +.TP 8 +.B \-a/\-\-available +Displays the CPACF subfunctions available on the system. +.B cpacfinfo \-f/\-\-functions +is the same as +.B cpacfinfo \-f/\-\-functions \-a/\-\-available +. If +.B -n/\-\-not-available +is specified no available functions are shown. To get available as well as functions not available use +.B cpacfinfo \-f/\-\-functions \-a/\-\-available \-n/\-\-not-available. + +.TP 8 +.B \-n/\-\-not-available +Displays the CPACF subfunctions known by the tool that are not-available on the system. +Depending on the Hardware not all MSA levels might be available which can be checked +with the +.B \-m/\-\-msa +option. In such cases it might not be obvious which functions of which instructions are available. +This option together with the +.B \-a/\-\-available +option shows all possible functions. + +.TP 8 +.B \-q/\-\-quiet +Suppresses the output of the Query Authentication Information. +By default +.B cpacfinfo +outputs the Query Authentication Information for every +Instruction. To keep outputs of other options clean and minimal this can be disabled +with this option. +On machines that do not provide /sys/devices/system/cpu/cpacf/ output will always look like this +option is supplied. + +.TP 8 +.B \-\-format +Format output in the specified format. [default: 'human'] + +If option +.B \-m/\-\-msa +is specified the JSON output will equal to +.B cpacfinfo -m +without any other specified options. If +.B \-m/\-\-msa +is NOT specified the JSON output will equal to +.B cpacfinfo -f +without any other specified options. + +.nr PI 2n +Possible values for +.B +: +.RS +.IP \[bu] 2 +.B human +: Use human readable format. + +.IP \[bu] 2 +.B json +: Use JSON format. +.RE + +.TP 8 +.B \-v/\-\-version +Print version information and exit. + +.TP 8 +.B \-h/\-\-help +Print help (see a summary with \-h') + +.SH Query Authentication Information +The Query Authentication Information is available since MSA 13 and if it is available +.B cpacfinfo +will display it by default. Query Authentication Information is available for every CPACF +instruction and contains the following: +.nr PI 2n +.RS +.IP \[bu] 2 +.B Format +: This format specifies how the binary blob read from sysfs is to be parsed and says nothing +about the Query Authentication Information itself. + +.IP \[bu] 2 +.B Hash length +: The hash length specifies the length of the hash in bytes. + +.IP \[bu] 2 +.B IFCL version +: The IFCL version specifies the instruction's firmware code level. + +.IP \[bu] 2 +.B Hash +: The hash of the firmware code level of the corresponding instruction. +.RE + diff --git a/rust/cpacfinfo/src/cli.rs b/rust/cpacfinfo/src/cli.rs new file mode 100644 index 00000000..fff85aaf --- /dev/null +++ b/rust/cpacfinfo/src/cli.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use clap::Parser; + +use crate::msa::InstructionKind; + +#[derive(Clone, PartialEq, clap::ValueEnum, Default)] +pub enum Format { + /// Human-focused, non-parsable output format + #[default] + Human, + + /// Use JSON format + Json, +} + +/// Command line interface to get information about CP Assist for Cryptographic Functions (CPACF) +#[derive(Parser)] +pub struct Cli { + /// Print version information and exit + #[arg(short, long, exclusive(true))] + pub version: bool, + + /// Provide information about the Message Security Assist (MSA) + /// + /// Shows which MSA levels are available and how many functions of the ones introduced by + /// this level are available. + /// Compatible with option -f/--functions to list all functions under the corresponding MSA + /// level. + #[arg(short, long, conflicts_with("quiet"))] + pub msa: bool, + + /// Shows available functions sorted by instructions + /// + /// Provides information about the subfunctions of an instruction. + /// Functions not known to cpacfinfo are displayed as "UNKNOWN". + #[arg(short, long)] + pub functions: bool, + + /// Filter instructions to provide in output + /// + /// Multiple instructions can be supplied separated by "," to only show the supllied + /// instructions in the output. + #[arg(short, long, num_args = 1.., value_delimiter = ',')] + pub instructions: Vec, + + /// Shows available functions + /// + /// Adds available functions to subfunction output. + #[arg(short, long)] + pub available: bool, + + /// Shows functions that are not available + /// + /// Adds non-available functions to subfunction output. + #[arg(short, long = "not-available")] + pub not_available: bool, + + /// Suppresses the Query Authentication Information output of Instructions + /// + /// By default cpacfinfo outputs the Query Authentication Information for every Instruction. + /// To keep outputs of other options clean and minimal this can be disabled with this + /// option. + #[arg(short, long)] + pub quiet: bool, + + /// Converts human readable output to JSON format + /// + /// Default is human to produce human readable output. When set to json will produce json + /// output. + #[arg(long, value_enum, default_value_t)] + pub format: Format, +} diff --git a/rust/cpacfinfo/src/main.rs b/rust/cpacfinfo/src/main.rs new file mode 100644 index 00000000..24ab5fce --- /dev/null +++ b/rust/cpacfinfo/src/main.rs @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +mod cli; +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 utils::print_version; + +use crate::cli::{Cli, Format}; +use crate::msa::*; +use crate::query::*; +use crate::stfle::*; + +/// producing -m/--msa output +fn out_msa(args: &Cli, levels: &Vec, instructions: &Vec) { + // produce json output + if args.format == Format::Json { + println!("{}", serde_json::to_string(levels).unwrap()); + return; + } + + // produce human readable output + for lvl in levels { + // print current level + println!("{lvl}"); + + // if -f/--functions is not specified continue to next level + if !args.functions { + continue; + } + + // print all functions introduced by the current level sorted by instruction + for ins in instructions { + // skip instructions for which the current level introduces no new functions + if !args.instructions.contains(&ins.kind) && !args.instructions.is_empty() + || !ins.info.available + { + continue; + } + + // filter all functions that do not fit the command line arguments + let funcs_to_be_printed = ins + .funcs + .iter() + .filter(|func| { + !(!func.available && !args.not_available + || func.available && !args.available && args.not_available) + }) + .filter(|func| func.msa == lvl.msa_level); + + // print all functions matching the command line arguments + let mut ins_printed = false; + for func in funcs_to_be_printed { + if !ins_printed { + println!("\t{ins}"); + ins_printed = true; + } + println!("\t\t{func}"); + } + } + } +} + +/// produces output for all cpacfinfo commands that do not contain the -m/--msa flag +fn out_instructions(args: &Cli, instructions: &Vec) { + // produce json output + if args.format == Format::Json { + println!("{}", serde_json::to_string(instructions).unwrap()); + return; + } + + // produce human readable output + for ins in instructions { + if !args.instructions.contains(&ins.kind) && !args.instructions.is_empty() + || !ins.info.available + { + continue; + } + println!("{ins}"); + + // --no-auth-info/-n suppresses the Authentication Information output + if !args.quiet && ins.info.qai_available { + println!("{}", ins.info.qai); + } else if !args.quiet { + println!("Query Authentication Information not available for {ins} instruction! (potentially insufficient machine level)"); + } + + // --functions/-f lists functions of instructions + if args.functions { + ins.funcs + .iter() + .filter(|func| { + !(!func.available && !args.not_available + || func.available && !args.available && args.not_available) + }) + .for_each(|func| println!("\t{func}")); + println!(); + } + } +} + +fn main() -> anyhowRes<()> { + /* ---- PARSE COMMAND LINE ARGUMENTS ---- */ + let args: Cli = Cli::parse(); + + /* ---- PRINT VERSION STRING ---- */ + if args.version { + print_version!(0, "2024"); + return Ok(()); + } + + /* ---- SET CONSTANTS ---- */ + let mut instructions = Vec::new(); + init_instructions(&mut instructions); + + let mut levels = Vec::new(); + for lvl in 0..MSA_LEVEL_COUNT { + let temp = match num2msa(lvl) { + Some(l) => l, + None => panic!("programming error"), + }; + let stfle_bit = msa2stfle(&temp); + levels.push(MsaLevel::new(temp, stfle_bit)); + let idx_of_last_element = levels.len() - 1; + update_msa_function_count(&args, &mut levels[idx_of_last_element], &instructions); + } + + /* ---- GET INFORMATION ---- */ + // get stfle bits + let stfle_bits = match Stfle::new() { + Ok(stfle) => stfle, + Err(e) => return Err(e), + }; + + // check stfle bits for available MSA levels + for lvl in &mut levels { + match lvl.stfle_bit { + Some(bit) => lvl.enabled = stfle_bits.check_bit_in_stfle(bit), + None => continue, + } + } + + // check if SYSFS_PATH is available + match check_sysfs() { + true => (), + false => return Ok(()), + } + + // run query function (fc 0) for every instruction to check available functions + for ins in &mut instructions { + if stfle_bits.check_bit_in_stfle(ins.info.stfle_bit) { + ins.info.available = true; + + // run query; save result in param + let mut param = match query(&ins.kind, QUERY_FUNCTION_CODE) { + Ok(pb) => match pb { + Param::QueryParam(_) => pb, + Param::QaiParam(_) => panic!("programming error"), + }, + Err(e) => match e.kind() { + ErrorKind::NotFound => { + println!("Warning: Not able to retrieve subfunction information from sysfs for {ins} instruction"); + continue; + } + _ => return Err(e.into()), + }, + }; + + // check if bit for functions of current instruction is set in param + for func in &mut ins.funcs { + if !param.check_bit_in_param(func.function_code as usize) { + continue; + } + + func.available = true; + + // unset the bit in param to later see if any unsupported functions may be available + param.unset_bit_in_param(func.function_code); + + // check if qai is available + if func.function_code == QAI_FUNCTION_CODE { + ins.info.qai_available = true; + } + + // sync MsaLevel struct + for lvl in &mut levels { + if lvl.msa_level == func.msa { + lvl.enabled = true; + lvl.available_functions += 1; + if args.instructions.is_empty() || args.instructions.contains(&ins.kind) { + lvl.dynamic_available_functions += 1; + } + break; + } + } + } + + // look for any unsupported functions that my be available + for i in 0..NUMBER_FUNC_CODES { + // every bit in param that is 1 is an unsupported function + if !param.check_bit_in_param(i) { + continue; + } + + // add function to instruction as UNKNOWN + Instruction::add(ins, Function::new(i as u8, Msa::UNKNOWN, "UNKNOWN")); + + // set function as available + match ins.funcs.last_mut() { + Some(ret) => ret.available = true, + None => panic!("programming error"), + } + } + + // if query authentication information (fc 127) available run query authentication + // information + if ins.info.qai_available { + // get qai from sysfs + let param = match query(&ins.kind, QAI_FUNCTION_CODE) { + Ok(pb) => match pb { + Param::QueryParam(_) => panic!("programming error"), + Param::QaiParam(_) => pb, + }, + Err(e) => match e.kind() { + ErrorKind::NotFound => { + println!("Warning: Not able to retrieve Query Authentication Information from sysfs for {ins} instruction"); + continue; + } + _ => return Err(e.into()), + }, + }; + + // parse qai information into QueryAuthInfo struct + match param.parse_qai_based_on_format(&mut ins.info.qai) { + Ok(true) => (), + Ok(false) => println!("WARNING: format {} in query authentication information of instruction {} is UNKNOWN", ins.info.qai.format, ins.kind), + Err(e) => bail!(e.to_string()), + } + } + } + } + + /* ---- OUTPUT ---- */ + match args.msa { + true => out_msa(&args, &levels, &instructions), + false => out_instructions(&args, &instructions), + } + Ok(()) +} diff --git a/rust/cpacfinfo/src/msa.rs b/rust/cpacfinfo/src/msa.rs new file mode 100644 index 00000000..4e9bad1e --- /dev/null +++ b/rust/cpacfinfo/src/msa.rs @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use crate::cli::Cli; +use core::fmt::{Display, Formatter, Result}; +use serde::{Serialize, Serializer}; +use utils::HexSlice; + +/// Number of total function codes (0 to 127) +pub const NUMBER_FUNC_CODES: usize = 128; + +/// Number of MSA levels starting with MSA (0) - MSA 13 +pub const MSA_LEVEL_COUNT: u8 = 14; + +/// enum of all supported instructions +#[derive(PartialEq, Clone, clap::ValueEnum, Serialize)] +#[allow(clippy::upper_case_acronyms)] +pub enum InstructionKind { + /// introduced with MSA + KM, + /// introduced with MSA + KMC, + /// introduced with MSA + KIMD, + /// introduced with MSA + KLMD, + /// introduced with MSA + KMAC, + /// introduced with MSA 3 + PCKMO, + /// introduced with MSA 4 + KMF, + /// introduced with MSA 4 + KMCTR, + /// introduced with MSA 4 + KMO, + /// introduced with MSA 4 + PCC, + /// introduced with MSA 5 + PRNO, + /// introduced with MSA 8 + KMA, + /// introduced with MSA 9 + KDSA, +} + +/// enum of all MSA levels +#[derive(Clone, Default, PartialEq)] +#[allow(clippy::upper_case_acronyms)] +pub enum Msa { + MSA, + MSA1, + MSA2, + MSA3, + MSA4, + MSA5, + MSA6, + MSA7, + MSA8, + MSA9, + MSA10, + MSA11, + MSA12, + MSA13, + #[default] + UNKNOWN, +} + +impl Serialize for Msa { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + match *self { + Self::MSA => serializer.serialize_unit_variant("Msa", 0, "0"), + Self::MSA1 => serializer.serialize_unit_variant("Msa", 1, "1"), + Self::MSA2 => serializer.serialize_unit_variant("Msa", 2, "2"), + Self::MSA3 => serializer.serialize_unit_variant("Msa", 3, "3"), + Self::MSA4 => serializer.serialize_unit_variant("Msa", 4, "4"), + Self::MSA5 => serializer.serialize_unit_variant("Msa", 5, "5"), + Self::MSA6 => serializer.serialize_unit_variant("Msa", 6, "6"), + Self::MSA7 => serializer.serialize_unit_variant("Msa", 7, "7"), + Self::MSA8 => serializer.serialize_unit_variant("Msa", 8, "8"), + Self::MSA9 => serializer.serialize_unit_variant("Msa", 9, "9"), + Self::MSA10 => serializer.serialize_unit_variant("Msa", 10, "10"), + Self::MSA11 => serializer.serialize_unit_variant("Msa", 11, "11"), + Self::MSA12 => serializer.serialize_unit_variant("Msa", 12, "12"), + Self::MSA13 => serializer.serialize_unit_variant("Msa", 13, "13"), + Self::UNKNOWN => serializer.serialize_unit_variant("Msa", 14, "UNKNOWN"), + } + } +} + +impl Display for Msa { + fn fmt(&self, f: &mut Formatter) -> Result { + match *self { + Self::MSA => write!(f, "MSA "), + Self::MSA1 => write!(f, "MSA 1"), + Self::MSA2 => write!(f, "MSA 2"), + Self::MSA3 => write!(f, "MSA 3"), + Self::MSA4 => write!(f, "MSA 4"), + Self::MSA5 => write!(f, "MSA 5"), + Self::MSA6 => write!(f, "MSA 6"), + Self::MSA7 => write!(f, "MSA 7"), + Self::MSA8 => write!(f, "MSA 8"), + Self::MSA9 => write!(f, "MSA 9"), + Self::MSA10 => write!(f, "MSA 10"), + Self::MSA11 => write!(f, "MSA 11"), + Self::MSA12 => write!(f, "MSA 12"), + Self::MSA13 => write!(f, "MSA 13"), + Self::UNKNOWN => write!(f, "UNKNOWN"), + } + } +} + +/// converts Instruction enum to a string representation +impl Display for InstructionKind { + fn fmt(&self, f: &mut Formatter) -> Result { + match *self { + Self::KM => write!(f, "KM"), + Self::KMC => write!(f, "KMC"), + Self::KIMD => write!(f, "KIMD"), + Self::KLMD => write!(f, "KLMD"), + Self::KMAC => write!(f, "KMAC"), + Self::PCKMO => write!(f, "PCKMO"), + Self::KMF => write!(f, "KMF"), + Self::KMCTR => write!(f, "KMCTR"), + Self::KMO => write!(f, "KMO"), + Self::PCC => write!(f, "PCC"), + Self::PRNO => write!(f, "PRNO"), + Self::KMA => write!(f, "KMA"), + Self::KDSA => write!(f, "KDSA"), + } + } +} + +#[derive(Serialize, Default)] +pub struct MsaLevel { + pub msa_level: Msa, + total_functions: u8, + pub available_functions: u8, + #[serde(skip)] + dynamic_total_functions: u8, + #[serde(skip)] + pub dynamic_available_functions: u8, + pub stfle_bit: Option, + pub enabled: bool, +} + +impl MsaLevel { + pub fn new(msa_level: Msa, stfle_bit: Option) -> Self { + Self { + msa_level, + stfle_bit, + ..Default::default() + } + } +} + +impl Display for MsaLevel { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{} ", self.msa_level)?; + match self.stfle_bit { + Some(bit) => write!(f, "STFLE bit [ {:>3} ] : ", bit)?, + None => write!(f, " : ")?, + } + match self.enabled { + true => write!(f, " AVAILABLE")?, + false => write!(f, "NOT AVAILABLE")?, + } + write!( + f, + " ( {:>2} / {:<2} functions available )", + self.dynamic_available_functions, self.dynamic_total_functions + ) + } +} + +#[derive(Serialize, Clone, Default)] +pub struct Function { + name: String, + pub function_code: u8, + pub available: bool, + #[serde(skip)] + pub msa: Msa, +} + +impl Function { + pub fn new(fc: u8, msa: Msa, name: &str) -> Self { + Self { + function_code: fc, + name: name.to_string(), + msa, + ..Default::default() + } + } +} + +impl Display for Function { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "({:3}) ", self.function_code)?; + match self.available { + true => write!(f, "[ AVAILABLE]")?, + false => write!(f, "[NOT AVAILABLE]")?, + } + write!(f, " {}", self.name) + } +} + +#[derive(Serialize, Default)] +pub struct QueryAuthInfo { + pub format: u8, + pub hash_len: u16, + pub version: u32, + // #[serde(with = "hex::serde")] + #[serde(serialize_with = "ser_hex")] + pub hash: Vec, +} + +impl Display for QueryAuthInfo { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, " Format: {}", self.format)?; + if self.format != 0 { + writeln!(f, " (unknown format)")?; + return Ok(()); + } + write!(f, "; Hash length: {}", self.hash_len)?; + writeln!(f, "; IFCL version: {}", self.version)?; + writeln!(f, " Hash:")?; + + for chunk in self.hash.chunks(16) { + writeln!(f, " {:-}", HexSlice::from(chunk))?; + } + Ok(()) + } +} + +#[derive(Serialize, Default)] +pub struct InstructionInfo { + pub name: String, + pub available: bool, + pub stfle_bit: u8, + #[serde(skip)] + pub qai_available: bool, + pub qai: QueryAuthInfo, +} + +impl InstructionInfo { + fn new(stfle_bit: u8, name: &str) -> Self { + Self { + stfle_bit, + name: name.to_string(), + ..Default::default() + } + } +} + +#[derive(Serialize)] +pub struct Instruction { + pub kind: InstructionKind, + pub info: InstructionInfo, + pub funcs: Vec, +} + +impl Instruction { + fn new(instruction: InstructionKind, stfle_bit: u8, name: &str) -> Self { + Self { + kind: instruction, + info: InstructionInfo::new(stfle_bit, name), + funcs: Vec::new(), + } + } + + pub fn add(&mut self, func: Function) { + self.funcs.push(func); + } +} + +impl Display for Instruction { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "{} ({})", self.info.name, self.kind) + } +} + +/// returns stfle bit based on given MSA level +pub fn msa2stfle(msa_level: &Msa) -> Option { + match msa_level { + Msa::MSA => Some(17), + Msa::MSA3 => Some(76), + Msa::MSA4 => Some(77), + Msa::MSA5 => Some(57), + Msa::MSA8 => Some(146), + Msa::MSA9 => Some(155), + Msa::MSA12 => Some(86), + _ => None, + } +} + +/// returns MSA level based on given u8 +pub fn num2msa(num: u8) -> Option { + match num { + 0 => Some(Msa::MSA), + 1 => Some(Msa::MSA1), + 2 => Some(Msa::MSA2), + 3 => Some(Msa::MSA3), + 4 => Some(Msa::MSA4), + 5 => Some(Msa::MSA5), + 6 => Some(Msa::MSA6), + 7 => Some(Msa::MSA7), + 8 => Some(Msa::MSA8), + 9 => Some(Msa::MSA9), + 10 => Some(Msa::MSA10), + 11 => Some(Msa::MSA11), + 12 => Some(Msa::MSA12), + 13 => Some(Msa::MSA13), + _ => None, + } +} + +/// Initializes all functions known by cpacfinfo +#[rustfmt::skip] +pub fn init_instructions(instructions: &mut Vec) { + let mut km = Instruction::new(InstructionKind::KM, 17, "Cipher Message"); + km.add(Function::new(0, Msa::MSA, "KM-Query")); + km.add(Function::new(1, Msa::MSA, "KM-DEA")); + km.add(Function::new(2, Msa::MSA, "KM-TDEA-128")); + km.add(Function::new(3, Msa::MSA, "KM-TDEA-192")); + km.add(Function::new(9, Msa::MSA3, "KM-Encrypted-DEA")); + km.add(Function::new(10, Msa::MSA3, "KM-Encrypted-TDEA-128")); + km.add(Function::new(11, Msa::MSA3, "KM-Encrypted-TDEA-192")); + km.add(Function::new(18, Msa::MSA1, "KM-AES-128")); + km.add(Function::new(19, Msa::MSA2, "KM-AES-192")); + km.add(Function::new(20, Msa::MSA2, "KM-AES-256")); + km.add(Function::new(26, Msa::MSA3, "KM-Encrypted-AES-128")); + km.add(Function::new(27, Msa::MSA3, "KM-Encrypted-AES-192")); + km.add(Function::new(28, Msa::MSA3, "KM-Encrypted-AES-256")); + km.add(Function::new(50, Msa::MSA4, "KM-XTS-AES-128")); + km.add(Function::new(52, Msa::MSA4, "KM-XTS-AES-256")); + km.add(Function::new(58, Msa::MSA4, "KM-XTS-Encrypted-AES-128")); + km.add(Function::new(60, Msa::MSA4, "KM-XTS-Encrypted-AES-256")); + km.add(Function::new(82, Msa::MSA10, "KM-FULL-XTS-AES-128")); + km.add(Function::new(84, Msa::MSA10, "KM-FULL-XTS-AES-256")); + km.add(Function::new(90, Msa::MSA10, "KM-FULL-XTS-Encrypted-AES-128")); + km.add(Function::new(92, Msa::MSA10, "KM-FULL-XTS-Encrypted-AES-256")); + km.add(Function::new(127, Msa::MSA13, "KM-Query-Authentication-Information")); + + let mut kmc = Instruction::new(InstructionKind::KMC, 17, "Cipher Message with Chaining"); + kmc.add(Function::new(0, Msa::MSA, "KMC-Query")); + kmc.add(Function::new(1, Msa::MSA, "KMC-DEA")); + kmc.add(Function::new(2, Msa::MSA, "KMC-TDEA-128")); + kmc.add(Function::new(3, Msa::MSA, "KMC-TDEA-192")); + kmc.add(Function::new(9, Msa::MSA3, "KMC-Encrypted-DEA")); + kmc.add(Function::new(10, Msa::MSA3, "KMC-Encrypted-TDEA-128")); + kmc.add(Function::new(11, Msa::MSA3, "KMC-Encrypted-TDEA-192")); + kmc.add(Function::new(18, Msa::MSA1, "KMC-AES-128")); + kmc.add(Function::new(19, Msa::MSA2, "KMC-AES-192")); + kmc.add(Function::new(20, Msa::MSA2, "KMC-AES-256")); + kmc.add(Function::new(26, Msa::MSA3, "KMC-Encrypted-AES-128")); + kmc.add(Function::new(27, Msa::MSA3, "KMC-Encrypted-AES-192")); + kmc.add(Function::new(28, Msa::MSA3, "KMC-Encrypted-AES-256")); + kmc.add(Function::new(67, Msa::MSA1, "KMC-PRNG")); + kmc.add(Function::new(127, Msa::MSA13, "KMC-Query-Authentication-Information")); + + let mut kimd = Instruction::new(InstructionKind::KIMD, 17, "Compute Intermediate Message Digest"); + kimd.add(Function::new(0, Msa::MSA, "KIMD-Query")); + kimd.add(Function::new(1, Msa::MSA, "KIMD-SHA-1")); + kimd.add(Function::new(2, Msa::MSA1, "KIMD-SHA-256")); + kimd.add(Function::new(3, Msa::MSA2, "KIMD-SHA-512")); + kimd.add(Function::new(32, Msa::MSA6, "KIMD-SHA3-224")); + kimd.add(Function::new(33, Msa::MSA6, "KIMD-SHA3-256")); + kimd.add(Function::new(34, Msa::MSA6, "KIMD-SHA3-384")); + kimd.add(Function::new(35, Msa::MSA6, "KIMD-SHA3-512")); + kimd.add(Function::new(36, Msa::MSA6, "KIMD-SHAKE-128")); + kimd.add(Function::new(37, Msa::MSA6, "KIMD-SHAKE-256")); + kimd.add(Function::new(65, Msa::MSA4, "KIMD-GHASH")); + kimd.add(Function::new(127, Msa::MSA13, "KIMD-Query-Authentication-Information")); + + let mut klmd = Instruction::new(InstructionKind::KLMD, 17, "Compute Last Message Digest"); + klmd.add(Function::new(0, Msa::MSA, "KLMD-Query")); + klmd.add(Function::new(1, Msa::MSA, "KLMD-SHA-1")); + klmd.add(Function::new(2, Msa::MSA1, "KLMD-SHA-256")); + klmd.add(Function::new(3, Msa::MSA2, "KLMD-SHA-512")); + klmd.add(Function::new(32, Msa::MSA6, "KLMD-SHA3-224")); + klmd.add(Function::new(33, Msa::MSA6, "KLMD-SHA3-256")); + klmd.add(Function::new(34, Msa::MSA6, "KLMD-SHA3-384")); + klmd.add(Function::new(35, Msa::MSA6, "KLMD-SHA3-512")); + klmd.add(Function::new(36, Msa::MSA6, "KLMD-SHAKE-128")); + klmd.add(Function::new(37, Msa::MSA6, "KLMD-SHAKE-256")); + klmd.add(Function::new(127, Msa::MSA13, "KLMD-Query-Authentication-Information")); + + let mut kmac = Instruction::new(InstructionKind::KMAC, 17, "Compute Message Authentication Code"); + kmac.add(Function::new(0, Msa::MSA, "KMAC-Query")); + kmac.add(Function::new(1, Msa::MSA, "KMAC-DEA")); + kmac.add(Function::new(2, Msa::MSA, "KMAC-TDEA-128")); + kmac.add(Function::new(3, Msa::MSA, "KMAC-TDEA-192")); + kmac.add(Function::new(9, Msa::MSA3, "KMAC-Encrypted-DEA")); + kmac.add(Function::new(10, Msa::MSA3, "KMAC-Encrypted-TDEA-128")); + kmac.add(Function::new(11, Msa::MSA3, "KMAC-Encrypted-TDEA-192")); + kmac.add(Function::new(18, Msa::MSA4, "KMAC-AES-128")); + kmac.add(Function::new(19, Msa::MSA4, "KMAC-AES-192")); + kmac.add(Function::new(20, Msa::MSA4, "KMAC-AES-256")); + kmac.add(Function::new(26, Msa::MSA4, "KMAC-Encrypted-AES-128")); + kmac.add(Function::new(27, Msa::MSA4, "KMAC-Encrypted-AES-192")); + kmac.add(Function::new(28, Msa::MSA4, "KMAC-Encrypted-AES-256")); + kmac.add(Function::new(112, Msa::MSA11, "KMAC-HMAC-SHA-224")); + kmac.add(Function::new(113, Msa::MSA11, "KMAC-HMAC-SHA-256")); + kmac.add(Function::new(114, Msa::MSA11, "KMAC-HMAC-SHA-384")); + kmac.add(Function::new(115, Msa::MSA11, "KMAC-HMAC-SHA-512")); + kmac.add(Function::new(120, Msa::MSA11, "KMAC-HMAC-Encrypted-SHA-224")); + kmac.add(Function::new(121, Msa::MSA11, "KMAC-HMAC-Encrypted-SHA-256")); + kmac.add(Function::new(122, Msa::MSA11, "KMAC-HMAC-Encrypted-SHA-384")); + kmac.add(Function::new(123, Msa::MSA11, "KMAC-HMAC-Encrypted-SHA-512")); + kmac.add(Function::new(127, Msa::MSA13, "KMAC-Query-Authentication-Information")); + + let mut pckmo = Instruction::new(InstructionKind::PCKMO, 76, "Perform Cryptographic Key Management Operation"); + pckmo.add(Function::new(0, Msa::MSA3, "PCKMO-Query")); + pckmo.add(Function::new(1, Msa::MSA3, "PCKMO-Encrypt-DEA-Key")); + pckmo.add(Function::new(2, Msa::MSA3, "PCKMO-Encrypt-TDEA-128-Key")); + pckmo.add(Function::new(3, Msa::MSA3, "PCKMO-Encrypt-TDEA-192-Key")); + pckmo.add(Function::new(18, Msa::MSA3, "PCKMO-Encrypt-AES-128-Key")); + pckmo.add(Function::new(19, Msa::MSA3, "PCKMO-Encrypt-AES-192-Key")); + pckmo.add(Function::new(20, Msa::MSA3, "PCKMO-Encrypt-AES-256-Key")); + pckmo.add(Function::new(21, Msa::MSA10, "PCKMO-AES-XTS-128-Double")); + pckmo.add(Function::new(22, Msa::MSA10, "PCKMO-AES-XTS-256-Double")); + pckmo.add(Function::new(32, Msa::MSA9, "PCKMO-Encrypt-ECC-P256-Key")); + pckmo.add(Function::new(33, Msa::MSA9, "PCKMO-Encrypt-ECC-P384-Key")); + pckmo.add(Function::new(34, Msa::MSA9, "PCKMO-Encrypt-ECC-P521-Key")); + pckmo.add(Function::new(40, Msa::MSA9, "PCKMO-Encrypt-ECC-Ed25519-Key")); + pckmo.add(Function::new(41, Msa::MSA9, "PCKMO-Encrypt-ECC-Ed448-Key")); + pckmo.add(Function::new(118, Msa::MSA11, "PCKMO-Encrypted-HMAC-512-KEY")); + pckmo.add(Function::new(122, Msa::MSA11, "PCKMO-Encrypted-HMAC-1024-KEY")); + pckmo.add(Function::new(127, Msa::MSA13, "PCKMO-Query-Authentication-Information")); + + let mut kmf = Instruction::new(InstructionKind::KMF, 77, "Cipher Message with Cipher Feedback"); + kmf.add(Function::new(0, Msa::MSA4, "KMF-Query")); + kmf.add(Function::new(1, Msa::MSA4, "KMF-DEA")); + kmf.add(Function::new(2, Msa::MSA4, "KMF-TDEA-128")); + kmf.add(Function::new(3, Msa::MSA4, "KMF-TDEA-192")); + kmf.add(Function::new(9, Msa::MSA4, "KMF-Encrypted-DEA")); + kmf.add(Function::new(10, Msa::MSA4, "KMF-Encrypted-TDEA-128")); + kmf.add(Function::new(11, Msa::MSA4, "KMF-Encrypted-TDEA-192")); + kmf.add(Function::new(18, Msa::MSA4, "KMF-AES-128")); + kmf.add(Function::new(19, Msa::MSA4, "KMF-AES-192")); + kmf.add(Function::new(20, Msa::MSA4, "KMF-AES-256")); + kmf.add(Function::new(26, Msa::MSA4, "KMF-Encrypted-AES-128")); + kmf.add(Function::new(27, Msa::MSA4, "KMF-Encrypted-AES-192")); + kmf.add(Function::new(28, Msa::MSA4, "KMF-Encrypted-AES-256")); + kmf.add(Function::new(127, Msa::MSA13, "KMF-Query-Authentication-Information")); + + let mut kmctr = Instruction::new(InstructionKind::KMCTR, 77, "Cipher Message with Counter"); + kmctr.add(Function::new(0, Msa::MSA4, "KMCTR-Query")); + kmctr.add(Function::new(1, Msa::MSA4, "KMCTR-DEA")); + kmctr.add(Function::new(2, Msa::MSA4, "KMCTR-TDEA-128")); + kmctr.add(Function::new(3, Msa::MSA4, "KMCTR-TDEA-192")); + kmctr.add(Function::new(9, Msa::MSA4, "KMCTR-Encrypted-DEA")); + kmctr.add(Function::new(10, Msa::MSA4, "KMCTR-Encrypted-TDEA-128")); + kmctr.add(Function::new(11, Msa::MSA4, "KMCTR-Encrypted-TDEA-192")); + kmctr.add(Function::new(18, Msa::MSA4, "KMCTR-AES-128")); + kmctr.add(Function::new(19, Msa::MSA4, "KMCTR-AES-192")); + kmctr.add(Function::new(20, Msa::MSA4, "KMCTR-AES-256")); + kmctr.add(Function::new(26, Msa::MSA4, "KMCTR-Encrypted-AES-128")); + kmctr.add(Function::new(27, Msa::MSA4, "KMCTR-Encrypted-AES-192")); + kmctr.add(Function::new(28, Msa::MSA4, "KMCTR-Encrypted-AES-256")); + kmctr.add(Function::new(127, Msa::MSA13, "KMCTR-Query-Authentication-Information")); + + let mut kmo = Instruction::new(InstructionKind::KMO, 77, "Cipher Message with Output Feedback"); + kmo.add(Function::new(0, Msa::MSA4, "KMO-Query")); + kmo.add(Function::new(1, Msa::MSA4, "KMO-DEA")); + kmo.add(Function::new(2, Msa::MSA4, "KMO-TDEA-128")); + kmo.add(Function::new(3, Msa::MSA4, "KMO-TDEA-192")); + kmo.add(Function::new(9, Msa::MSA4, "KMO-Encrypted-DEA")); + kmo.add(Function::new(10, Msa::MSA4, "KMO-Encrypted-TDEA-128")); + kmo.add(Function::new(11, Msa::MSA4, "KMO-Encrypted-TDEA-192")); + kmo.add(Function::new(18, Msa::MSA4, "KMO-AES-128")); + kmo.add(Function::new(19, Msa::MSA4, "KMO-AES-192")); + kmo.add(Function::new(20, Msa::MSA4, "KMO-AES-256")); + kmo.add(Function::new(26, Msa::MSA4, "KMO-Encrypted-AES-128")); + kmo.add(Function::new(27, Msa::MSA4, "KMO-Encrypted-AES-192")); + kmo.add(Function::new(28, Msa::MSA4, "KMO-Encrypted-AES-256")); + kmo.add(Function::new(127, Msa::MSA13, "KMO-Query-Authentication-Information")); + + let mut pcc = Instruction::new(InstructionKind::PCC, 77, "Perform Cryptographic Computation"); + pcc.add(Function::new(0, Msa::MSA4, "PCC-Query")); + pcc.add(Function::new(1, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-DEA")); + pcc.add(Function::new(2, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-TDEA-128")); + pcc.add(Function::new(3, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-TDEA-192")); + pcc.add(Function::new(9, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-Encrypted-DEA")); + pcc.add(Function::new(10, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-Encrypted-TDEA-128")); + pcc.add(Function::new(11, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-Encrypted-TDEA-192")); + pcc.add(Function::new(18, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-AES-128")); + pcc.add(Function::new(19, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-AES-192")); + pcc.add(Function::new(20, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-AES-256")); + pcc.add(Function::new(26, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-Encrypted-AES-128")); + pcc.add(Function::new(27, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-Encrypted-AES-192")); + pcc.add(Function::new(28, Msa::MSA4, "PCC-Compute-Last-Block-CMAC-Using-Encrypted-AES-256")); + pcc.add(Function::new(50, Msa::MSA4, "PCC-Compute-XTS-Parameter-Using-AES-128")); + pcc.add(Function::new(52, Msa::MSA4, "PCC-Compute-XTS-Parameter-Using-AES-256")); + pcc.add(Function::new(58, Msa::MSA4, "PCC-Compute-XTS-Parameter-Using-Encrypted-AES-128")); + pcc.add(Function::new(60, Msa::MSA4, "PCC-Compute-XTS-Parameter-Using-Encrypted-AES-256")); + pcc.add(Function::new(64, Msa::MSA9, "PCC-Scalar-Multiply-P256")); + pcc.add(Function::new(65, Msa::MSA9, "PCC-Scalar-Multiply-P384")); + pcc.add(Function::new(66, Msa::MSA9, "PCC-Scalar-Multiply-P521")); + pcc.add(Function::new(72, Msa::MSA9, "PCC-Scalar-Multiply-Ed25519")); + pcc.add(Function::new(73, Msa::MSA9, "PCC-Scalar-Multiply-Ed448")); + pcc.add(Function::new(80, Msa::MSA9, "PCC-Scalar-Multiply-X25519")); + pcc.add(Function::new(81, Msa::MSA9, "PCC-Scalar-Multiply-X448")); + pcc.add(Function::new(127, Msa::MSA13, "PCC-Query-Authentication-Information")); + + let mut prno = Instruction::new(InstructionKind::PRNO, 57, "Perform Random Number Operation"); + prno.add(Function::new(0, Msa::MSA5, "PRNO-Query")); + prno.add(Function::new(3, Msa::MSA5, "PRNO-SHA-512-DRNG")); + prno.add(Function::new(112, Msa::MSA7, "PRNO-TRNG-Query-Raw-to-Conditioned-Ratio")); + prno.add(Function::new(114, Msa::MSA7, "PRNO-TRNG")); + prno.add(Function::new(127, Msa::MSA13, "PRNO-Query-Authentication-Information")); + + let mut kma = Instruction::new(InstructionKind::KMA, 146, "Cipher Message with Authentication"); + kma.add(Function::new(0, Msa::MSA8, "KMA-Query")); + kma.add(Function::new(18, Msa::MSA8, "KMA-GCM-AES-128")); + kma.add(Function::new(19, Msa::MSA8, "KMA-GCM-AES-192")); + kma.add(Function::new(20, Msa::MSA8, "KMA-GCM-AES-256")); + kma.add(Function::new(26, Msa::MSA8, "KMA-GCM-Encrypted-AES-128")); + kma.add(Function::new(27, Msa::MSA8, "KMA-GCM-Encrypted-AES-192")); + kma.add(Function::new(28, Msa::MSA8, "KMA-GCM-Encrypted-AES-256")); + kma.add(Function::new(127, Msa::MSA13, "KMA-Query-Authentication-Information")); + + let mut kdsa = Instruction::new(InstructionKind::KDSA, 155, "Compute Digital Signature Authentication"); + kdsa.add(Function::new(0, Msa::MSA9, "KDSA-Query")); + kdsa.add(Function::new(1, Msa::MSA9, "KDSA-ECDSA-Verify-P256")); + kdsa.add(Function::new(2, Msa::MSA9, "KDSA-ECDSA-Verify-P384")); + kdsa.add(Function::new(3, Msa::MSA9, "KDSA-ECDSA-Verify-P521")); + kdsa.add(Function::new(9, Msa::MSA9, "KDSA-ECDSA-Sign-P256")); + kdsa.add(Function::new(10, Msa::MSA9, "KDSA-ECDSA-Sign-P384")); + kdsa.add(Function::new(11, Msa::MSA9, "KDSA-ECDSA-Sign-P521")); + kdsa.add(Function::new(17, Msa::MSA9, "KDSA-Encrypted-ECDSA-Sign-P256")); + kdsa.add(Function::new(18, Msa::MSA9, "KDSA-Encrypted-ECDSA-Sign-P384")); + kdsa.add(Function::new(19, Msa::MSA9, "KDSA-Encrypted-ECDSA-Sign-P521")); + kdsa.add(Function::new(32, Msa::MSA9, "KDSA-EdDSA-Verify-Ed25519")); + kdsa.add(Function::new(36, Msa::MSA9, "KDSA-EdDSA-Verify-Ed448")); + kdsa.add(Function::new(40, Msa::MSA9, "KDSA-EdDSA-Sign-Ed25519")); + kdsa.add(Function::new(44, Msa::MSA9, "KDSA-EdDSA-Sign-Ed448")); + kdsa.add(Function::new(48, Msa::MSA9, "KDSA-Encrypted-EdDSA-Sign-Ed25519")); + kdsa.add(Function::new(52, Msa::MSA9, "KDSA-Encrypted-EdDSA-Sign-Ed448")); + kdsa.add(Function::new(127, Msa::MSA13, "KDSA-Query-Authentication-Information")); + + instructions.push(km); + instructions.push(kmc); + instructions.push(kimd); + instructions.push(klmd); + instructions.push(kmac); + instructions.push(pckmo); + instructions.push(kmf); + instructions.push(kmctr); + instructions.push(kmo); + instructions.push(pcc); + instructions.push(prno); + instructions.push(kma); + instructions.push(kdsa); +} + +/// number of functions introduced by a level is dynamically counted to ease extension +pub fn update_msa_function_count(args: &Cli, msa: &mut MsaLevel, ins: &Vec) { + for i in ins { + let num_of_funcs_in_level = i.funcs.iter().filter(|f| f.msa == msa.msa_level).count() as u8; + + msa.total_functions += num_of_funcs_in_level; + if args.instructions.is_empty() || args.instructions.contains(&i.kind) { + msa.dynamic_total_functions += num_of_funcs_in_level; + } + } +} + +pub fn ser_hex(data: &Vec, ser: S) -> std::result::Result { + HexSlice::from(data).serialize(ser) +} diff --git a/rust/cpacfinfo/src/noop.c b/rust/cpacfinfo/src/noop.c new file mode 100644 index 00000000..df9d66b2 --- /dev/null +++ b/rust/cpacfinfo/src/noop.c @@ -0,0 +1,8 @@ +#include +#include + +uint32_t stfle(uint64_t __attribute__((unused)) * stfle_fac_list, + uint32_t __attribute__((unused)) size) +{ + return 0; +} diff --git a/rust/cpacfinfo/src/query.rs b/rust/cpacfinfo/src/query.rs new file mode 100644 index 00000000..85916732 --- /dev/null +++ b/rust/cpacfinfo/src/query.rs @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use std::fs::File; +use std::io::Error; +use std::io::Read; +use std::io::Result as ioRes; +use std::ops::Index; +use std::result::Result; + +use zerocopy::FromBytes; +use zerocopy::FromZeroes; + +use crate::msa::InstructionKind; +use crate::msa::QueryAuthInfo; + +/// Path to sysfs in which the query and qai informations are fetched from +const SYSFS_PATH: &str = "/sys/devices/system/cpu/cpacf/"; + +/// Every Instruction has a Query function to get information about what functions are available +pub const QUERY_FUNCTION_CODE: u8 = 0; + +/// Number of bytes returned by this Query +pub const QUERY_PARAM_SIZE_IN_BYTES: usize = 16; + +/// Starting with MSA 13 every Instruction has a Query Authentication Information function to get +/// information about the running firmware +pub const QAI_FUNCTION_CODE: u8 = 127; + +/// Number of bytes returned by this Query Authentication Information +pub const QAI_PARAM_SIZE_IN_BYTES: usize = 256; + +/// Query authentication information format identifier +const FORMAT_0: u8 = 0; + +#[derive(FromBytes, FromZeroes)] +#[repr(C)] +struct QaiFmt0 { + res00: [u8; 6], + hash_length: u16, + res08: [u8; 4], + version: u32, + hash: [u8; 64], +} + +#[allow(clippy::large_enum_variant)] +pub enum Param { + QueryParam([u8; QUERY_PARAM_SIZE_IN_BYTES]), + QaiParam([u8; QAI_PARAM_SIZE_IN_BYTES]), +} + +impl Index for Param { + type Output = u8; + + fn index(&self, index: u8) -> &Self::Output { + match self { + Self::QueryParam(p) => &p[index as usize], + Self::QaiParam(p) => &p[index as usize], + } + } +} + +impl Param { + pub fn len(&self) -> usize { + match self { + Self::QueryParam(_) => QUERY_PARAM_SIZE_IN_BYTES, + Self::QaiParam(_) => QAI_PARAM_SIZE_IN_BYTES, + } + } + + /// check if a specific bit is 1 in param + pub fn check_bit_in_param(&self, check_bit: usize) -> bool { + // get correct byte of param + let byte = check_bit / 8; + + // get correct byte of param + if byte >= self.len() { + return false; + } + + // get correct bit of param + let bit = 8 - ((check_bit % 8) + 1); + + // return if specified bit is set + match self { + Self::QueryParam(param) => (param[byte] & (1 << bit)) > 0, + Self::QaiParam(param) => (param[byte] & (1 << bit)) > 0, + } + } + + /// set given bit in param to 0 + pub fn unset_bit_in_param(&mut self, flip_bit: u8) { + // get correct byte of param + let byte = flip_bit / 8; + if byte as usize >= self.len() { + return; + } + + // get correct bit of param + let bit = 8 - ((flip_bit % 8) + 1); + + // build template to logically AND against param byte + // i.e. (flip_bit = 0) template = 1000 0000 + let mut template: u8 = 1 << bit; + // flip all bits in template + // i.e. (flip_bit = 0) template = 0111 1111 + template = !template; + + // set bit to 0 while not changing any other bit + // i.e. (flip_bit = 0) 0111 1111 & xxxx xxxx = 0xxx xxxx + match self { + Self::QueryParam(c) => c[byte as usize] &= template, + Self::QaiParam(c) => c[byte as usize] &= template, + } + } + + /// set all bytes of self to value + #[cfg(test)] + pub fn set_param_to(&mut self, value: u8) { + match self { + Self::QueryParam(ref mut content) => *content = [value; QUERY_PARAM_SIZE_IN_BYTES], + Self::QaiParam(ref mut content) => *content = [value; QAI_PARAM_SIZE_IN_BYTES], + } + } + + // Outsourced for potential future formats to be easily added in this match statement + pub fn parse_qai_based_on_format( + &self, + qai: &mut QueryAuthInfo, + ) -> Result { + match self { + Self::QueryParam(_) => panic!("programming error"), + Self::QaiParam(bin) => { + // The third byte of the param block specifies which format to use to parse the rest + qai.format = bin[3]; + + // for new formats add a match case here along with a parsing function + match qai.format { + FORMAT_0 => { + parse_qai_format_0(qai, bin); + Ok(true) + } + _ => Ok(false), + } + } + } + } +} + +// check if SYSFS_PATH exists +pub fn check_sysfs() -> bool { + match std::path::Path::new(SYSFS_PATH).exists() { + true => true, + false => { + println!("Warning: There seems to be an insufficient kernel level running (sysfs interface {SYSFS_PATH} is missing)\nNo information can be fetched from sysfs, application exits early."); + false + } + } +} + +/// parsing the information supplied by sysfs into QueryAuthInfo struct +/// +/// The following box shows the qai block with named fields each with a length in bytes. +/// The length of field IFCL Hash depends on IFCL Hash Length and is either 32 or 64 bytes long. +/// In case of a 32 bytes length the latter 32 bytes of the 64 bytes Hash are filled with zeros. +/// +/// | BYTE | BYTE | BYTE | BYTE | +/// ----------------------------------------------------------------- +/// | RESERVED (3) FORMAT (1) | +/// | RESERVED (2) IFCL HASH LENGTH (2) | +/// | RESERVED (4) | +/// | IFCL VERSION (4) | +/// | IFCL HASH (32 / 64) | +/// | RESERVED (176) | +/// ----------------------------------------------------------------- +fn parse_qai_format_0(qai: &mut QueryAuthInfo, param: &[u8]) { + // parse param to temporary struct to ease further conversion + let tmp = QaiFmt0::read_from_prefix(param).expect("programming error"); + + // parse from temporary struct + qai.hash_len = tmp.hash_length; + qai.version = tmp.version; + + // depending on the parsed hash length the hash is parsed + qai.hash = vec![0; qai.hash_len as usize]; + qai.hash + .as_mut_slice() + .copy_from_slice(&tmp.hash[..qai.hash_len as usize]); +} + +/// cpacfinfo does not execute the actual instruction with function code but uses information +/// provided by the sysfs +pub fn query(ins: &InstructionKind, fc: u8) -> Result { + // query dependent file names + let auth_info; + let mut param; + match fc { + QUERY_FUNCTION_CODE => { + auth_info = ""; + param = Param::QueryParam([0; QUERY_PARAM_SIZE_IN_BYTES]); + } + QAI_FUNCTION_CODE => { + auth_info = "_auth_info"; + param = Param::QaiParam([0; QAI_PARAM_SIZE_IN_BYTES]); + } + _ => panic!("programming error"), + }; + + // depending on which query is performed the bytes to be read from sysfs vary + let bytes_to_be_read = param.len(); + + // build filepath + let filepath = format!( + "{SYSFS_PATH}{}_query{auth_info}_raw", + ins.to_string().to_lowercase() + ); + + // open file + let mut f = match File::open(filepath) { + ioRes::Ok(file) => file, + Err(e) => return Err(e), + }; + + // read file + let res = match param { + Param::QueryParam(ref mut c) => read_file_to_buf(&mut f, c), + Param::QaiParam(ref mut c) => read_file_to_buf(&mut f, c), + }; + + let bytes_read = match res { + Ok(b) => b, + Err(e) => return Err(e), + }; + + match bytes_read == bytes_to_be_read { + true => Ok(param), + false => Err(Error::new(std::io::ErrorKind::UnexpectedEof, "test")), + } +} + +fn read_file_to_buf(file: &mut File, buf: &mut [u8]) -> Result { + file.read(buf) + /* match file.read(buf) { + Result::Ok(bytes_read) => Ok(bytes_read), + Err(e) => Err(e), + } */ +} + +#[cfg(test)] +#[test] +fn test_param_funcs() { + // initialize param with all ones + let mut param = Param::QueryParam([0; QUERY_PARAM_SIZE_IN_BYTES]); + const NUMBER_OF_BITS: usize = 8 * QUERY_PARAM_SIZE_IN_BYTES; + + for i in 0..NUMBER_OF_BITS { + // reset param to all ones + param.set_param_to(0xFF); + assert!(param.check_bit_in_param(i)); + + // set one bit to zero + param.unset_bit_in_param(i as u8); + + // check if that bit is zero + assert!(!param.check_bit_in_param(i)); + } +} diff --git a/rust/cpacfinfo/src/stfle.c b/rust/cpacfinfo/src/stfle.c new file mode 100644 index 00000000..9bf433b7 --- /dev/null +++ b/rust/cpacfinfo/src/stfle.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +#include + +uint32_t stfle(uint64_t *stfle_fac_list, uint32_t size) +{ + uint32_t reg0 = size - 1; + + asm volatile(" lgr %%r0,%[reg0]\n" + " .insn s,0xb2b00000,%[list]\n" /* stfle */ + " lgr %[reg0],%%r0\n" + : [reg0] "+&d"(reg0), [list] "+Q"(*stfle_fac_list) + : + : "memory", "cc", "r0"); + return reg0; +} diff --git a/rust/cpacfinfo/src/stfle.rs b/rust/cpacfinfo/src/stfle.rs new file mode 100644 index 00000000..2a88edc3 --- /dev/null +++ b/rust/cpacfinfo/src/stfle.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. 2024 + +use anyhow::Error; + +/// Specifies the number of u64 values needed to store the stfle block +pub const STFLE_LEN: usize = 3; + +pub struct Stfle { + data: [u64; STFLE_LEN], +} + +impl Stfle { + /// Constructs a STFLE block, and saves the STFLE information in the structure. + pub fn new() -> Result { + let mut ret = Self { + data: [0; STFLE_LEN], + }; + + // SAFETY: this call is safe because ret can store 64 bits * 3 which equals the required 192 + // bits. + let rc = unsafe { stfle(&mut ret.data[0], STFLE_LEN as u32) }; + let rc = match rc { + 0 => { + println!("Unable to fetch STFLE which is only available on s390x architecture"); + return Ok(ret); + } + rc if rc as usize >= STFLE_LEN => STFLE_LEN as u32, + rc => rc + 1, + }; + + if rc != STFLE_LEN as u32 { + println!("Partial read of STFLE, information might be incomplete"); + } + + Ok(ret) + } + + /// check specific bit in stfle (accounts for big-endianness of stfle) + pub fn check_bit_in_stfle(&self, check_bit: u8) -> bool { + // stfle is big endian while check_bit is little endian + let byte = (check_bit / 64) as usize; + if byte >= STFLE_LEN { + return false; + } + // conversion from little endian check_bit to big endian + let bit = ((check_bit / 64 + 1) * 64 - 1) - check_bit; + self.data[byte] & 1 << bit > 0 + } +} + +// STFLE bits cannot be retrieved from the system but have to be fetched by running the STFLE +// instruction of Z. This is done in linked C code +extern "C" { + /// Retrieve STFLE bits into list + /// + /// List is in big-endian when returned. + /// @list is to return the outcome of the stfle operation. Pointer must be able to store + /// 192 bits. + /// @doublewords specifies the length of the pointer @list as a number of elements behind + /// the pointer. + fn stfle(list: *mut u64, doublewords: u32) -> u32; +} + +#[cfg(test)] +#[test] +fn test_check_bit_in_stfle() { + let mut stfle = match Stfle::new() { + Ok(ret) => ret, + Err(e) => panic!("{e}"), + }; + + for b in 0..(STFLE_LEN * 64) { + // set bit + stfle.data[b / 64] = u64::pow(2, (63 - (b - ((b / 64) * 64))) as u32); + + // check if bit is set + assert!(stfle.check_bit_in_stfle(b as u8)); + + // reset stfle + stfle.data = [0; STFLE_LEN]; + } +}