mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
utils: Read hybrid HKDs
* Add `HkdVersion` (classic or hybrid) and `HkdVersionSelection`. * Add `get_verified_hkds_new` that returns a list of verified HKDs read according to the given `HkdVersionSelection`. Co-Developed-by: Timo Keller <tkeller@linux.ibm.com> Signed-off-by: Timo Keller <tkeller@linux.ibm.com> Signed-off-by: Marc Hartmayer <marc@linux.ibm.com> Reviewed-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
committed by
Steffen Eiden
parent
cb9c2f993c
commit
d6fc4921fb
@@ -84,6 +84,9 @@ pub enum Error {
|
||||
#[error("The provided host key document in {0} contains no certificate!")]
|
||||
NoHkdInFile(String),
|
||||
|
||||
#[error("The provided host key document in {0} contains the wrong number of keys!")]
|
||||
WrongNumberOfKeys(String),
|
||||
|
||||
#[error("Invalid input size ({0}) for boot hdr")]
|
||||
InvBootHdrSize(usize),
|
||||
|
||||
@@ -135,6 +138,9 @@ pub enum Error {
|
||||
|
||||
#[error("Unsupported cipher: {:?}", .0.as_raw())]
|
||||
UnsupportedCipher(Nid),
|
||||
|
||||
#[error("{}", .0)]
|
||||
InvalidHkd(String),
|
||||
}
|
||||
|
||||
// used in macros
|
||||
|
||||
@@ -79,7 +79,7 @@ pub mod pem {
|
||||
pub mod misc {
|
||||
pub use pv_core::misc::*;
|
||||
|
||||
pub use crate::utils::read_certs;
|
||||
pub use crate::utils::{read_certs, read_hkd};
|
||||
}
|
||||
|
||||
pub use error::{Error, Result};
|
||||
@@ -95,7 +95,9 @@ pub mod request {
|
||||
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, SymKeyType,
|
||||
SHA_512_HASH_LEN,
|
||||
};
|
||||
pub use crate::req::{EcPubKeyCoord, Encrypt, HostKey, Keyslot, ReqEncrCtx, Request};
|
||||
pub use crate::req::{
|
||||
EcPubKeyCoord, Encrypt, HostKey, HybridPKey, Keyslot, ReqEncrCtx, Request,
|
||||
};
|
||||
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
|
||||
|
||||
/// Reexports some useful OpenSSL symbols
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
use std::path::Path;
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
use log::{error, info};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::x509::{X509Crl, X509};
|
||||
use pv_core::misc::read_file;
|
||||
|
||||
use crate::req::{HostKey, HybridPKey};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Read all CRLs from the buffer and parse them into a vector.
|
||||
@@ -31,6 +36,49 @@ pub fn read_certs<T: AsRef<[u8]>>(buf: T) -> Result<Vec<X509>, ErrorStack> {
|
||||
.or_else(|_| X509::stack_from_pem(buf.as_ref()))
|
||||
}
|
||||
|
||||
/// Read a host-key document from a file.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// - The file cannot be read
|
||||
/// - The content is not valid PEM or DER format
|
||||
/// - The file contains no certificates or more than 2 certificates
|
||||
/// - The public key cannot be extracted from the certificate(s)
|
||||
pub fn read_hkd<P: AsRef<Path>>(path: P) -> Result<HostKey> {
|
||||
let path = path.as_ref();
|
||||
let hk = read_file(path, "host-key document")?;
|
||||
let certs = read_certs(&hk).map_err(|source| Error::HkdNotPemOrDer {
|
||||
hkd: path.display().to_string(),
|
||||
source,
|
||||
})?;
|
||||
if certs.is_empty() {
|
||||
return Err(Error::NoHkdInFile(path.display().to_string()));
|
||||
}
|
||||
let c1 = certs.first().unwrap();
|
||||
match certs.len() {
|
||||
1 => {
|
||||
info!("Using version 1 of the host-key document format");
|
||||
Ok(HostKey::V1(c1.public_key()?))
|
||||
}
|
||||
2 => {
|
||||
info!("Using version 2 of the host-key document format");
|
||||
let c2 = &certs[1];
|
||||
Ok(HostKey::V2(HybridPKey::new(
|
||||
c1.public_key()?,
|
||||
c2.public_key()?,
|
||||
)?))
|
||||
}
|
||||
_ => {
|
||||
error!(
|
||||
"Invalid host-key document '{}': it contains more than two certificates, which is not supported by any host-key document format.",
|
||||
path.display()
|
||||
);
|
||||
Err(Error::WrongNumberOfKeys(path.display().to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test_utils::*;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023, 2024
|
||||
// Copyright IBM Corp.
|
||||
use std::fmt::Display;
|
||||
use std::io::{Read, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -11,10 +11,12 @@ use std::str::FromStr;
|
||||
use clap::builder::{EnumValueParser, PossibleValue, TypedValueParser};
|
||||
use clap::{Arg, ArgAction, ArgGroup, Args, Command, ValueEnum, ValueHint};
|
||||
use log::{info, warn, LevelFilter};
|
||||
use openssl::Nid;
|
||||
use pv::misc::{create_file, open_file, read_certs, read_file};
|
||||
use pv::request::openssl::pkey::{PKey, Public};
|
||||
use pv::request::HkdVerifier;
|
||||
use pv::request::openssl::pkey::{KeyType, PKey, PKeyRef, Public};
|
||||
use pv::request::{openssl, HkdVerifier, HostKey, HybridPKey};
|
||||
use pv::{Error, Result};
|
||||
use utils_macros::{ValueEnumDisplay, ValueEnumFromStr};
|
||||
|
||||
/// Generic version selection for CLI
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -131,6 +133,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Host key document version for CLI
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)]
|
||||
pub enum HkdVersion {
|
||||
/// Version 1 - uses traditional cryptographic keys
|
||||
Classical,
|
||||
/// Version 2 - uses hybrid (post-quantum) cryptographic keys
|
||||
Hybrid,
|
||||
}
|
||||
|
||||
pub type HkdVersionSelection = AutoOrExplicit<HkdVersion>;
|
||||
|
||||
/// CLI Argument collection for handling host-keys, IBM signing keys, and certificates.
|
||||
#[derive(Args, Debug, Clone, PartialEq, Eq, Default)]
|
||||
#[command(
|
||||
@@ -263,6 +276,105 @@ impl CertificateOptions {
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn is_ec_p521_key(key: &PKeyRef<Public>) -> bool {
|
||||
if key.id() == openssl::pkey::Id::EC {
|
||||
let ec_key = key.ec_key().unwrap();
|
||||
let group = ec_key.group();
|
||||
let curve_nid = group.curve_name().unwrap();
|
||||
curve_nid == Nid::SECP521R1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_mlkem1024_key(key: &PKeyRef<Public>) -> bool {
|
||||
key.is_a(KeyType::ML_KEM_1024)
|
||||
}
|
||||
|
||||
/// Read the hybrid host-keys specified and verifies them if required
|
||||
///
|
||||
/// - `protectee`: what you want to create. e.g. add-secret request or SE-image
|
||||
/// - `version`: requested host-key document version
|
||||
///
|
||||
/// # Error
|
||||
/// Returns an error if something went wrong during parsing the HKDs, the verification chain
|
||||
/// could not built, or when the verification
|
||||
/// failed.
|
||||
pub fn get_verified_hkds_new(
|
||||
&self,
|
||||
protectee: &'static str,
|
||||
requested_version: HkdVersionSelection,
|
||||
) -> Result<Vec<HostKey>> {
|
||||
let hkds = &self.host_key_documents;
|
||||
let verifier = self.verifier(protectee)?;
|
||||
|
||||
let mut res = Vec::with_capacity(hkds.len());
|
||||
for hkd in hkds {
|
||||
let hk = read_file(hkd, "host-key document")?;
|
||||
let certs = read_certs(&hk).map_err(|source| Error::HkdNotPemOrDer {
|
||||
hkd: hkd.display().to_string(),
|
||||
source,
|
||||
})?;
|
||||
if certs.is_empty() {
|
||||
return Err(Error::NoHkdInFile(hkd.display().to_string()));
|
||||
}
|
||||
let required_cert_count = match requested_version {
|
||||
HkdVersionSelection::Auto => {
|
||||
info!("Auto-detecting version of the host-key document format");
|
||||
match certs.len() {
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
_ => {
|
||||
warn!(
|
||||
"The host-key document in '{}' contains more than two certificates!",
|
||||
hkd.display()
|
||||
);
|
||||
return Err(Error::WrongNumberOfKeys(hkd.display().to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
HkdVersionSelection::Explicit(HkdVersion::Classical) => {
|
||||
info!("Using version 1 of the host-key document format");
|
||||
1
|
||||
}
|
||||
HkdVersionSelection::Explicit(HkdVersion::Hybrid) => {
|
||||
info!("Using version 2 of the host-key document format");
|
||||
2
|
||||
}
|
||||
};
|
||||
if required_cert_count != certs.len() {
|
||||
return Err(Error::WrongNumberOfKeys(hkd.display().to_string()));
|
||||
}
|
||||
let c1 = certs.first().unwrap();
|
||||
if !Self::is_ec_p521_key(c1.public_key()?.as_ref()) {
|
||||
return Err(Error::InvalidHkd(
|
||||
"First key must be a EC-p521 key".to_string(),
|
||||
));
|
||||
}
|
||||
verifier.verify(c1)?;
|
||||
match certs.len() {
|
||||
1 => {
|
||||
res.push(HostKey::V1(c1.public_key()?));
|
||||
}
|
||||
2 => {
|
||||
let c2 = &certs[1];
|
||||
if !Self::is_mlkem1024_key(c2.public_key()?.as_ref()) {
|
||||
return Err(Error::InvalidHkd(
|
||||
"Second key must be a ML-KEM 1024 key".to_string(),
|
||||
));
|
||||
}
|
||||
verifier.verify(c2)?;
|
||||
res.push(HostKey::V2(HybridPKey::new(
|
||||
c1.public_key()?,
|
||||
c2.public_key()?,
|
||||
)?))
|
||||
}
|
||||
_ => unreachable!("Already checked"),
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// stdout
|
||||
|
||||
@@ -19,8 +19,8 @@ pub use utils_macros::{ControlFlag, ValueEnumDisplay, ValueEnumFromStr};
|
||||
pub use crate::cli::{
|
||||
combined_path_opt, combined_path_req, get_reader_from_cli_file_arg,
|
||||
get_writer_from_cli_file_arg, print_cli_error, print_error, AutoOrExplicit,
|
||||
AutoOrExplicitParser, CertificateOptions, DeprecatedVerbosityOptions, VerbosityOptions, STDIN,
|
||||
STDOUT,
|
||||
AutoOrExplicitParser, CertificateOptions, DeprecatedVerbosityOptions, HkdVersion,
|
||||
HkdVersionSelection, VerbosityOptions, STDIN, STDOUT,
|
||||
};
|
||||
pub use crate::exit_code::{docstring, ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc};
|
||||
pub use crate::file::{AtomicFile, AtomicFileOperation};
|
||||
|
||||
Reference in New Issue
Block a user