Skip to content

Commit ff448ec

Browse files
committed
The EK Certificate chain can be stored in the NVRAM Indexes ranging from 0x01c00100 to 0x01c001ff, with the certificates stored concatenated in DER format.
If handles within this specified range are present, the following steps will be executed: 1. The content of all NV handles will be collected into a vector. 2. The content of the vector will be split into individual certificates. 3. Each certificate will be converted to PEM format. 4. The resulting PEM certificate chain will be provided as the 'ek_ca_chain' attribute to the registrar. I appreciate any feedback, as I have no experience with the Rust programming language. Signed-off-by: Eugen Matery <[email protected]>
1 parent aa60cfe commit ff448ec

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

keylime-agent/src/main.rs

+8
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,12 @@ async fn main() -> Result<()> {
464464
// Calculate the SHA-256 hash of the public key in PEM format
465465
let ek_hash = hash_ek_pubkey(ek_result.public.clone())?;
466466

467+
// Read EK CA Chain form TPM
468+
let ek_ca_chain = match ctx.create_ek_ca_chain() {
469+
Ok(value) => Some(value),
470+
Err(_) => None,
471+
};
472+
467473
// Replace the uuid with the actual EK hash if the option was set.
468474
// We cannot do that when the configuration is loaded initially,
469475
// because only have later access to the the TPM.
@@ -723,6 +729,7 @@ async fn main() -> Result<()> {
723729
&PublicBuffer::try_from(ek_result.public.clone())?
724730
.marshall()?,
725731
ek_result.ek_cert,
732+
ek_ca_chain,
726733
&PublicBuffer::try_from(ak.public)?.marshall()?,
727734
Some(
728735
&PublicBuffer::try_from(iak.public.clone())?
@@ -749,6 +756,7 @@ async fn main() -> Result<()> {
749756
&PublicBuffer::try_from(ek_result.public.clone())?
750757
.marshall()?,
751758
ek_result.ek_cert,
759+
ek_ca_chain,
752760
&PublicBuffer::try_from(ak.public)?.marshall()?,
753761
None,
754762
None,

keylime-agent/src/registrar_agent.rs

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ fn is_empty(buf: &[u8]) -> bool {
1616
struct Register<'a> {
1717
#[serde(serialize_with = "serialize_maybe_base64")]
1818
ekcert: Option<Vec<u8>>,
19+
#[serde(skip_serializing_if = "Option::is_none")]
20+
ek_ca_chain: Option<String>,
1921
#[serde(
2022
serialize_with = "serialize_as_base64",
2123
skip_serializing_if = "is_empty"
@@ -134,6 +136,7 @@ pub(crate) async fn do_register_agent(
134136
agent_uuid: &str,
135137
ek_tpm: &[u8],
136138
ekcert: Option<Vec<u8>>,
139+
ek_ca_chain: Option<String>,
137140
aik_tpm: &[u8],
138141
iak_tpm: Option<&[u8]>,
139142
idevid_tpm: Option<&[u8]>,
@@ -168,6 +171,7 @@ pub(crate) async fn do_register_agent(
168171

169172
let data = Register {
170173
ekcert,
174+
ek_ca_chain,
171175
ek_tpm,
172176
aik_tpm,
173177
iak_tpm,

keylime/src/tpm.rs

+130-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use openssl::{
1616
memcmp,
1717
pkey::{HasPublic, Id, PKeyRef, Public},
1818
};
19-
19+
use openssl::x509::X509;
2020
use tss_esapi::{
2121
abstraction::{
2222
ak,
@@ -57,6 +57,10 @@ use tss_esapi::{
5757
tss2_esys::{TPML_DIGEST, TPML_PCR_SELECTION},
5858
Error::Tss2Error,
5959
};
60+
use tss_esapi::abstraction::nv;
61+
use tss_esapi::constants::CapabilityType;
62+
use tss_esapi::interface_types::resource_handles::NvAuth;
63+
use tss_esapi::structures::CapabilityData;
6064

6165
/// Maximum size of nonce used in `quote`.
6266
pub const MAX_NONCE_SIZE: usize = 64;
@@ -108,6 +112,10 @@ const IAK_AUTH_POLICY_SHA256: [u8; 32] = [
108112
];
109113
const UNIQUE_IAK: [u8; 3] = [0x49, 0x41, 0x4b];
110114

115+
const RSA_EK_CERTIFICATE_CHAIN_START: u32 = 0x01c00100;
116+
const RSA_EK_CERTIFICATE_CHAIN_END: u32 = 0x01c001ff;
117+
118+
111119
/// TpmError wraps all possible errors raised in tpm.rs
112120
#[derive(Error, Debug)]
113121
pub enum TpmError {
@@ -586,6 +594,119 @@ impl Context {
586594
})
587595
}
588596

597+
pub fn read_ek_ca_chain(&mut self) -> tss_esapi::Result<Vec<u8>> {
598+
let mut result: Vec<u8> = Vec::new();
599+
let context = &mut self.inner;
600+
601+
// Get handles for NV-Index in range 0x01c00100 - 0x01c001ff
602+
let (capabilities, _) = context.get_capability(
603+
CapabilityType::Handles,
604+
RSA_EK_CERTIFICATE_CHAIN_START,
605+
RSA_EK_CERTIFICATE_CHAIN_END - RSA_EK_CERTIFICATE_CHAIN_START,
606+
)?;
607+
608+
if let CapabilityData::Handles(handle_list) = capabilities {
609+
for handle in handle_list.iter() {
610+
if let TpmHandle::NvIndex(nv_idx) = handle {
611+
// Attempt to get the NV authorization handle
612+
let nv_auth_handle = context.execute_without_session(|ctx| {
613+
ctx.tr_from_tpm_public(*handle)
614+
.map(|v| NvAuth::NvIndex(v.into()))
615+
})?;
616+
617+
// Read the full NV data
618+
let data = context.execute_with_nullauth_session(|ctx| {
619+
nv::read_full(ctx, nv_auth_handle, *nv_idx)
620+
})?;
621+
622+
result.extend(data);
623+
} else {
624+
// Handle other types of handles if necessary
625+
break; // Skip non-NvIndex handles
626+
}
627+
}
628+
}
629+
630+
Ok(result) // Return the accumulated result
631+
}
632+
633+
pub fn split_der_certificates(
634+
&mut self,
635+
der_data: &[u8]
636+
) -> Vec<Vec<u8>>
637+
{
638+
let mut certificates = Vec::new();
639+
let mut offset = 0;
640+
641+
while offset < der_data.len() {
642+
// Check if the current byte indicates the start of a sequence (0x30)
643+
if der_data[offset] != 0x30 {
644+
break; // Not a valid certificate start
645+
}
646+
647+
// Read the length of the sequence
648+
let length_byte = der_data[offset + 1];
649+
let cert_length = if length_byte & 0x80 == 0 {
650+
// Short form length
651+
length_byte as usize + 2 // +2 for the tag and length byte
652+
} else {
653+
// Long form length
654+
let length_of_length = (length_byte & 0x7F) as usize;
655+
let length_bytes = &der_data[offset + 2..offset + 2 + length_of_length];
656+
let cert_length = length_bytes.iter().fold(0, |acc, &b| (acc << 8) | b as usize);
657+
cert_length + 2 + length_of_length // +2 for the tag and length byte
658+
};
659+
660+
// Extract the certificate
661+
let cert = der_data[offset..offset + cert_length].to_vec();
662+
certificates.push(cert);
663+
664+
// Move the offset to the next certificate
665+
offset += cert_length;
666+
}
667+
668+
certificates
669+
}
670+
671+
pub fn der_to_pem(
672+
&mut self,
673+
der_certificates: Vec<Vec<u8>>
674+
) -> std::result::Result<String, Box<dyn std::error::Error>> {
675+
let mut pem_string = String::new();
676+
677+
for der in der_certificates {
678+
// Convert DER to X509
679+
let cert = X509::from_der(&der)?;
680+
681+
// Convert X509 to PEM format
682+
let pem = cert.to_pem()?;
683+
684+
// Append the PEM string to the result
685+
pem_string.push_str(&String::from_utf8(pem)?);
686+
}
687+
688+
Ok(pem_string)
689+
}
690+
691+
/// Read the EK CA Chain from the tpm and return it.
692+
///
693+
/// As described in https://trustedcomputinggroup.org/wp-content/uploads/TCG-EK-Credential-Profile-V-2.5-R2_published.pdf (2.2.1.5.2 Handle Values for EK Certificate Chains)
694+
/// Intermediate certificates can be stored directly in the TPM. The index used for it should be
695+
/// 0x01c00100 - 0x01c001ff. The CA Chain is stored in DER format and will overlflow into the
696+
/// next register as long as there is data.
697+
///
698+
/// This is for example the case for Intel fTPM, starting 11th gen core.
699+
///
700+
/// # Returns
701+
///
702+
/// A `String` with all certificates in PEM format if successful, an Error otherwise
703+
pub fn create_ek_ca_chain(&mut self) -> std::result::Result<String, Box<dyn std::error::Error>> {
704+
let der_data = self.read_ek_ca_chain()?;
705+
let der_certificates = self.split_der_certificates(&der_data);
706+
let pem_string = self.der_to_pem(der_certificates)?;
707+
Ok(pem_string)
708+
}
709+
589710
/// Creates an AK
590711
///
591712
/// # Arguments
@@ -2132,6 +2253,14 @@ pub mod testing {
21322253
}
21332254
}
21342255

2256+
#[test]
2257+
#[cfg(feature = "testing")]
2258+
fn test_create_ek_ca_chain() {
2259+
let mut ctx = Context::new().unwrap(); //#[allow_ci]
2260+
let c = ctx.create_ek_ca_chain();
2261+
assert!(c.is_ok());
2262+
}
2263+
21352264
#[test]
21362265
#[cfg(feature = "testing")]
21372266
fn test_create_and_load_ak() {

0 commit comments

Comments
 (0)