From 183529d0ffb5e11877a4509d2ac1d09523a4693c Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Fri, 15 Nov 2024 10:15:19 +0100 Subject: [PATCH 1/5] main: Move IAK/IDevID related code to dedicated module Move the IAK/IDevID initialization code to the dedicated module 'device_id'. The module implements the builder pattern to set the parameters set through configuration. The goal is to simplify the code in main. Signed-off-by: Anderson Toshiyuki Sasaki --- keylime-agent/src/error.rs | 4 + keylime-agent/src/main.rs | 184 +++----------- keylime/src/crypto.rs | 21 +- keylime/src/device_id.rs | 488 +++++++++++++++++++++++++++++++++++++ keylime/src/lib.rs | 1 + keylime/src/tpm.rs | 56 ++++- 6 files changed, 591 insertions(+), 163 deletions(-) create mode 100644 keylime/src/device_id.rs diff --git a/keylime-agent/src/error.rs b/keylime-agent/src/error.rs index 6524ed6e9..caccb26a8 100644 --- a/keylime-agent/src/error.rs +++ b/keylime-agent/src/error.rs @@ -27,6 +27,10 @@ pub(crate) enum Error { Conversion(String), #[error("Configuration error")] Configuration(#[from] crate::config::KeylimeConfigError), + #[error("Device ID error")] + DeviceID(#[from] keylime::device_id::DeviceIDError), + #[error("Device ID builder error")] + DeviceIDBuilder(#[from] keylime::device_id::DeviceIDBuilderError), #[error("Reqwest error: {0}")] Reqwest(#[from] reqwest::Error), #[error("Registrar error: received {code} from {addr}")] diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs index 78646c3ea..58ce14a5a 100644 --- a/keylime-agent/src/main.rs +++ b/keylime-agent/src/main.rs @@ -58,8 +58,11 @@ use futures::{ try_join, }; use keylime::{ - crypto, crypto::x509::CertificateBuilder, ima::MeasurementList, - list_parser::parse_list, tpm, + crypto::{self, x509::CertificateBuilder}, + device_id::{DeviceID, DeviceIDBuilder}, + ima::MeasurementList, + list_parser::parse_list, + tpm::{self, IAKResult, IDevIDResult}, }; use log::*; use openssl::{ @@ -324,137 +327,6 @@ async fn main() -> Result<()> { config.agent.tpm_signing_alg.as_ref(), )?; - let iak_cert: Option; - let idevid_cert: Option; - // Attempt to load the IAK and IDevID certificates - if config.agent.enable_iak_idevid { - iak_cert = match config.agent.iak_cert.as_ref() { - "" => { - debug!("The iak_cert option was not set in the configuration file"); - None - } - path => { - let iak_path = Path::new(&path); - if iak_path.exists() { - debug!( - "Loading IAK certificate from {}", - iak_path.display() - ); - let iakcert = match crypto::load_x509_der(iak_path) { - Ok(cert) => cert, - Err(error) => crypto::load_x509_pem(iak_path)?, - }; - Some(iakcert) - } else { - debug!("Can not find IAK certificate"); - None - } - } - }; - idevid_cert = match config.agent.idevid_cert.as_ref() { - "" => { - debug!("The idevid_cert option was not set in the configuration file"); - None - } - path => { - let idevid_path = Path::new(&path); - if idevid_path.exists() { - debug!( - "Loading IDevID certificate from {}", - idevid_path.display() - ); - let idevcert = match crypto::load_x509_der(idevid_path) { - Ok(cert) => cert, - Err(error) => crypto::load_x509_pem(idevid_path)?, - }; - Some(idevcert) - } else { - debug!("Can not find IDevID certificate"); - None - } - } - }; - } else { - iak_cert = None; - idevid_cert = None; - } - /// Regenerate the IAK and IDevID keys or collect and authorise persisted ones and check that the keys match the certificates that have been loaded - let (iak, idevid) = if config.agent.enable_iak_idevid { - /// Try to detect which template has been used by checking the certificate - let (asym_alg, name_alg) = tpm::get_idevid_template( - &crypto::match_cert_to_template( - &iak_cert.clone().ok_or(Error::Other( - "IAK/IDevID enabled but cert could not be used" - .to_string(), - ))?, - )?, - config.agent.iak_idevid_template.as_str(), - config.agent.iak_idevid_asymmetric_alg.as_str(), - config.agent.iak_idevid_name_alg.as_str(), - )?; - - /// IDevID recreation/collection - let idevid = if config.agent.idevid_handle.trim().is_empty() { - /// If handle is not set in config, recreate IDevID according to template - info!("Recreating IDevID."); - let regen_idev = ctx.create_idevid(asym_alg, name_alg)?; - ctx.flush_context(regen_idev.handle.into())?; - // Flush after creating to make room for AK and EK and IAK - regen_idev - } else { - info!("Collecting persisted IDevID."); - ctx.idevid_from_handle( - config.agent.idevid_handle.as_str(), - config.agent.idevid_password.as_str(), - )? - }; - /// Check that recreated/collected IDevID key matches the one in the certificate - if crypto::check_x509_key( - &idevid_cert.clone().ok_or(Error::Other( - "IAK/IDevID enabled but IDevID cert could not be used" - .to_string(), - ))?, - idevid.clone().public, - )? { - info!("IDevID matches certificate."); - } else { - error!("IDevID template does not match certificate. Check template in configuration."); - return Err(Error::Configuration(config::KeylimeConfigError::Generic("IDevID template does not match certificate. Check template in configuration.".to_string()))); - } - - /// IAK recreation/collection - let iak = if config.agent.iak_handle.trim().is_empty() { - /// If handle is not set in config, recreate IAK according to template - info!("Recreating IAK."); - ctx.create_iak(asym_alg, name_alg)? - } else { - /// If a handle has been set, try to collect from the handle - /// If there is an IAK password, add the password to the handle - info!("Collecting persisted IAK."); - ctx.iak_from_handle( - config.agent.iak_handle.as_str(), - config.agent.iak_password.as_str(), - )? - }; - /// Check that recreated/collected IAK key matches the one in the certificate - if crypto::check_x509_key( - &iak_cert.clone().ok_or(Error::Other( - "IAK/IDevID enabled but IAK cert could not be used" - .to_string(), - ))?, - iak.clone().public, - )? { - info!("IAK matches certificate."); - } else { - error!("IAK template does not match certificate. Check template in configuration."); - return Err(Error::Configuration(config::KeylimeConfigError::Generic("IAK template does not match certificate. Check template in configuration.".to_string()))); - } - - (Some(iak), Some(idevid)) - } else { - (None, None) - }; - // Gather EK values and certs let ek_result = match config.agent.ek_handle.as_ref() { "" => ctx.create_ek(tpm_encryption_alg, None)?, @@ -562,13 +434,33 @@ async fn main() -> Result<()> { info!("Agent UUID: {}", agent_uuid); - let (attest, signature) = if config.agent.enable_iak_idevid { - let qualifying_data = config.agent.uuid.as_bytes(); - let (attest, signature) = ctx.certify_credential_with_iak( - Data::try_from(qualifying_data).unwrap(), //#[allow_ci] - ak_handle, - iak.as_ref().unwrap().handle, //#[allow_ci] - )?; + // If using IAK/IDevID is enabled, obtain IAK/IDevID and respective certificates + let mut device_id = if config.agent.enable_iak_idevid { + Some( + DeviceIDBuilder::new() + .iak_handle(&config.agent.iak_handle) + .iak_cert_path(&config.agent.iak_cert) + .iak_password(&config.agent.iak_password) + .iak_template(&config.agent.iak_idevid_template) + .iak_asym_alg(&config.agent.iak_idevid_asymmetric_alg) + .iak_hash_alg(&config.agent.iak_idevid_name_alg) + .idevid_handle(&config.agent.idevid_handle) + .idevid_cert_path(&config.agent.idevid_cert) + .idevid_password(&config.agent.idevid_password) + .idevid_template(&config.agent.iak_idevid_template) + .idevid_asym_alg(&config.agent.iak_idevid_asymmetric_alg) + .idevid_hash_alg(&config.agent.iak_idevid_name_alg) + .build(&mut ctx)?, + ) + } else { + None + }; + + let (attest, signature) = if let Some(dev_id) = &mut device_id { + let qualifying_data = Data::try_from(agent_uuid.as_bytes())?; + let (attest, signature) = + dev_id.certify(qualifying_data, ak_handle, &mut ctx)?; + info!("AK certified with IAK."); // // For debugging certify(), the following checks the generated signature @@ -705,8 +597,8 @@ async fn main() -> Result<()> { { // Request keyblob material let keyblob = if config.agent.enable_iak_idevid { - let (Some(iak), Some(idevid), Some(attest), Some(signature)) = - (iak, idevid, attest, signature) + let (Some(dev_id), Some(attest), Some(signature)) = + (&device_id, attest, signature) else { error!( "IDevID and IAK are enabled but could not be generated" @@ -725,15 +617,15 @@ async fn main() -> Result<()> { ek_result.ek_cert, &PublicBuffer::try_from(ak.public)?.marshall()?, Some( - &PublicBuffer::try_from(iak.public.clone())? + &PublicBuffer::try_from(dev_id.iak.public.clone())? .marshall()?, ), Some( - &PublicBuffer::try_from(idevid.public.clone())? + &PublicBuffer::try_from(dev_id.idevid.public.clone())? .marshall()?, ), - idevid_cert, - iak_cert, + Some(dev_id.idevid_cert.clone()), + Some(dev_id.iak_cert.clone()), Some(attest.marshall()?), Some(signature.marshall()?), mtls_cert, diff --git a/keylime/src/crypto.rs b/keylime/src/crypto.rs index 5564a3673..5c951b1cc 100644 --- a/keylime/src/crypto.rs +++ b/keylime/src/crypto.rs @@ -56,6 +56,10 @@ pub enum CryptoError { #[error("failed to create EcKey structure from public point")] ECKeyFromPublicPointError(#[source] openssl::error::ErrorStack), + /// File not found + #[error("could not find file {0}")] + FileNotFound(String), + /// Error creating file #[error("failed to create file {file}")] FSCreateError { @@ -270,6 +274,15 @@ pub fn load_x509_pem(input_cert_path: &Path) -> Result { X509::from_pem(&contents).map_err(CryptoError::X509FromPEMError) } +/// Load a X509 certificate in PEM or DER format from a given path +pub fn load_x509(path: &Path) -> Result { + if path.exists() { + load_x509_der(path).or(load_x509_pem(path)) + } else { + Err(CryptoError::FileNotFound(path.display().to_string())) + } +} + /// Load X509 certificate chain in PEM format from file fn load_x509_cert_chain( input_cert_path: &Path, @@ -368,7 +381,7 @@ pub fn hash( /// Check an x509 certificate contains a specific public key pub fn check_x509_key( cert: &X509, - tpm_key: tss_esapi::structures::Public, + tpm_key: &tss_esapi::structures::Public, ) -> Result { // Id:RSA_PSS only added in rust-openssl from v0.10.59; remove this let and use Id::RSA_PSS after update // Id taken from https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/nid.h#4039 @@ -389,7 +402,7 @@ pub fn check_x509_key( let mut cert_n_str = format!("{:?}", cert_n); _ = cert_n_str.pop(); _ = cert_n_str.remove(0); - let key = SubjectPublicKeyInfo::try_from(tpm_key) + let key = SubjectPublicKeyInfo::try_from(tpm_key.clone()) .map_err(CryptoError::SubjectPublicKeyInfoFromRSAError)?; let key_der = picky_asn1_der::to_vec(&key) .map_err(CryptoError::SubjectPublicKeyInfoToDERError)?; @@ -408,7 +421,7 @@ pub fn check_x509_key( let mut cert_n_str = format!("{:?}", cert_n); _ = cert_n_str.pop(); _ = cert_n_str.remove(0); - let key = SubjectPublicKeyInfo::try_from(tpm_key) + let key = SubjectPublicKeyInfo::try_from(tpm_key.clone()) .map_err(CryptoError::SubjectPublicKeyInfoFromRSAError)?; let key_der = picky_asn1_der::to_vec(&key) .map_err(CryptoError::SubjectPublicKeyInfoToDERError)?; @@ -427,7 +440,7 @@ pub fn check_x509_key( let mut cert_n_str = format!("{:?}", cert_n); _ = cert_n_str.pop(); _ = cert_n_str.remove(0); - let key = SubjectPublicKeyInfo::try_from(tpm_key) + let key = SubjectPublicKeyInfo::try_from(tpm_key.clone()) .map_err(CryptoError::SubjectPublicKeyInfoFromECCError)?; let key_der = picky_asn1_der::to_vec(&key) .map_err(CryptoError::SubjectPublicKeyInfoToDERError)?; diff --git a/keylime/src/device_id.rs b/keylime/src/device_id.rs new file mode 100644 index 000000000..9006050d4 --- /dev/null +++ b/keylime/src/device_id.rs @@ -0,0 +1,488 @@ +use crate::{ + crypto::{self, CryptoError}, + tpm::{self, IAKResult, IDevIDResult, TpmError}, +}; +use log::*; +use openssl::x509::X509; +use std::path::Path; +use thiserror::Error; +use tss_esapi::{ + handles::KeyHandle, + interface_types::algorithm::{AsymmetricAlgorithm, HashingAlgorithm}, + structures::{Attest, Data, Signature}, +}; + +#[derive(Error, Debug)] +pub enum DeviceIDBuilderError { + /// Failed to load certificate + #[error("Failed to load certificate")] + CertLoad(#[source] crypto::CryptoError), + + /// Public key does not match the certificate + #[error("Public key does not match the certificate")] + CertPubKeyMismatch(#[source] tpm::TpmError), + + /// Failed to create IAK + #[error("Failed to create IAK")] + IAKCreate(#[source] TpmError), + + /// Could not get IAK from provided handle + #[error("Could not get IAK from provided handle")] + IAKFromHandle(#[source] TpmError), + + /// IAK handle not set in DeviceIDBuilder + #[error("IAK handle not set in DeviceBuilder. Set the IAK handle with the iak_handle() method from the DeviceIDBuilder object")] + IAKHandleNotSet, + + /// IAK certificate not set in DeviceIDBuilder + #[error("IAK certificate not set in DeviceBuilder. Set the IAK certificate with the iak_cert() method from the DeviceIDBuilder object")] + IAKCertNotSet, + + /// IAK password not set in DeviceIDBuilder + #[error("IAK password not set in DeviceBuilder. Set the IAK password with the iak_password() method from the DeviceIDBuilder object")] + IAKPasswordNotSet, + + /// Failed to create IDevID + #[error("Failed to create IDevID")] + IDevIDCreate(#[source] TpmError), + + /// Could not get IDevID from provided handle + #[error("Could not get IDevID from provided handle")] + IDevIDFromHandle(#[source] TpmError), + + /// IDevID handle not set in DeviceIDBuilder + #[error("IDevID handle not set in DeviceBuilder. Set the IDevID handle with the idevid_handle() method from the DeviceIDBuilder object")] + IDevIDHandleNotSet, + + /// IDevID certificate not set in DeviceIDBuilder + #[error("IDevID certificate not set in DeviceBuilder. Set the IDevID certificate with the idevid_cert() method from the DeviceIDBuilder object")] + IDevIDCertNotSet, + + /// IDevID password not set in DeviceIDBuilder + #[error("IDevID password not set in DeviceBuilder. Set the IDevID password with the idevid_password() method from the DeviceIDBuilder object")] + IDevIDPasswordNotSet, + + /// Failed to get template from certificate + #[error("Failed to get template from certificate")] + TemplateFromCert(#[source] CryptoError), + + /// Failed to obtain template + #[error("Failed to obtain template")] + Template(#[source] TpmError), +} + +#[derive(Error, Debug)] +pub enum DeviceIDError { + /// Failed to certify + #[error("Failed to certify credential with IAK")] + Certify(#[source] TpmError), +} + +#[derive(Debug, Default)] +pub struct DeviceIDBuilder<'a> { + iak_handle: Option<&'a str>, + iak_password: Option<&'a str>, + iak_cert: Option, + iak_cert_path: Option<&'a str>, + iak_template: Option<&'a str>, + iak_asym_alg: Option<&'a str>, + iak_hash_alg: Option<&'a str>, + idevid_handle: Option<&'a str>, + idevid_password: Option<&'a str>, + idevid_cert: Option, + idevid_cert_path: Option<&'a str>, + idevid_template: Option<&'a str>, + idevid_asym_alg: Option<&'a str>, + idevid_hash_alg: Option<&'a str>, +} + +impl<'a> DeviceIDBuilder<'a> { + /// Create a new DeviceIDBuilder object + pub fn new() -> DeviceIDBuilder<'a> { + Self::default() + } + + /// Set the IAK handle to use when building the DeviceID object + /// + /// # Arguments: + /// + /// * iak_handle (&str): The IAK handle + pub fn iak_handle(mut self, iak_handle: &'a str) -> DeviceIDBuilder<'a> { + self.iak_handle = Some(iak_handle); + self + } + + /// Set the IAK password to use when building the DeviceID object + /// + /// # Arguments: + /// + /// * iak_password (&str): The IAK password + pub fn iak_password( + mut self, + iak_password: &'a str, + ) -> DeviceIDBuilder<'a> { + self.iak_password = Some(iak_password); + self + } + + /// Set the path to the IAK certificate to use when building the DeviceID object + /// + /// # Arguments: + /// + /// * path (&str): The path to the IAK certificate + pub fn iak_cert_path(mut self, path: &'a str) -> DeviceIDBuilder<'a> { + self.iak_cert_path = Some(path); + self + } + + /// Set the template to use for the IAK + /// + /// If the string is empty, or the special keywords 'detect' or 'default' are set, the template + /// will be obtained from the IAK certificate provided in `iak_cert` + /// + /// # Arguments: + /// + /// * template(&str): The template name to use. + pub fn iak_template(mut self, template: &'a str) -> DeviceIDBuilder<'a> { + self.iak_template = Some(template); + self + } + + /// Set the Asymmetric algorithm to use in the IAK template + /// + /// # Arguments: + /// + /// * iak_asym_alg (&str): The template Asymmetric algorithm to use for the IAK template + pub fn iak_asym_alg(mut self, asym_alg: &'a str) -> DeviceIDBuilder<'a> { + self.iak_asym_alg = Some(asym_alg); + self + } + + /// Set the template Hash algorithm to use in the IAK template + /// + /// # Arguments: + /// + /// * iak_hash_alg (&str): The Hash algorithm to use for the IAK template + pub fn iak_hash_alg(mut self, hash_alg: &'a str) -> DeviceIDBuilder<'a> { + self.iak_hash_alg = Some(hash_alg); + self + } + + /// Set the IDevID handle to use when building the DeviceID object + /// + /// # Arguments: + /// + /// * idevid_handle (&str): The IDevID handle + pub fn idevid_handle( + mut self, + idevid_handle: &'a str, + ) -> DeviceIDBuilder<'a> { + self.idevid_handle = Some(idevid_handle); + self + } + + /// Set the IDevID password to use when building the DeviceID object + /// + /// # Arguments: + /// + /// * idevid_password (&str): The password to use for the IDevID + pub fn idevid_password( + mut self, + idevid_password: &'a str, + ) -> DeviceIDBuilder<'a> { + self.idevid_password = Some(idevid_password); + self + } + + /// Set the IDevID certificate path to use when building the DeviceID object + /// + /// # Arguments: + /// + /// * path (&str): The path to the IDevID certificate + pub fn idevid_cert_path(mut self, path: &'a str) -> DeviceIDBuilder<'a> { + self.idevid_cert_path = Some(path); + self + } + + /// Set the template to use for the IDevID + /// + /// If the string is empty, or the special keywords 'detect' or 'default' are set, the template + /// will be obtained from the IDevID certificate provided in `idevid_cert` + /// + /// # Arguments: + /// + /// * template(&str): The template name to use. + pub fn idevid_template( + mut self, + template: &'a str, + ) -> DeviceIDBuilder<'a> { + self.idevid_template = Some(template); + self + } + + /// Set the Asymmetric algorithm to use in the IDevID template + /// + /// # Arguments: + /// + /// * idevid_asym_alg (&str): The template Asymmetric algorithm to use for the IDevID template + pub fn idevid_asym_alg( + mut self, + asym_alg: &'a str, + ) -> DeviceIDBuilder<'a> { + self.idevid_asym_alg = Some(asym_alg); + self + } + + /// Set the template Hash algorithm to use in the IDevID template + /// + /// # Arguments: + /// + /// * idevid_hash_alg (&str): The Hash algorithm to use for the IDevID template + pub fn idevid_hash_alg( + mut self, + hash_alg: &'a str, + ) -> DeviceIDBuilder<'a> { + self.idevid_hash_alg = Some(hash_alg); + self + } + + /// Get the IAK template + /// + /// If configured to detect, get the template from the IAK certificate + /// Otherwise, construct the template from the provided algorithms + fn get_iak_template( + &mut self, + ) -> Result<(AsymmetricAlgorithm, HashingAlgorithm), DeviceIDBuilderError> + { + let iak_cert = self.get_iak_cert()?; + let detected = &crypto::match_cert_to_template(iak_cert) + .map_err(DeviceIDBuilderError::TemplateFromCert)?; + + let template = self.iak_template.unwrap_or("").trim(); + let asym_alg = self.iak_asym_alg.unwrap_or("").trim(); + let hash_alg = self.iak_hash_alg.unwrap_or("").trim(); + + tpm::get_idevid_template(detected, template, asym_alg, hash_alg) + .map_err(DeviceIDBuilderError::Template) + } + + /// Get the IDevID template + /// + /// If configured to detect, get the template from the IDevID certificate + /// Otherwise, construct the template from the provided algorithms + fn get_idevid_template( + &mut self, + ) -> Result<(AsymmetricAlgorithm, HashingAlgorithm), DeviceIDBuilderError> + { + let idevid_cert = self.get_idevid_cert()?; + let detected = &crypto::match_cert_to_template(idevid_cert) + .map_err(DeviceIDBuilderError::TemplateFromCert)?; + + let template = self.idevid_template.unwrap_or("").trim(); + let asym_alg = self.idevid_asym_alg.unwrap_or("").trim(); + let hash_alg = self.idevid_hash_alg.unwrap_or("").trim(); + + tpm::get_idevid_template(detected, template, asym_alg, hash_alg) + .map_err(DeviceIDBuilderError::Template) + } + + /// Get the IAK from the given handle and password or recreate following the given template + /// + /// If a handle has been set, try to obtain the IAK from the handle + /// If there is a configured IAK password, add the password to the handle + /// + /// If the handle is empty, recreate the IAK using the provided algorithms + fn get_iak( + &mut self, + tpm_ctx: &mut tpm::Context, + ) -> Result { + let (asym_alg, hash_alg) = self.get_iak_template()?; + match self.iak_handle { + Some(handle) => { + if handle.trim().is_empty() { + info!("Recreating IAK."); + tpm_ctx + .create_iak(asym_alg, hash_alg) + .map_err(DeviceIDBuilderError::IAKCreate) + } else { + let password = self.iak_password.unwrap_or("").trim(); + info!("Collecting persisted IAK."); + tpm_ctx + .iak_from_handle(handle.trim(), password) + .map_err(DeviceIDBuilderError::IAKFromHandle) + } + } + None => Err(DeviceIDBuilderError::IAKHandleNotSet), + } + } + + /// Get the IDevID from the given handle and password or recreate following the given template + /// + /// If a handle has been set, try to obtain the IDevID from the handle + /// If there is a configured IDevID password, add the password to the handle + /// + /// If the handle is empty, recreate the IDevId using the provided algorithms + fn get_idevid( + &mut self, + tpm_ctx: &mut tpm::Context, + ) -> Result { + let (asym_alg, hash_alg) = self.get_idevid_template()?; + match self.idevid_handle { + Some(handle) => { + if handle.trim().is_empty() { + info!("Recreating IDevID."); + tpm_ctx + .create_idevid(asym_alg, hash_alg) + .map_err(DeviceIDBuilderError::IDevIDCreate) + } else { + let password = self.idevid_password.unwrap_or("").trim(); + info!("Collecting persisted IDevID."); + tpm_ctx + .idevid_from_handle(handle.trim(), password.trim()) + .map_err(DeviceIDBuilderError::IDevIDFromHandle) + } + } + None => Err(DeviceIDBuilderError::IDevIDHandleNotSet), + } + } + + /// Get the IAK certificate + /// If the iak_cert is not set, try to load the certificate from the iak_cert_path and cache + /// the loaded certificate + fn get_iak_cert(&mut self) -> Result<&X509, DeviceIDBuilderError> { + match self.iak_cert { + Some(ref cert) => Ok(cert), + None => match &self.iak_cert_path { + Some(path) => { + if path.trim().is_empty() { + debug!( + "The IAK certificate was not set in the configuration file" + ); + Err(DeviceIDBuilderError::IAKCertNotSet) + } else { + self.iak_cert = Some(crypto::load_x509(Path::new(path.trim())).map_err(|e| { + debug!("Could not load IAK certificate from {path}: {e}"); + e + }).map_err(DeviceIDBuilderError::CertLoad)?); + if let Some(ref cert) = &self.iak_cert { + Ok(cert) + } else { + unreachable!(); + } + } + } + None => Err(DeviceIDBuilderError::IAKCertNotSet), + }, + } + } + + /// Get the IDevID certificate + /// + /// If the idevid_cert is not set, try to load the certificate from the idevid_cert_path and cache the loaded certificate + fn get_idevid_cert(&mut self) -> Result<&X509, DeviceIDBuilderError> { + match self.idevid_cert { + Some(ref cert) => Ok(cert), + None => match self.idevid_cert_path { + Some(path) => { + if path.trim().is_empty() { + debug!( + "The IDevId certificate was not set in the configuration file" + ); + Err(DeviceIDBuilderError::IDevIDCertNotSet) + } else { + self.idevid_cert = Some(crypto::load_x509(Path::new(path.trim())).map_err(|e| { + debug!("Could not load IAK certificate from {path}: {e}"); + e + }).map_err(DeviceIDBuilderError::CertLoad)?); + if let Some(ref cert) = self.idevid_cert { + Ok(cert) + } else { + unreachable!(); + } + } + } + None => Err(DeviceIDBuilderError::IDevIDCertNotSet), + }, + } + } + + /// Generate the DeviceID object using the previously set options + pub fn build( + mut self, + tpm_ctx: &mut tpm::Context, + ) -> Result { + let iak = self.get_iak(tpm_ctx)?; + let Some(iak_cert) = self.iak_cert.take() else { + unreachable!(); + }; + + // Check that recreated/collected IAK key matches the one in the certificate + tpm::check_pubkey_match_cert(&iak.public, &iak_cert, "IAK") + .map_err(DeviceIDBuilderError::CertPubKeyMismatch)?; + + let idevid = self.get_idevid(tpm_ctx)?; + let Some(idevid_cert) = self.idevid_cert.take() else { + unreachable!(); + }; + + // Check that recreated/collected IDevID key matches the one in the certificate + tpm::check_pubkey_match_cert(&idevid.public, &idevid_cert, "IDevID") + .map_err(DeviceIDBuilderError::CertPubKeyMismatch)?; + + Ok(DeviceID { + iak, + iak_cert, + idevid, + idevid_cert, + }) + } +} + +#[derive(Debug)] +pub struct DeviceID { + pub iak: IAKResult, + pub iak_cert: X509, + pub idevid: IDevIDResult, + pub idevid_cert: X509, +} + +impl DeviceID { + /// Certify IAK against AK using the provided qualifying data + /// + /// # Arguments: + /// + /// * qualifying_data (Data): The qualifying data. + /// * ak (KeyHandle): The AK handle + pub fn certify( + &mut self, + qualifying_data: Data, + ak: KeyHandle, + tpm_ctx: &mut tpm::Context, + ) -> Result<(Attest, Signature), DeviceIDError> { + tpm_ctx + .certify_credential_with_iak(qualifying_data, ak, self.iak.handle) + .map_err(DeviceIDError::Certify) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_device_id_builder_setting() { + let _builder = DeviceIDBuilder::new() + .iak_handle("") + .iak_cert_path("") + .iak_password("") + .iak_template("") + .iak_asym_alg("") + .iak_hash_alg("") + .idevid_handle("") + .idevid_cert_path("") + .idevid_password("") + .idevid_template("") + .idevid_asym_alg("") + .idevid_hash_alg(""); + } +} diff --git a/keylime/src/lib.rs b/keylime/src/lib.rs index 791729e13..1d10e02bc 100644 --- a/keylime/src/lib.rs +++ b/keylime/src/lib.rs @@ -1,5 +1,6 @@ pub mod algorithms; pub mod crypto; +pub mod device_id; pub mod hostname_parser; pub mod ima; pub mod ip_parser; diff --git a/keylime/src/tpm.rs b/keylime/src/tpm.rs index dafddf022..372483649 100644 --- a/keylime/src/tpm.rs +++ b/keylime/src/tpm.rs @@ -1,8 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2021 Keylime Authors -use crate::algorithms::{ - AlgorithmError, EncryptionAlgorithm, HashAlgorithm, SignAlgorithm, +use crate::{ + algorithms::{ + AlgorithmError, EncryptionAlgorithm, HashAlgorithm, SignAlgorithm, + }, + crypto, }; use base64::{engine::general_purpose, Engine as _}; use log::*; @@ -18,6 +21,7 @@ use openssl::{ hash::{Hasher, MessageDigest}, memcmp, pkey::{HasPublic, Id, PKeyRef, Public}, + x509::X509, }; use tss_esapi::{ @@ -50,10 +54,11 @@ use tss_esapi::{ Attest, AttestInfo, Auth, Data, Digest, DigestValues, EccParameter, EccPoint, EccScheme, EncryptedSecret, HashScheme, IdObject, KeyDerivationFunctionScheme, Name, PcrSelectionList, - PcrSelectionListBuilder, PcrSlot, PublicBuilder, - PublicEccParametersBuilder, PublicKeyRsa, PublicRsaParametersBuilder, - RsaExponent, RsaScheme, Signature, SignatureScheme, - SymmetricDefinitionObject, Ticket, VerifiedTicket, + PcrSelectionListBuilder, PcrSlot, Private as TssPrivate, + Public as TssPublic, PublicBuilder, PublicEccParametersBuilder, + PublicKeyRsa, PublicRsaParametersBuilder, RsaExponent, RsaScheme, + Signature, SignatureScheme, SymmetricDefinitionObject, Ticket, + VerifiedTicket, }, tcti_ldr::TctiNameConf, traits::Marshall, @@ -114,6 +119,10 @@ const UNIQUE_IAK: [u8; 3] = [0x49, 0x41, 0x4b]; /// TpmError wraps all possible errors raised in tpm.rs #[derive(Error, Debug)] pub enum TpmError { + /// Public key does not match with certificate + #[error("{0} key does not match with certificate. Check template in configuration.")] + PublicKeyCertificateMismatch(String), + /// Unsupported hashing algorithm error #[error("Unsupported hashing algorithm : {alg:?}")] UnsupportedHashingAlgorithm { alg: HashingAlgorithm }, @@ -405,6 +414,10 @@ pub enum TpmError { #[error("AlgorithmError")] AlgorithmError(#[from] AlgorithmError), + /// Generic catch-all crypto error + #[error("CryptoError")] + CryptoError(#[from] crypto::CryptoError), + /// Generic catch-all error #[error("{0}")] Other(String), @@ -430,40 +443,40 @@ type Result = std::result::Result; pub struct EKResult { pub key_handle: KeyHandle, pub ek_cert: Option>, - pub public: tss_esapi::structures::Public, + pub public: TssPublic, } /// Holds the output of create_ak. #[derive(Clone, Debug)] pub struct AKResult { - pub public: tss_esapi::structures::Public, - pub private: tss_esapi::structures::Private, + pub public: TssPublic, + pub private: TssPrivate, } /// Holds the output of create_iak. #[derive(Clone, Debug)] pub struct IAKResult { - pub public: tss_esapi::structures::Public, + pub public: TssPublic, pub handle: tss_esapi::handles::KeyHandle, } /// Holds the output of create_idevid. #[derive(Clone, Debug)] pub struct IDevIDResult { - pub public: tss_esapi::structures::Public, + pub public: TssPublic, pub handle: tss_esapi::handles::KeyHandle, } /// Holds the Public result from create_idevid_public_from_default_template #[derive(Clone, Debug)] pub struct IDevIDPublic { - pub public: tss_esapi::structures::Public, + pub public: TssPublic, } /// Holds the Public result from create_iak_public_from_default_template #[derive(Clone, Debug)] pub struct IAKPublic { - pub public: tss_esapi::structures::Public, + pub public: TssPublic, } /// Wrapper around tss_esapi::Context. @@ -1911,6 +1924,23 @@ pub fn get_idevid_template( Ok((asym_alg, name_alg)) } +/// Check if a public key and certificate match +/// +/// The provided label is used to generate logging messages +pub fn check_pubkey_match_cert( + pubkey: &TssPublic, + certificate: &X509, + label: &str, +) -> Result<()> { + if crypto::check_x509_key(certificate, pubkey)? { + info!("{label} public key matches certificate."); + Ok(()) + } else { + error!("{label} public key does not match certificate. Check template in configuration."); + Err(TpmError::PublicKeyCertificateMismatch(label.to_string())) + } +} + pub mod testing { use super::*; #[cfg(feature = "testing")] From afa60ab1c158a0aea01374da7f6352156dac1993 Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Wed, 27 Nov 2024 10:59:12 +0100 Subject: [PATCH 2/5] tests: Add unit test for device ID builder This makes the tests/run.sh script to generate the IAK and IDevID certificates if the tpm2-openssl provider is available. The added test is executed only if both the IAK and IDevID certificates are available. Signed-off-by: Anderson Toshiyuki Sasaki --- keylime/src/device_id.rs | 47 ++++++++++++++++++++++++++++++ tests/generate-iak-idevid-certs.sh | 2 +- tests/run.sh | 45 +++++++++++++++++++++++----- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/keylime/src/device_id.rs b/keylime/src/device_id.rs index 9006050d4..1f68fd3e3 100644 --- a/keylime/src/device_id.rs +++ b/keylime/src/device_id.rs @@ -485,4 +485,51 @@ mod tests { .idevid_asym_alg("") .idevid_hash_alg(""); } + + #[tokio::test] + #[cfg(feature = "testing")] + async fn test_device_id_builder() { + let _mutex = tpm::testing::lock_tests().await; + let certs_dir = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("test-data") + .join("iak-idevid-certs"); + + if certs_dir.exists() { + let iak_cert = certs_dir.join("iak.cert.pem"); + let idevid_cert = certs_dir.join("idevid.cert.pem"); + if iak_cert.exists() && idevid_cert.exists() { + let mut tpm_ctx = tpm::Context::new().unwrap(); //#[allow_ci] + let result = DeviceIDBuilder::new() + .iak_handle("") + .iak_cert_path( + iak_cert + .to_str() + .expect("Failed to get str for IAK cert"), + ) + .iak_password("") + .iak_template("") + .iak_asym_alg("") + .iak_hash_alg("") + .idevid_handle("") + .idevid_cert_path( + idevid_cert + .to_str() + .expect("Failed to get str for IDevID cert"), + ) + .idevid_password("") + .idevid_template("") + .idevid_asym_alg("") + .idevid_hash_alg("") + .build(&mut tpm_ctx); + assert!(result.is_ok(), "Result: {result:?}"); + let dev_id = result.unwrap(); //#[allow_ci] + + // Flush context to free TPM memory + let r = tpm_ctx.flush_context(dev_id.iak.handle.into()); + assert!(r.is_ok(), "Result: {r:?}"); + let r = tpm_ctx.flush_context(dev_id.idevid.handle.into()); + assert!(r.is_ok(), "Result: {r:?}"); + } + } + } } diff --git a/tests/generate-iak-idevid-certs.sh b/tests/generate-iak-idevid-certs.sh index 894bf41d2..a619c95ff 100755 --- a/tests/generate-iak-idevid-certs.sh +++ b/tests/generate-iak-idevid-certs.sh @@ -159,7 +159,7 @@ pushd "${OUTPUTDIR}" > /dev/null || exit 1 -out cacert.pem popd > /dev/null || exit 1 cat intermediate/cacert.pem root/cacert.pem \ - > cert-chain.pem + > ca-cert-chain.pem popd > /dev/null || exit 1 mkdir "${OUTPUTDIR}/ikeys" diff --git a/tests/run.sh b/tests/run.sh index 3a0c6b5d9..09714d80d 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -2,6 +2,19 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2021 Keylime Authors +# Check that the script is running from inside the repository tree +GIT_ROOT=$(git rev-parse --show-toplevel) || { + echo "Please run this script from inside the rust-keylime repository tree" + exit 1 +} + +TESTS_DIR="${GIT_ROOT}/tests" +TEST_DATA_DIR="${GIT_ROOT}/test-data" +TPMDIR="${TEST_DATA_DIR}/tpm-state" + +# These certificates are used for the keylime/device_id tests +IAK_IDEVID_CERTS="${GIT_ROOT}/keylime/test-data/iak-idevid-certs" + # Store the old TCTI setting OLD_TCTI=$TCTI OLD_TPM2TOOLS_TCTI=$TPM2TOOLS_TCTI @@ -11,14 +24,13 @@ set -euf -o pipefail echo "-------- Setting up Software TPM" -# Create temporary directories -TEMPDIR=$(mktemp -d) -TPMDIR="${TEMPDIR}/tpmdir" -mkdir -p ${TPMDIR} +if [[ ! -d "${TPMDIR}" ]]; then + mkdir -p "${TPMDIR}" +fi # Manufacture a new Software TPM swtpm_setup --tpm2 \ - --tpmstate ${TPMDIR} \ + --tpmstate "${TPMDIR}" \ --createek --decryption --create-ek-cert \ --create-platform-cert \ --lock-nvram \ @@ -29,7 +41,7 @@ swtpm_setup --tpm2 \ function start_swtpm { # Initialize the swtpm socket swtpm socket --tpm2 \ - --tpmstate dir=${TPMDIR} \ + --tpmstate dir="${TPMDIR}" \ --flags startup-clear \ --ctrl type=tcp,port=2322 \ --server type=tcp,port=2321 \ @@ -39,7 +51,7 @@ function start_swtpm { function stop_swtpm { # Stop swtpm if running - if [[ -n "$SWTPM_PID" ]]; then + if [[ -n "${SWTPM_PID}" ]]; then echo "Stopping swtpm" kill $SWTPM_PID fi @@ -72,6 +84,25 @@ RUST_BACKTRACE=1 cargo build echo "-------- Testing" start_swtpm + + +# Check that tpm2-openssl provider is available +if openssl list -provider tpm2 -providers > /dev/null; then + # If any IAK/IDevID related certificate is missing, re-generate them + if [[ ( ! -f "${IAK_IDEVID_CERTS}/iak.cert.pem" ) || + ( ! -f "${IAK_IDEVID_CERTS}/iak.cert.der" ) || + ( ! -f "${IAK_IDEVID_CERTS}/idevid.cert.pem" ) || + ( ! -f "${IAK_IDEVID_CERTS}/idevid.cert.der" ) || + ( ! -f "${IAK_IDEVID_CERTS}/ca-cert-chain.pem" ) ]] + then + # Remove any leftover from old certificates + rm -rf "${IAK_IDEVID_CERTS}" + mkdir -p "${IAK_IDEVID_CERTS}" + echo "-------- Create IAK/IDevID certificates" + "${GIT_ROOT}/tests/generate-iak-idevid-certs.sh" -o "${IAK_IDEVID_CERTS}" + fi +fi + mkdir -p /var/lib/keylime RUST_BACKTRACE=1 RUST_LOG=info \ KEYLIME_CONFIG=$PWD/keylime-agent.conf \ From 90c2794a0b6b85967b214f340767b9c808ff899a Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Mon, 9 Dec 2024 12:27:40 +0100 Subject: [PATCH 3/5] workflows: Run job in the CI container directly Instead of invoking docker ourselves, set the github job to run in the CI container directly. This also adds a workaround for: https://github.com/actions/runner/issues/2033 Signed-off-by: Anderson Toshiyuki Sasaki --- .github/workflows/rust.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d59bdf191..9d52accb8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,10 +24,14 @@ jobs: tests: name: Fedora tests runs-on: ubuntu-latest + container: + image: quay.io/keylime/keylime-ci:latest steps: - uses: actions/checkout@v4 + - name: Set git safe.directory for the working directory + run : git config --system --add safe.directory "$PWD" - name: Run tests - run: docker run --security-opt seccomp=tests/seccomp-profile.json -v $(pwd):/tmp/code_under_test -w /tmp/code_under_test quay.io/keylime/keylime-ci:latest dbus-run-session -- /tmp/code_under_test/tests/run.sh + run: bash tests/run.sh - uses: actions/upload-artifact@v4 with: name: tarpaulin-report From 1e90317a0930371bf5e3bd33564f50b70949e798 Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Tue, 10 Dec 2024 13:26:33 +0100 Subject: [PATCH 4/5] Fix warnings reported by clippy Signed-off-by: Anderson Toshiyuki Sasaki --- keylime-agent/src/main.rs | 2 +- keylime/src/crypto/x509.rs | 18 +++++++++--------- keylime/src/hostname_parser.rs | 2 +- keylime/src/ip_parser.rs | 2 +- keylime/src/tpm.rs | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs index 58ce14a5a..2d7a11bf4 100644 --- a/keylime-agent/src/main.rs +++ b/keylime-agent/src/main.rs @@ -982,7 +982,7 @@ mod testing { TSSError(#[from] tss_esapi::Error), } - impl<'a> Drop for QuoteData<'a> { + impl Drop for QuoteData<'_> { /// Flush the created AK when dropping fn drop(&mut self) { self.tpmcontext diff --git a/keylime/src/crypto/x509.rs b/keylime/src/crypto/x509.rs index 37bcea592..71d57e7ae 100644 --- a/keylime/src/crypto/x509.rs +++ b/keylime/src/crypto/x509.rs @@ -75,7 +75,7 @@ impl<'a> CertificateBuilder<'a> { pub fn common_name( &'a mut self, cn: &'a str, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { self.common_name = Some(cn); self } @@ -88,7 +88,7 @@ impl<'a> CertificateBuilder<'a> { pub fn hash_algorithm( &'a mut self, hash_algorithm: MessageDigest, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { self.hash_algorithm = Some(hash_algorithm); self } @@ -102,7 +102,7 @@ impl<'a> CertificateBuilder<'a> { pub fn not_before( &'a mut self, days_from_now: u32, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { self.not_before = Some(days_from_now); self } @@ -115,7 +115,7 @@ impl<'a> CertificateBuilder<'a> { pub fn not_after( &'a mut self, days_from_now: u32, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { self.not_after = Some(days_from_now); self } @@ -131,7 +131,7 @@ impl<'a> CertificateBuilder<'a> { pub fn version( &'a mut self, version: i32, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { self.version = Some(version); self } @@ -144,7 +144,7 @@ impl<'a> CertificateBuilder<'a> { pub fn private_key( &'a mut self, private_key: &'a PKey, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { self.private_key = Some(private_key); self } @@ -158,7 +158,7 @@ impl<'a> CertificateBuilder<'a> { pub fn add_dns_names( &'a mut self, dns_names: Vec<&'a str>, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { match &mut self.dns_names { None => { self.dns_names = Some(dns_names); @@ -181,7 +181,7 @@ impl<'a> CertificateBuilder<'a> { pub fn add_ips( &'a mut self, ips: Vec<&'a str>, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { match &mut self.ips { None => { self.ips = Some(ips); @@ -204,7 +204,7 @@ impl<'a> CertificateBuilder<'a> { pub fn add_extensions( &'a mut self, extensions: Vec, - ) -> &mut CertificateBuilder<'a> { + ) -> &'a mut CertificateBuilder<'a> { match &mut self.extensions { None => { self.extensions = Some(extensions); diff --git a/keylime/src/hostname_parser.rs b/keylime/src/hostname_parser.rs index dbf971a65..650e8277d 100644 --- a/keylime/src/hostname_parser.rs +++ b/keylime/src/hostname_parser.rs @@ -60,7 +60,7 @@ pub fn parse_hostname(hostname: &str) -> Result<&str, HostnameParsingError> { else { return Err(HostnameParsingError::InvalidInput(hostname.to_string())); }; - return Ok(pair.as_str()); + Ok(pair.as_str()) } // Unit Testing diff --git a/keylime/src/ip_parser.rs b/keylime/src/ip_parser.rs index 94b3aa7d2..31c6c180b 100644 --- a/keylime/src/ip_parser.rs +++ b/keylime/src/ip_parser.rs @@ -85,7 +85,7 @@ pub fn parse_ip(ip: &str) -> Result<&str, IpParsingError> { else { return Err(IpParsingError::InvalidInput(ip.to_string())); }; - return get_inner_ip(pair); + get_inner_ip(pair) } // Unit Testing diff --git a/keylime/src/tpm.rs b/keylime/src/tpm.rs index 372483649..153c36657 100644 --- a/keylime/src/tpm.rs +++ b/keylime/src/tpm.rs @@ -487,7 +487,7 @@ pub struct Context<'a> { static TPM_CTX: OnceLock>> = OnceLock::new(); -impl<'a> Context<'a> { +impl Context<'_> { /// Creates a connection context. pub fn new() -> Result { let tcti_path = match std::env::var("TCTI") { From e7ca79984e1ca4a608f76f46716e0b0398748916 Mon Sep 17 00:00:00 2001 From: Anderson Toshiyuki Sasaki Date: Wed, 11 Dec 2024 18:10:25 +0100 Subject: [PATCH 5/5] config: Make IAK and IDevID certificates optional When IAK/IDevID are enabled, but the paths to the certificates are explicitly configured as the empty string, continue normally and register without IAK and IDevID certificates. This is to make it possible to use IAK and IDevID without the certificates, in case the user does the public key matching check separately. Signed-off-by: Anderson Toshiyuki Sasaki --- keylime-agent/src/config.rs | 26 +++- keylime-agent/src/main.rs | 48 ++++--- keylime/src/device_id.rs | 259 ++++++++++++++++++++++++++++-------- keylime/src/tpm.rs | 6 + 4 files changed, 259 insertions(+), 80 deletions(-) diff --git a/keylime-agent/src/config.rs b/keylime-agent/src/config.rs index 32eee7c6f..d5209dd54 100644 --- a/keylime-agent/src/config.rs +++ b/keylime-agent/src/config.rs @@ -501,6 +501,7 @@ fn config_translate_keywords( &config.agent.agent_data_path, keylime_dir, DEFAULT_AGENT_DATA_PATH, + false, ); let mut ima_ml_path = config_get_file_path( @@ -508,6 +509,7 @@ fn config_translate_keywords( &config.agent.ima_ml_path, root_path, DEFAULT_IMA_ML_PATH, + false, ); let mut measuredboot_ml_path = config_get_file_path( @@ -515,6 +517,7 @@ fn config_translate_keywords( &config.agent.measuredboot_ml_path, root_path, DEFAULT_MEASUREDBOOT_ML_PATH, + false, ); let mut server_key = config_get_file_path( @@ -522,6 +525,7 @@ fn config_translate_keywords( &config.agent.server_key, keylime_dir, DEFAULT_SERVER_KEY, + false, ); let mut server_cert = config_get_file_path( @@ -529,6 +533,7 @@ fn config_translate_keywords( &config.agent.server_cert, keylime_dir, DEFAULT_SERVER_CERT, + false, ); let trusted_client_ca: String = @@ -540,6 +545,7 @@ fn config_translate_keywords( t, keylime_dir, DEFAULT_TRUSTED_CLIENT_CA, + false, ) }) .collect::>() @@ -550,6 +556,7 @@ fn config_translate_keywords( &config.agent.iak_cert, keylime_dir, DEFAULT_IAK_CERT, + true, ); let mut idevid_cert = config_get_file_path( @@ -557,6 +564,7 @@ fn config_translate_keywords( &config.agent.idevid_cert, keylime_dir, DEFAULT_IDEVID_CERT, + true, ); let ek_handle = match config.agent.ek_handle.as_ref() { @@ -630,6 +638,7 @@ fn config_translate_keywords( &config.agent.revocation_cert, keylime_dir, &format!("secure/unzipped/{DEFAULT_REVOCATION_CERT}"), + false, ); Ok(KeylimeConfig { @@ -657,7 +666,7 @@ fn config_translate_keywords( /// Expand a file path from the configuration file. /// /// If the string is set as "default", return the provided default path relative from the provided work_dir. -/// If the string is empty, use again the default value +/// If the string is empty, use the default value unless the 'leave_empty' is 'true' /// If the string is a relative path, return the path relative from the provided work_dir /// If the string is an absolute path, return the path without change. fn config_get_file_path( @@ -665,10 +674,15 @@ fn config_get_file_path( path: &str, work_dir: &Path, default: &str, + leave_empty: bool, ) -> String { match path { "default" => work_dir.join(default).display().to_string(), "" => { + if leave_empty { + return "".to_string(); + } + warn!("Empty string provided in configuration option {option}, using default {default}"); work_dir.join(default).display().to_string() } @@ -1107,7 +1121,7 @@ mod tests { let translated: Vec = list .iter() - .map(|e| config_get_file_path("test", e, workdir, default)) + .map(|e| config_get_file_path("test", e, workdir, default, false)) .collect(); assert_eq!( @@ -1122,5 +1136,13 @@ mod tests { ], translated ); + + let translated = + config_get_file_path("test", "", workdir, "default", true); + assert_eq!("", translated); + + let translated = + config_get_file_path("test", "", workdir, "default", false); + assert_eq!("/workdir/default", translated); } } diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs index 2d7a11bf4..1291c9b3c 100644 --- a/keylime-agent/src/main.rs +++ b/keylime-agent/src/main.rs @@ -436,22 +436,30 @@ async fn main() -> Result<()> { // If using IAK/IDevID is enabled, obtain IAK/IDevID and respective certificates let mut device_id = if config.agent.enable_iak_idevid { - Some( - DeviceIDBuilder::new() - .iak_handle(&config.agent.iak_handle) - .iak_cert_path(&config.agent.iak_cert) - .iak_password(&config.agent.iak_password) - .iak_template(&config.agent.iak_idevid_template) - .iak_asym_alg(&config.agent.iak_idevid_asymmetric_alg) - .iak_hash_alg(&config.agent.iak_idevid_name_alg) - .idevid_handle(&config.agent.idevid_handle) - .idevid_cert_path(&config.agent.idevid_cert) - .idevid_password(&config.agent.idevid_password) - .idevid_template(&config.agent.iak_idevid_template) - .idevid_asym_alg(&config.agent.iak_idevid_asymmetric_alg) - .idevid_hash_alg(&config.agent.iak_idevid_name_alg) - .build(&mut ctx)?, - ) + let mut builder = DeviceIDBuilder::new() + .iak_handle(&config.agent.iak_handle) + .iak_password(&config.agent.iak_password) + .iak_default_template(config::DEFAULT_IAK_IDEVID_TEMPLATE) + .iak_template(&config.agent.iak_idevid_template) + .iak_asym_alg(&config.agent.iak_idevid_asymmetric_alg) + .iak_hash_alg(&config.agent.iak_idevid_name_alg) + .idevid_handle(&config.agent.idevid_handle) + .idevid_cert_path(&config.agent.idevid_cert) + .idevid_password(&config.agent.idevid_password) + .idevid_default_template(config::DEFAULT_IAK_IDEVID_TEMPLATE) + .idevid_template(&config.agent.iak_idevid_template) + .idevid_asym_alg(&config.agent.iak_idevid_asymmetric_alg) + .idevid_hash_alg(&config.agent.iak_idevid_name_alg); + + if !&config.agent.iak_cert.is_empty() { + builder = builder.iak_cert_path(&config.agent.iak_cert); + } + + if !&config.agent.idevid_cert.is_empty() { + builder = builder.idevid_cert_path(&config.agent.idevid_cert); + } + + Some(builder.build(&mut ctx)?) } else { None }; @@ -617,15 +625,15 @@ async fn main() -> Result<()> { ek_result.ek_cert, &PublicBuffer::try_from(ak.public)?.marshall()?, Some( - &PublicBuffer::try_from(dev_id.iak.public.clone())? + &PublicBuffer::try_from(dev_id.iak_pubkey.clone())? .marshall()?, ), Some( - &PublicBuffer::try_from(dev_id.idevid.public.clone())? + &PublicBuffer::try_from(dev_id.idevid_pubkey.clone())? .marshall()?, ), - Some(dev_id.idevid_cert.clone()), - Some(dev_id.iak_cert.clone()), + dev_id.idevid_cert.clone(), + dev_id.iak_cert.clone(), Some(attest.marshall()?), Some(signature.marshall()?), mtls_cert, diff --git a/keylime/src/device_id.rs b/keylime/src/device_id.rs index 1f68fd3e3..38a61070f 100644 --- a/keylime/src/device_id.rs +++ b/keylime/src/device_id.rs @@ -9,7 +9,7 @@ use thiserror::Error; use tss_esapi::{ handles::KeyHandle, interface_types::algorithm::{AsymmetricAlgorithm, HashingAlgorithm}, - structures::{Attest, Data, Signature}, + structures::{Attest, Data, Public as TssPublic, Signature}, }; #[derive(Error, Debug)] @@ -22,10 +22,18 @@ pub enum DeviceIDBuilderError { #[error("Public key does not match the certificate")] CertPubKeyMismatch(#[source] tpm::TpmError), + /// Failed to flush context + #[error("Failed to flush context")] + FlushContext(#[source] TpmError), + /// Failed to create IAK #[error("Failed to create IAK")] IAKCreate(#[source] TpmError), + /// IAK default Template not set + #[error("IAK default template not set")] + IAKDefaultTemplateNotSet, + /// Could not get IAK from provided handle #[error("Could not get IAK from provided handle")] IAKFromHandle(#[source] TpmError), @@ -34,10 +42,6 @@ pub enum DeviceIDBuilderError { #[error("IAK handle not set in DeviceBuilder. Set the IAK handle with the iak_handle() method from the DeviceIDBuilder object")] IAKHandleNotSet, - /// IAK certificate not set in DeviceIDBuilder - #[error("IAK certificate not set in DeviceBuilder. Set the IAK certificate with the iak_cert() method from the DeviceIDBuilder object")] - IAKCertNotSet, - /// IAK password not set in DeviceIDBuilder #[error("IAK password not set in DeviceBuilder. Set the IAK password with the iak_password() method from the DeviceIDBuilder object")] IAKPasswordNotSet, @@ -46,6 +50,10 @@ pub enum DeviceIDBuilderError { #[error("Failed to create IDevID")] IDevIDCreate(#[source] TpmError), + /// IDevID default Template not set + #[error("IDevID default template not set")] + IDevIDDefaultTemplateNotSet, + /// Could not get IDevID from provided handle #[error("Could not get IDevID from provided handle")] IDevIDFromHandle(#[source] TpmError), @@ -54,10 +62,6 @@ pub enum DeviceIDBuilderError { #[error("IDevID handle not set in DeviceBuilder. Set the IDevID handle with the idevid_handle() method from the DeviceIDBuilder object")] IDevIDHandleNotSet, - /// IDevID certificate not set in DeviceIDBuilder - #[error("IDevID certificate not set in DeviceBuilder. Set the IDevID certificate with the idevid_cert() method from the DeviceIDBuilder object")] - IDevIDCertNotSet, - /// IDevID password not set in DeviceIDBuilder #[error("IDevID password not set in DeviceBuilder. Set the IDevID password with the idevid_password() method from the DeviceIDBuilder object")] IDevIDPasswordNotSet, @@ -80,20 +84,22 @@ pub enum DeviceIDError { #[derive(Debug, Default)] pub struct DeviceIDBuilder<'a> { - iak_handle: Option<&'a str>, - iak_password: Option<&'a str>, + iak_asym_alg: Option<&'a str>, iak_cert: Option, iak_cert_path: Option<&'a str>, - iak_template: Option<&'a str>, - iak_asym_alg: Option<&'a str>, + iak_default_template: Option<&'a str>, + iak_handle: Option<&'a str>, iak_hash_alg: Option<&'a str>, - idevid_handle: Option<&'a str>, - idevid_password: Option<&'a str>, + iak_password: Option<&'a str>, + iak_template: Option<&'a str>, + idevid_asym_alg: Option<&'a str>, idevid_cert: Option, idevid_cert_path: Option<&'a str>, - idevid_template: Option<&'a str>, - idevid_asym_alg: Option<&'a str>, + idevid_default_template: Option<&'a str>, + idevid_handle: Option<&'a str>, idevid_hash_alg: Option<&'a str>, + idevid_password: Option<&'a str>, + idevid_template: Option<&'a str>, } impl<'a> DeviceIDBuilder<'a> { @@ -135,6 +141,22 @@ impl<'a> DeviceIDBuilder<'a> { self } + /// Set the default template to use for the IAK + /// + /// When `iak_cert` is not set, the provided template is used when `iak_template` string is + /// empty, or the special keywords 'detect' or 'default' are set + /// + /// # Arguments: + /// + /// * template(&str): The name of the default template to use. + pub fn iak_default_template( + mut self, + template: &'a str, + ) -> DeviceIDBuilder<'a> { + self.iak_default_template = Some(template); + self + } + /// Set the template to use for the IAK /// /// If the string is empty, or the special keywords 'detect' or 'default' are set, the template @@ -204,6 +226,22 @@ impl<'a> DeviceIDBuilder<'a> { self } + /// Set the default template to use for the IDevID + /// + /// When `idevid_cert` is not set, the provided template is used when `idevid_template` string is + /// empty, or the special keywords 'detect' or 'default' are set + /// + /// # Arguments: + /// + /// * template(&str): The name of the default template to use. + pub fn idevid_default_template( + mut self, + template: &'a str, + ) -> DeviceIDBuilder<'a> { + self.idevid_default_template = Some(template); + self + } + /// Set the template to use for the IDevID /// /// If the string is empty, or the special keywords 'detect' or 'default' are set, the template @@ -255,14 +293,28 @@ impl<'a> DeviceIDBuilder<'a> { ) -> Result<(AsymmetricAlgorithm, HashingAlgorithm), DeviceIDBuilderError> { let iak_cert = self.get_iak_cert()?; - let detected = &crypto::match_cert_to_template(iak_cert) - .map_err(DeviceIDBuilderError::TemplateFromCert)?; + + // If the IAK cert is set, get the template based on it + // Otherwise use the default template set + let detected = match iak_cert { + Some(cert) => crypto::match_cert_to_template(cert) + .map_err(DeviceIDBuilderError::TemplateFromCert)?, + None => { + if let Some(default) = self.iak_default_template { + default.trim().to_string() + } else { + return Err( + DeviceIDBuilderError::IAKDefaultTemplateNotSet, + ); + } + } + }; let template = self.iak_template.unwrap_or("").trim(); let asym_alg = self.iak_asym_alg.unwrap_or("").trim(); let hash_alg = self.iak_hash_alg.unwrap_or("").trim(); - tpm::get_idevid_template(detected, template, asym_alg, hash_alg) + tpm::get_idevid_template(&detected, template, asym_alg, hash_alg) .map_err(DeviceIDBuilderError::Template) } @@ -275,14 +327,28 @@ impl<'a> DeviceIDBuilder<'a> { ) -> Result<(AsymmetricAlgorithm, HashingAlgorithm), DeviceIDBuilderError> { let idevid_cert = self.get_idevid_cert()?; - let detected = &crypto::match_cert_to_template(idevid_cert) - .map_err(DeviceIDBuilderError::TemplateFromCert)?; + + // If the IAK cert is set, get the template based on it + // Otherwise use the default template set + let detected = match idevid_cert { + Some(cert) => crypto::match_cert_to_template(cert) + .map_err(DeviceIDBuilderError::TemplateFromCert)?, + None => { + if let Some(default) = self.idevid_default_template { + default.trim().to_string() + } else { + return Err( + DeviceIDBuilderError::IDevIDDefaultTemplateNotSet, + ); + } + } + }; let template = self.idevid_template.unwrap_or("").trim(); let asym_alg = self.idevid_asym_alg.unwrap_or("").trim(); let hash_alg = self.idevid_hash_alg.unwrap_or("").trim(); - tpm::get_idevid_template(detected, template, asym_alg, hash_alg) + tpm::get_idevid_template(&detected, template, asym_alg, hash_alg) .map_err(DeviceIDBuilderError::Template) } @@ -349,29 +415,31 @@ impl<'a> DeviceIDBuilder<'a> { /// Get the IAK certificate /// If the iak_cert is not set, try to load the certificate from the iak_cert_path and cache /// the loaded certificate - fn get_iak_cert(&mut self) -> Result<&X509, DeviceIDBuilderError> { + fn get_iak_cert( + &mut self, + ) -> Result, DeviceIDBuilderError> { match self.iak_cert { - Some(ref cert) => Ok(cert), + Some(ref cert) => Ok(Some(cert)), None => match &self.iak_cert_path { Some(path) => { if path.trim().is_empty() { debug!( - "The IAK certificate was not set in the configuration file" + "The IAK certificate was not set in the configuration file and will be ignored" ); - Err(DeviceIDBuilderError::IAKCertNotSet) + Ok(None) } else { self.iak_cert = Some(crypto::load_x509(Path::new(path.trim())).map_err(|e| { debug!("Could not load IAK certificate from {path}: {e}"); e }).map_err(DeviceIDBuilderError::CertLoad)?); if let Some(ref cert) = &self.iak_cert { - Ok(cert) + Ok(Some(cert)) } else { unreachable!(); } } } - None => Err(DeviceIDBuilderError::IAKCertNotSet), + None => Ok(None), }, } } @@ -379,29 +447,31 @@ impl<'a> DeviceIDBuilder<'a> { /// Get the IDevID certificate /// /// If the idevid_cert is not set, try to load the certificate from the idevid_cert_path and cache the loaded certificate - fn get_idevid_cert(&mut self) -> Result<&X509, DeviceIDBuilderError> { + fn get_idevid_cert( + &mut self, + ) -> Result, DeviceIDBuilderError> { match self.idevid_cert { - Some(ref cert) => Ok(cert), + Some(ref cert) => Ok(Some(cert)), None => match self.idevid_cert_path { Some(path) => { if path.trim().is_empty() { debug!( - "The IDevId certificate was not set in the configuration file" + "The IDevId certificate was not set in the configuration file and will be ignored" ); - Err(DeviceIDBuilderError::IDevIDCertNotSet) + Ok(None) } else { self.idevid_cert = Some(crypto::load_x509(Path::new(path.trim())).map_err(|e| { debug!("Could not load IAK certificate from {path}: {e}"); e }).map_err(DeviceIDBuilderError::CertLoad)?); if let Some(ref cert) = self.idevid_cert { - Ok(cert) + Ok(Some(cert)) } else { unreachable!(); } } } - None => Err(DeviceIDBuilderError::IDevIDCertNotSet), + None => Ok(None), }, } } @@ -411,28 +481,43 @@ impl<'a> DeviceIDBuilder<'a> { mut self, tpm_ctx: &mut tpm::Context, ) -> Result { - let iak = self.get_iak(tpm_ctx)?; - let Some(iak_cert) = self.iak_cert.take() else { - unreachable!(); - }; + let idevid = self.get_idevid(tpm_ctx)?; - // Check that recreated/collected IAK key matches the one in the certificate - tpm::check_pubkey_match_cert(&iak.public, &iak_cert, "IAK") - .map_err(DeviceIDBuilderError::CertPubKeyMismatch)?; + // Flush the IDevID handle to free TPM memory + if !idevid.is_persistent { + tpm_ctx + .flush_context(idevid.handle.into()) + .map_err(DeviceIDBuilderError::FlushContext)?; + } - let idevid = self.get_idevid(tpm_ctx)?; - let Some(idevid_cert) = self.idevid_cert.take() else { - unreachable!(); + // Check that recreated/collected IDevID key matches the one in the certificate + let idevid_cert = match self.idevid_cert.take() { + Some(cert) => { + tpm::check_pubkey_match_cert(&idevid.public, &cert, "IDevID") + .map_err(DeviceIDBuilderError::CertPubKeyMismatch)?; + + Some(cert) + } + None => None, }; - // Check that recreated/collected IDevID key matches the one in the certificate - tpm::check_pubkey_match_cert(&idevid.public, &idevid_cert, "IDevID") - .map_err(DeviceIDBuilderError::CertPubKeyMismatch)?; + let iak = self.get_iak(tpm_ctx)?; + let iak_cert = match self.iak_cert.take() { + Some(cert) => { + tpm::check_pubkey_match_cert(&iak.public, &cert, "IDevID") + .map_err(DeviceIDBuilderError::CertPubKeyMismatch)?; + + Some(cert) + } + None => None, + }; Ok(DeviceID { - iak, + iak_handle: iak.handle, + iak_handle_persistent: iak.is_persistent, + iak_pubkey: iak.public, iak_cert, - idevid, + idevid_pubkey: idevid.public, idevid_cert, }) } @@ -440,10 +525,12 @@ impl<'a> DeviceIDBuilder<'a> { #[derive(Debug)] pub struct DeviceID { - pub iak: IAKResult, - pub iak_cert: X509, - pub idevid: IDevIDResult, - pub idevid_cert: X509, + pub iak_handle: KeyHandle, + pub iak_handle_persistent: bool, + pub iak_pubkey: TssPublic, + pub iak_cert: Option, + pub idevid_pubkey: TssPublic, + pub idevid_cert: Option, } impl DeviceID { @@ -460,7 +547,7 @@ impl DeviceID { tpm_ctx: &mut tpm::Context, ) -> Result<(Attest, Signature), DeviceIDError> { tpm_ctx - .certify_credential_with_iak(qualifying_data, ak, self.iak.handle) + .certify_credential_with_iak(qualifying_data, ak, self.iak_handle) .map_err(DeviceIDError::Certify) } } @@ -475,12 +562,14 @@ mod tests { .iak_handle("") .iak_cert_path("") .iak_password("") + .iak_default_template("") .iak_template("") .iak_asym_alg("") .iak_hash_alg("") .idevid_handle("") .idevid_cert_path("") .idevid_password("") + .idevid_default_template("") .idevid_template("") .idevid_asym_alg("") .idevid_hash_alg(""); @@ -507,6 +596,7 @@ mod tests { .expect("Failed to get str for IAK cert"), ) .iak_password("") + .iak_default_template("") .iak_template("") .iak_asym_alg("") .iak_hash_alg("") @@ -517,6 +607,7 @@ mod tests { .expect("Failed to get str for IDevID cert"), ) .idevid_password("") + .idevid_default_template("") .idevid_template("") .idevid_asym_alg("") .idevid_hash_alg("") @@ -525,11 +616,63 @@ mod tests { let dev_id = result.unwrap(); //#[allow_ci] // Flush context to free TPM memory - let r = tpm_ctx.flush_context(dev_id.iak.handle.into()); - assert!(r.is_ok(), "Result: {r:?}"); - let r = tpm_ctx.flush_context(dev_id.idevid.handle.into()); + let r = tpm_ctx.flush_context(dev_id.iak_handle.into()); assert!(r.is_ok(), "Result: {r:?}"); } } } + + #[tokio::test] + #[cfg(feature = "testing")] + async fn test_device_id_builder_no_certs() { + let _mutex = tpm::testing::lock_tests().await; + let mut tpm_ctx = tpm::Context::new().unwrap(); //#[allow_ci] + let result = DeviceIDBuilder::new() + .iak_handle("") + .iak_password("") + .iak_default_template("H-1") + .iak_template("detect") + .iak_asym_alg("") + .iak_hash_alg("") + .idevid_handle("") + .idevid_password("") + .idevid_default_template("H-1") + .idevid_template("detect") + .idevid_asym_alg("") + .idevid_hash_alg("") + .build(&mut tpm_ctx); + assert!(result.is_ok(), "Result: {result:?}"); + let dev_id = result.unwrap(); //#[allow_ci] + + // Flush context to free TPM memory + let r = tpm_ctx.flush_context(dev_id.iak_handle.into()); + assert!(r.is_ok(), "Result: {r:?}"); + } + + #[tokio::test] + #[cfg(feature = "testing")] + async fn test_device_id_builder_no_certs_no_default() { + let _mutex = tpm::testing::lock_tests().await; + let mut tpm_ctx = tpm::Context::new().unwrap(); //#[allow_ci] + let result = DeviceIDBuilder::new() + .iak_handle("") + .iak_password("") + .iak_default_template("") + .iak_template("detect") + .iak_asym_alg("rsa") + .iak_hash_alg("sha256") + .idevid_handle("") + .idevid_password("") + .idevid_default_template("") + .idevid_template("detect") + .idevid_asym_alg("rsa") + .idevid_hash_alg("sha256") + .build(&mut tpm_ctx); + assert!(result.is_ok(), "Result: {result:?}"); + let dev_id = result.unwrap(); //#[allow_ci] + + // Flush context to free TPM memory + let r = tpm_ctx.flush_context(dev_id.iak_handle.into()); + assert!(r.is_ok(), "Result: {r:?}"); + } } diff --git a/keylime/src/tpm.rs b/keylime/src/tpm.rs index 153c36657..997334dc4 100644 --- a/keylime/src/tpm.rs +++ b/keylime/src/tpm.rs @@ -458,6 +458,7 @@ pub struct AKResult { pub struct IAKResult { pub public: TssPublic, pub handle: tss_esapi::handles::KeyHandle, + pub is_persistent: bool, } /// Holds the output of create_idevid. @@ -465,6 +466,7 @@ pub struct IAKResult { pub struct IDevIDResult { pub public: TssPublic, pub handle: tss_esapi::handles::KeyHandle, + pub is_persistent: bool, } /// Holds the Public result from create_idevid_public_from_default_template @@ -748,6 +750,7 @@ impl Context<'_> { Ok(IDevIDResult { public: idevid_pub, handle: idevid_handle, + is_persistent: true, }) } @@ -767,6 +770,7 @@ impl Context<'_> { Ok(IAKResult { public: iak_pub, handle: iak_handle, + is_persistent: true, }) } @@ -818,6 +822,7 @@ impl Context<'_> { Ok(IDevIDResult { public: primary_key.out_public, handle: primary_key.key_handle, + is_persistent: false, }) } @@ -1035,6 +1040,7 @@ impl Context<'_> { Ok(IAKResult { public: primary_key.out_public, handle: primary_key.key_handle, + is_persistent: false, }) }