Skip to content

Commit 59cfd11

Browse files
committed
Add support for ek certificate chain, stored in TPM NVRAM.
See enhancement-1552: EK Certificate Chain support Signed-off-by: Eugen Matery <[email protected]>
1 parent 329329d commit 59cfd11

File tree

3 files changed

+182
-15
lines changed

3 files changed

+182
-15
lines changed

keylime-agent/src/main.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,8 @@ async fn main() -> Result<()> {
635635
}
636636

637637
// If the certificate is not None add it to the builder
638-
if let Some(ek_cert) = ek_result.ek_cert {
639-
builder = builder.ek_cert(ek_cert);
638+
if let Some(ekchain) = ek_result.to_pem() {
639+
builder = builder.ek_cert(ekchain);
640640
}
641641

642642
// Set the IAK/IDevID related fields, if enabled

keylime/src/registrar_client.rs

+15-11
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub enum RegistrarClientBuilderError {
7575
pub struct RegistrarClientBuilder<'a> {
7676
ak_pub: Option<&'a [u8]>,
7777
ek_pub: Option<&'a [u8]>,
78-
ek_cert: Option<Vec<u8>>,
78+
ek_cert: Option<String>,
7979
enabled_api_versions: Option<Vec<&'a str>>,
8080
iak_attest: Option<Vec<u8>>,
8181
iak_cert: Option<X509>,
@@ -135,8 +135,8 @@ impl<'a> RegistrarClientBuilder<'a> {
135135
///
136136
/// # Arguments:
137137
///
138-
/// * ek_cert (Vec<u8>): A vector containing the EK certificate in DER format
139-
pub fn ek_cert(mut self, ek_cert: Vec<u8>) -> Self {
138+
/// * ek_cert (String): A string containing the EK certificate in PEM format
139+
pub fn ek_cert(mut self, ek_cert: String) -> Self {
140140
self.ek_cert = Some(ek_cert);
141141
self
142142
}
@@ -445,7 +445,7 @@ impl<'a> RegistrarClientBuilder<'a> {
445445
ak_pub,
446446
api_version: registrar_api_version,
447447
ek_pub,
448-
ek_cert: self.ek_cert.take(),
448+
ek_cert: self.ek_cert,
449449
iak_attest: self.iak_attest.take(),
450450
iak_cert,
451451
iak_sign: self.iak_sign.take(),
@@ -489,7 +489,7 @@ pub enum RegistrarClientError {
489489
pub struct RegistrarClient<'a> {
490490
ak_pub: &'a [u8],
491491
api_version: String,
492-
ek_cert: Option<Vec<u8>>,
492+
ek_cert: Option<String>,
493493
ek_pub: &'a [u8],
494494
enabled_api_versions: Vec<&'a str>,
495495
iak_attest: Option<Vec<u8>>,
@@ -536,8 +536,8 @@ struct Register<'a> {
536536
skip_serializing_if = "is_empty"
537537
)]
538538
ek_tpm: &'a [u8],
539-
#[serde(serialize_with = "serialize_maybe_base64")]
540-
ekcert: Option<Vec<u8>>,
539+
#[serde(skip_serializing_if = "Option::is_none")]
540+
ekcert: Option<String>,
541541
#[serde(
542542
serialize_with = "serialize_maybe_base64",
543543
skip_serializing_if = "Option::is_none"
@@ -783,6 +783,7 @@ mod tests {
783783
let port = uri[1].parse().unwrap(); //#[allow_ci]
784784

785785
let mock_data = [0u8; 1];
786+
let mock_chain = String::from("");
786787
let priv_key = crypto::testing::rsa_generate(2048).unwrap(); //#[allow_ci]
787788
let cert = crypto::x509::CertificateBuilder::new()
788789
.private_key(&priv_key)
@@ -794,7 +795,7 @@ mod tests {
794795
let response = RegistrarClientBuilder::new()
795796
.ak_pub(&mock_data)
796797
.ek_pub(&mock_data)
797-
.ek_cert(mock_data.to_vec())
798+
.ek_cert(mock_chain)
798799
.enabled_api_versions(vec!["1.2"])
799800
.iak_attest(vec![0])
800801
.iak_cert(cert.clone())
@@ -841,6 +842,7 @@ mod tests {
841842
let port = uri[1].parse().unwrap(); //#[allow_ci]
842843

843844
let mock_data = [0u8; 1];
845+
let mock_chain = String::from("");
844846
let priv_key = crypto::testing::rsa_generate(2048).unwrap(); //#[allow_ci]
845847
let cert = crypto::x509::CertificateBuilder::new()
846848
.private_key(&priv_key)
@@ -852,7 +854,7 @@ mod tests {
852854
let builder = RegistrarClientBuilder::new()
853855
.ak_pub(&mock_data)
854856
.ek_pub(&mock_data)
855-
.ek_cert(mock_data.to_vec())
857+
.ek_cert(mock_chain)
856858
.enabled_api_versions(vec!["1.2", "3.4"])
857859
.mtls_cert(cert)
858860
.ip("1.2.3.4".to_string())
@@ -906,6 +908,7 @@ mod tests {
906908
let port = uri[1].parse().unwrap(); //#[allow_ci]
907909

908910
let mock_data = [0u8; 1];
911+
let mock_chain = String::from("");
909912
let priv_key = crypto::testing::rsa_generate(2048).unwrap(); //#[allow_ci]
910913
let cert = crypto::x509::CertificateBuilder::new()
911914
.private_key(&priv_key)
@@ -917,7 +920,7 @@ mod tests {
917920
let builder = RegistrarClientBuilder::new()
918921
.ak_pub(&mock_data)
919922
.ek_pub(&mock_data)
920-
.ek_cert(mock_data.to_vec())
923+
.ek_cert(mock_chain)
921924
.enabled_api_versions(vec!["1.2", "3.4"])
922925
.mtls_cert(cert)
923926
.ip("1.2.3.4".to_string())
@@ -1078,6 +1081,7 @@ mod tests {
10781081
let port = uri[1].parse().unwrap(); //#[allow_ci]
10791082

10801083
let mock_data = [0u8; 1];
1084+
let mock_chain = String::from("");
10811085
let priv_key = crypto::testing::rsa_generate(2048).unwrap(); //#[allow_ci]
10821086
let cert = crypto::x509::CertificateBuilder::new()
10831087
.private_key(&priv_key)
@@ -1090,7 +1094,7 @@ mod tests {
10901094
let response = RegistrarClientBuilder::new()
10911095
.ak_pub(&mock_data)
10921096
.ek_pub(&mock_data)
1093-
.ek_cert(mock_data.to_vec())
1097+
.ek_cert(mock_chain)
10941098
.enabled_api_versions(vec!["1.2"])
10951099
.mtls_cert(cert)
10961100
.ip("1.2.3.4".to_string())

keylime/src/tpm.rs

+165-2
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ use tss_esapi::{
3131
ek,
3232
pcr::{read_all, PcrData},
3333
DefaultKey,
34+
nv,
3435
},
3536
attributes::{
3637
object::ObjectAttributesBuilder, session::SessionAttributesBuilder,
3738
},
3839
constants::{
3940
response_code::Tss2ResponseCodeKind, session_type::SessionType,
41+
CapabilityType
4042
},
4143
handles::{
4244
AuthHandle, KeyHandle, ObjectHandle, PcrHandle, PersistentTpmHandle,
@@ -46,7 +48,10 @@ use tss_esapi::{
4648
algorithm::{AsymmetricAlgorithm, HashingAlgorithm, PublicAlgorithm},
4749
ecc::EccCurve,
4850
key_bits::RsaKeyBits,
49-
resource_handles::Hierarchy,
51+
resource_handles::{
52+
Hierarchy,
53+
NvAuth,
54+
},
5055
session_handles::AuthSession,
5156
structure_tags::AttestationType,
5257
},
@@ -58,7 +63,7 @@ use tss_esapi::{
5863
Public as TssPublic, PublicBuilder, PublicEccParametersBuilder,
5964
PublicKeyRsa, PublicRsaParametersBuilder, RsaExponent, RsaScheme,
6065
Signature, SignatureScheme, SymmetricDefinitionObject, Ticket,
61-
VerifiedTicket,
66+
VerifiedTicket, CapabilityData
6267
},
6368
tcti_ldr::TctiNameConf,
6469
traits::Marshall,
@@ -116,6 +121,9 @@ const IAK_AUTH_POLICY_SHA256: [u8; 32] = [
116121
];
117122
const UNIQUE_IAK: [u8; 3] = [0x49, 0x41, 0x4b];
118123

124+
const RSA_EK_CERTIFICATE_CHAIN_START: u32 = 0x01c00100;
125+
const RSA_EK_CERTIFICATE_CHAIN_END: u32 = 0x01c001ff;
126+
119127
/// TpmError wraps all possible errors raised in tpm.rs
120128
#[derive(Error, Debug)]
121129
pub enum TpmError {
@@ -444,6 +452,39 @@ pub struct EKResult {
444452
pub key_handle: KeyHandle,
445453
pub ek_cert: Option<Vec<u8>>,
446454
pub public: TssPublic,
455+
pub ek_chain: Option<Vec<u8>>,
456+
}
457+
458+
impl EKResult {
459+
pub fn to_pem(&self) -> Option<String> {
460+
let mut ca_chain: Vec<Vec<u8>> = Vec::new();
461+
462+
match &self.ek_chain {
463+
Some(chain) => {
464+
ca_chain.extend(split_der_certificates(&chain));
465+
}
466+
None => {
467+
debug!("* No EK certificate chain");
468+
}
469+
}
470+
471+
match &self.ek_cert {
472+
Some(cert) => {
473+
ca_chain.push(cert.clone());
474+
}
475+
None => {
476+
debug!("* No EK certificate");
477+
}
478+
}
479+
480+
match der_to_pem(ca_chain) {
481+
Ok(pem) => Some(pem),
482+
Err(err) => {
483+
error!("Failed to transform certificate chain to PEM format, due to {err:?}");
484+
None
485+
}
486+
}
487+
}
447488
}
448489

449490
/// Holds the output of create_ak.
@@ -612,10 +653,25 @@ impl Context<'_> {
612653
let (tpm_pub, _, _) = ctx
613654
.read_public(key_handle)
614655
.map_err(|source| TpmError::TSSReadPublicError { source })?;
656+
657+
let chain = match read_ek_ca_chain(&mut ctx) {
658+
Ok(der_data) => {
659+
if ! der_data.is_empty() {
660+
info!("Found EK certificate chain in TPM NVRAM")
661+
}
662+
Some(der_data)
663+
},
664+
Err(_) => {
665+
warn!("Failed reading EK certificate chain from TPM NVRAM");
666+
None
667+
}
668+
};
669+
615670
Ok(EKResult {
616671
key_handle,
617672
ek_cert: cert,
618673
public: tpm_pub,
674+
ek_chain: chain,
619675
})
620676
}
621677

@@ -1947,6 +2003,113 @@ pub fn check_pubkey_match_cert(
19472003
}
19482004
}
19492005

2006+
/// Find certificates (DER format) in binary data and split them
2007+
///
2008+
/// # Arguments
2009+
///
2010+
/// `der_data`: Binary data containing certificates in DER format
2011+
///
2012+
/// # Returns
2013+
///
2014+
/// 'Vec<Vec<u8>>', a vector ob certificates in DER format
2015+
pub fn split_der_certificates(
2016+
der_data: &[u8]
2017+
) -> Vec<Vec<u8>>
2018+
{
2019+
let mut certificates = Vec::new();
2020+
let mut offset = 0;
2021+
while offset < der_data.len() {
2022+
// Check if the current byte indicates the start of a sequence (0x30)
2023+
if der_data[offset] != 0x30 {
2024+
break; // Not a valid certificate start
2025+
}
2026+
// Read the length of the sequence
2027+
let length_byte = der_data[offset + 1];
2028+
let cert_length = if length_byte & 0x80 == 0 {
2029+
// Short form length
2030+
length_byte as usize + 2 // +2 for the tag and length byte
2031+
} else {
2032+
// Long form length
2033+
let length_of_length = (length_byte & 0x7F) as usize;
2034+
let length_bytes = &der_data[offset + 2..offset + 2 + length_of_length];
2035+
let cert_length = length_bytes.iter().fold(0, |acc, &b| (acc << 8) | b as usize);
2036+
cert_length + 2 + length_of_length // +2 for the tag and length byte
2037+
};
2038+
// Extract the certificate
2039+
let cert = der_data[offset..offset + cert_length].to_vec();
2040+
certificates.push(cert);
2041+
// Move the offset to the next certificate
2042+
offset += cert_length;
2043+
}
2044+
certificates
2045+
}
2046+
2047+
/// Convert a vector of der certificates into a single string with all certificates in PEM format.
2048+
///
2049+
/// # Arguments
2050+
///
2051+
/// `der_certificates`: Vector of certificates in DER format
2052+
///
2053+
/// # Returns
2054+
///
2055+
/// A `String` containing all concatenated certificates in PEM format (order is maintained)
2056+
pub fn der_to_pem(
2057+
der_certificates: Vec<Vec<u8>>
2058+
) -> std::result::Result<String, Box<dyn std::error::Error>> {
2059+
let mut pem_string = String::new();
2060+
for der in der_certificates.iter().rev() {
2061+
// Convert DER to X509
2062+
let cert = X509::from_der(&der)?;
2063+
// Convert X509 to PEM format
2064+
let pem = cert.to_pem()?;
2065+
// Append the PEM string to the result
2066+
pem_string.push_str(&String::from_utf8(pem)?);
2067+
}
2068+
Ok(pem_string)
2069+
}
2070+
2071+
/// Read certificate chain from TPM.
2072+
///
2073+
/// Read content of NV Handle 0x01c00100 - 0x01c001ff
2074+
///
2075+
/// # Returns
2076+
///
2077+
/// `Vec<u8>', binary data of certificate chain
2078+
pub fn read_ek_ca_chain(context: &mut tss_esapi::Context) -> tss_esapi::Result<Vec<u8>> {
2079+
let mut result: Vec<u8> = Vec::new();
2080+
2081+
// Get handles for NV-Index in range 0x01c00100 - 0x01c001ff
2082+
let (capabilities, _) = context.get_capability(
2083+
CapabilityType::Handles,
2084+
RSA_EK_CERTIFICATE_CHAIN_START,
2085+
RSA_EK_CERTIFICATE_CHAIN_END - RSA_EK_CERTIFICATE_CHAIN_START,
2086+
)?;
2087+
2088+
if let CapabilityData::Handles(handle_list) = capabilities {
2089+
for handle in handle_list.iter() {
2090+
if let TpmHandle::NvIndex(nv_idx) = handle {
2091+
// Attempt to get the NV authorization handle
2092+
let nv_auth_handle = context.execute_without_session(|ctx| {
2093+
ctx.tr_from_tpm_public(*handle)
2094+
.map(|v| NvAuth::NvIndex(v.into()))
2095+
})?;
2096+
2097+
// Read the full NV data
2098+
let data = context.execute_with_nullauth_session(|ctx| {
2099+
nv::read_full(ctx, nv_auth_handle, *nv_idx)
2100+
})?;
2101+
2102+
result.extend(data);
2103+
} else {
2104+
// Handle other types of handles if necessary
2105+
break; // Skip non-NvIndex handles
2106+
}
2107+
}
2108+
}
2109+
2110+
Ok(result) // Return the accumulated result
2111+
}
2112+
19502113
pub mod testing {
19512114
use super::*;
19522115
#[cfg(feature = "testing")]

0 commit comments

Comments
 (0)