mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
pvattest: Add firmware check version 2
Add Response version 2 which includes more details about the verification process. Reviewed-by: Marc Hartmayer <marc@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
e53f5ccfea
commit
8878ba4fd2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -216,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.
|
||||
@@ -303,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.
|
||||
|
||||
@@ -15,11 +15,11 @@ use pv::misc::{create_file, open_file, read_file};
|
||||
use serde::Serialize;
|
||||
use utils::HexSlice;
|
||||
|
||||
use self::firmware::firmware_check_v1;
|
||||
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};
|
||||
use crate::cli::{CheckOpt, CheckOptIO, FirmwareCheckVersion};
|
||||
use crate::exchange::ExchangeFormatResponse;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -120,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_v1(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),
|
||||
|
||||
@@ -25,6 +25,8 @@ const CLIENT_ID: &str = "x-client-id: X";
|
||||
enum Version {
|
||||
#[serde(rename = "1.0")]
|
||||
V1,
|
||||
#[serde(rename = "2.0")]
|
||||
V2,
|
||||
}
|
||||
|
||||
trait Request: Serialize + Debug {
|
||||
@@ -50,6 +52,23 @@ impl Request for RequestV1_1 {
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
@@ -75,6 +94,7 @@ trait Response: serde::de::DeserializeOwned + Debug + Display {
|
||||
fn verify_api(endp: &str) -> String {
|
||||
let ver = match Self::VERSION {
|
||||
Version::V1 => "v1",
|
||||
Version::V2 => "v2",
|
||||
};
|
||||
format!("{endp}/hmrs/firmware/attestation/{ver}/verify",)
|
||||
}
|
||||
@@ -113,6 +133,44 @@ impl Display for ResponseV1 {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -209,6 +267,10 @@ pub fn firmware_check_v1(opt: &CheckOpt, att_res: &AttestationResult) -> Result<
|
||||
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::*;
|
||||
@@ -226,6 +288,19 @@ mod test {
|
||||
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#"{
|
||||
@@ -264,4 +339,67 @@ mod test {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user