From 33d624d683439e41d4c02c3351a15f6f6407e772 Mon Sep 17 00:00:00 2001 From: Isaac Matthews Date: Tue, 14 Nov 2023 17:45:00 +0000 Subject: [PATCH] Add certificates and certificate checking for IDevID and IAK keys (#669) * Add certificates and certificate checking for IDevID and IAK keys Signed-off-by: Isaac Matthews --- keylime-agent.conf | 18 +++++- keylime-agent/src/config.rs | 40 ++++++++++++- keylime-agent/src/crypto.rs | 62 ++++++++++++++++++++ keylime-agent/src/main.rs | 86 ++++++++++++++++++++++++++++ keylime-agent/src/registrar_agent.rs | 24 ++++++++ 5 files changed, 228 insertions(+), 2 deletions(-) diff --git a/keylime-agent.conf b/keylime-agent.conf index 12c93f70..a8d0203d 100644 --- a/keylime-agent.conf +++ b/keylime-agent.conf @@ -240,7 +240,23 @@ ek_handle = "generate" enable_iak_idevid = false iak_idevid_asymmetric_alg = "rsa" iak_idevid_name_alg = "sha256" -iak_idevid_template = "" +iak_idevid_template = "H-1" + +# The name of the file containing the X509 IAK certificate. +# If set as "default", the "iak-cert.crt" value is used +# If a relative path is set, it will be considered relative from the keylime_dir. +# If an absolute path is set, it is used without change. +# +# To override iak_cert, set KEYLIME_AGENT_IAK_CERT environment variable. +iak_cert = "default" + +# The name of the file containing the X509 IDevID certificate. +# If set as "default", the "idevid-cert.crt" value is used +# If a relative path is set, it will be considered relative from the keylime_dir. +# If an absolute path is set, it is used without change. +# +# To override idevid_cert, set KEYLIME_AGENT_IDEVID_CERT environment variable. +idevid_cert = "default" # Use this option to state the existing TPM ownerpassword. # This option should be set only when a password is set for the Endorsement diff --git a/keylime-agent/src/config.rs b/keylime-agent/src/config.rs index 927c60f0..f6fd3e95 100644 --- a/keylime-agent/src/config.rs +++ b/keylime-agent/src/config.rs @@ -31,6 +31,8 @@ pub static DEFAULT_ENABLE_AGENT_MTLS: bool = true; pub static DEFAULT_KEYLIME_DIR: &str = "/var/lib/keylime"; pub static DEFAULT_SERVER_KEY: &str = "server-private.pem"; pub static DEFAULT_SERVER_CERT: &str = "server-cert.crt"; +pub static DEFAULT_IAK_CERT: &str = "iak-cert.crt"; +pub static DEFAULT_IDEVID_CERT: &str = "idevid-cert.crt"; pub static DEFAULT_SERVER_KEY_PASSWORD: &str = ""; // The DEFAULT_TRUSTED_CLIENT_CA is relative from KEYLIME_DIR pub static DEFAULT_TRUSTED_CLIENT_CA: &str = "cv_ca/cacert.crt"; @@ -58,7 +60,7 @@ pub static DEFAULT_EK_HANDLE: &str = "generate"; pub static DEFAULT_ENABLE_IAK_IDEVID: bool = true; pub static DEFAULT_IAK_IDEVID_ASYMMETRIC_ALG: &str = "rsa"; pub static DEFAULT_IAK_IDEVID_NAME_ALG: &str = "sha256"; -pub static DEFAULT_IAK_IDEVID_TEMPLATE: &str = ""; +pub static DEFAULT_IAK_IDEVID_TEMPLATE: &str = "H-1"; pub static DEFAULT_RUN_AS: &str = "keylime:tss"; pub static DEFAULT_AGENT_DATA_PATH: &str = "agent_data.json"; pub static DEFAULT_CONFIG: &str = "/etc/keylime/agent.conf"; @@ -78,6 +80,8 @@ pub(crate) struct EnvConfig { pub keylime_dir: Option, pub server_key: Option, pub server_cert: Option, + pub iak_cert: Option, + pub idevid_cert: Option, pub server_key_password: Option, pub trusted_client_ca: Option, pub enc_keyname: Option, @@ -120,6 +124,8 @@ pub(crate) struct AgentConfig { pub keylime_dir: String, pub server_key: String, pub server_cert: String, + pub iak_cert: String, + pub idevid_cert: String, pub server_key_password: String, pub trusted_client_ca: String, pub enc_keyname: String, @@ -199,6 +205,12 @@ impl EnvConfig { if let Some(ref v) = self.server_cert { _ = agent.insert("server_cert".to_string(), v.to_string().into()); } + if let Some(ref v) = self.iak_cert { + _ = agent.insert("iak_cert".to_string(), v.to_string().into()); + } + if let Some(ref v) = self.idevid_cert { + _ = agent.insert("idevid_cert".to_string(), v.to_string().into()); + } if let Some(ref v) = self.trusted_client_ca { _ = agent.insert( "trusted_client_ca".to_string(), @@ -395,6 +407,14 @@ impl Source for KeylimeConfig { "server_cert".to_string(), self.agent.server_cert.to_string().into(), ); + _ = m.insert( + "iak_cert".to_string(), + self.agent.iak_cert.to_string().into(), + ); + _ = m.insert( + "idevid_cert".to_string(), + self.agent.idevid_cert.to_string().into(), + ); _ = m.insert( "trusted_client_ca".to_string(), self.agent.trusted_client_ca.to_string().into(), @@ -544,6 +564,8 @@ impl Default for AgentConfig { server_key: "default".to_string(), server_key_password: DEFAULT_SERVER_KEY_PASSWORD.to_string(), server_cert: "default".to_string(), + iak_cert: "default".to_string(), + idevid_cert: "default".to_string(), trusted_client_ca: "default".to_string(), revocation_actions: DEFAULT_REVOCATION_ACTIONS.to_string(), revocation_actions_dir: DEFAULT_REVOCATION_ACTIONS_DIR @@ -724,6 +746,20 @@ fn config_translate_keywords( .collect::>() .join(", "); + let mut iak_cert = config_get_file_path( + "iak_cert", + &config.agent.iak_cert, + keylime_dir, + DEFAULT_IAK_CERT, + ); + + let mut idevid_cert = config_get_file_path( + "idevid_cert", + &config.agent.idevid_cert, + keylime_dir, + DEFAULT_IDEVID_CERT, + ); + let ek_handle = match config.agent.ek_handle.as_ref() { "generate" => "".to_string(), "" => "".to_string(), @@ -764,6 +800,8 @@ fn config_translate_keywords( uuid, server_key, server_cert, + iak_cert, + idevid_cert, trusted_client_ca, ek_handle, agent_data_path, diff --git a/keylime-agent/src/crypto.rs b/keylime-agent/src/crypto.rs index 8ec34494..3b71a7aa 100644 --- a/keylime-agent/src/crypto.rs +++ b/keylime-agent/src/crypto.rs @@ -18,6 +18,7 @@ use openssl::{ x509::store::X509StoreBuilder, x509::{X509Name, X509}, }; +use picky_asn1_x509::SubjectPublicKeyInfo; use std::{ fs::{read_to_string, set_permissions, File, Permissions}, io::{Read, Write}, @@ -30,6 +31,19 @@ use crate::{ Error, Result, AES_128_KEY_LEN, AES_256_KEY_LEN, AES_BLOCK_SIZE, }; +// Read a X509 cert in DER format from path +pub(crate) fn load_x509_der(input_cert_path: &Path) -> Result { + let contents = std::fs::read(input_cert_path).map_err(Error::from)?; + + X509::from_der(&contents).map_err(Error::Crypto) +} + +pub(crate) fn load_x509_pem(input_cert_path: &Path) -> Result { + let contents = std::fs::read(input_cert_path).map_err(Error::from)?; + + X509::from_pem(&contents).map_err(Error::Crypto) +} + // Read a X509 cert or cert chain and outputs the first certificate pub(crate) fn load_x509(input_cert_path: &Path) -> Result { let mut cert_chain = load_x509_cert_chain(input_cert_path)?; @@ -75,6 +89,54 @@ pub(crate) fn write_x509(cert: &X509, file_path: &Path) -> Result<()> { Ok(()) } +/// Check an x509 certificate contains a specific public key +pub(crate) fn check_x509_key( + cert: &X509, + 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 + let id_rsa_pss: Id = Id::from_raw(912); + match cert.public_key()?.id() { + Id::RSA => { + let cert_n = cert.public_key()?.rsa()?.n().to_vec(); + 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_der = picky_asn1_der::to_vec(&key)?; + let key_der_str = format!("{:?}", key_der); + + Ok(key_der_str.contains(&cert_n_str)) + } + cert_id if cert_id == id_rsa_pss => { + let cert_n = cert.public_key()?.rsa()?.n().to_vec(); + 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_der = picky_asn1_der::to_vec(&key)?; + let key_der_str = format!("{:?}", key_der); + + Ok(key_der_str.contains(&cert_n_str)) + } + Id::EC => { + let cert_n = cert.public_key()?.ec_key()?.public_key_to_der()?; + 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_der = picky_asn1_der::to_vec(&key)?; + let key_der_str = format!("{:?}", key_der); + + Ok(key_der_str.contains(&cert_n_str)) + } + _ => Err(Error::Other( + "Certificate does not seem to have an RSA or EC key".to_string(), + )), + } +} + /// Read a PEM file and returns the public and private keys pub(crate) fn load_key_pair( key_path: &Path, diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs index 0a16f07b..57401424 100644 --- a/keylime-agent/src/main.rs +++ b/keylime-agent/src/main.rs @@ -295,6 +295,88 @@ async fn main() -> Result<()> { (None, None) }; + let iak_cert: Option; + let idevid_cert: Option; + // Attempt to load the IAK and IDevID certificates + // Check the certificates contain the keys that have been regenerated by the TPM + // If they do not there is likely a configuration issue where the template has been set to the wrong value + 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)?, + }; + if crypto::check_x509_key( + &iakcert, + iak.clone() + .expect( + "IAK could not be used in cert key check.", + ) + .public, + )? { + Some(iakcert) + } else { + error!("IAK template does not match certificate. Check template in configuration."); + return Err(Error::Configuration("IAK template does not match certificate. Check template in configuration.".to_string())); + } + } 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)?, + }; + if crypto::check_x509_key( + &idevcert, + idevid + .clone() + .expect( + "IDevID could not be used in cert key check.", + ) + .public, + )? { + Some(idevcert) + } else { + error!("IDevID template does not match certificate. Check template in configuration."); + return Err(Error::Configuration("IDevID template does not match certificate. Check template in configuration.".to_string())); + } + } else { + debug!("Can not find IDevID certificate"); + None + } + } + }; + } else { + iak_cert = None; + idevid_cert = None; + } + // Gather EK values and certs let ek_result = match config.agent.ek_handle.as_ref() { "" => ctx.create_ek(tpm_encryption_alg, None)?, @@ -562,6 +644,8 @@ async fn main() -> Result<()> { &PublicBuffer::try_from(idevid.public.clone())? .marshall()?, ), + idevid_cert, + iak_cert, Some(attest.marshall()?), Some(signature.marshall()?), mtls_cert, @@ -582,6 +666,8 @@ async fn main() -> Result<()> { None, None, None, + None, + None, mtls_cert, config.agent.contact_ip.as_ref(), config.agent.contact_port, diff --git a/keylime-agent/src/registrar_agent.rs b/keylime-agent/src/registrar_agent.rs index b36db2d2..f086f71a 100644 --- a/keylime-agent/src/registrar_agent.rs +++ b/keylime-agent/src/registrar_agent.rs @@ -32,6 +32,10 @@ struct Register<'a> { skip_serializing_if = "Option::is_none" )] idevid_tpm: Option<&'a [u8]>, + #[serde(serialize_with = "serialize_maybe_base64")] + idevid_cert: Option>, + #[serde(serialize_with = "serialize_maybe_base64")] + iak_cert: Option>, #[serde( serialize_with = "serialize_maybe_base64", skip_serializing_if = "Option::is_none" @@ -116,6 +120,8 @@ pub(crate) async fn do_register_agent( aik_tpm: &[u8], iak_tpm: Option<&[u8]>, idevid_tpm: Option<&[u8]>, + idevid_cert_x509: Option, + iak_cert_x509: Option, iak_attest: Option>, iak_sign: Option>, mtls_cert_x509: Option<&X509>, @@ -127,6 +133,16 @@ pub(crate) async fn do_register_agent( None => Some("disabled".to_string()), }; + let idevid_cert = match idevid_cert_x509 { + Some(cert) => Some(cert.to_der()?), + None => None, + }; + + let iak_cert = match iak_cert_x509 { + Some(cert) => Some(cert.to_der()?), + None => None, + }; + let ip = if ip.is_empty() { None } else { @@ -139,6 +155,8 @@ pub(crate) async fn do_register_agent( aik_tpm, iak_tpm, idevid_tpm, + idevid_cert, + iak_cert, iak_attest, iak_sign, mtls_cert, @@ -227,6 +245,8 @@ mod tests { None, None, None, + None, + None, Some(&cert), "", 0, @@ -273,6 +293,8 @@ mod tests { None, None, None, + None, + None, Some(&cert), "", 0, @@ -315,6 +337,8 @@ mod tests { None, None, None, + None, + None, Some(&cert), "", 0,