Skip to content

Commit 9a06d06

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 9a06d06

File tree

3 files changed

+184
-18
lines changed

3 files changed

+184
-18
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

+167-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use tss_esapi::{
2828
abstraction::{
2929
ak,
3030
cipher::Cipher,
31-
ek,
31+
ek, nv,
3232
pcr::{read_all, PcrData},
3333
DefaultKey,
3434
},
@@ -37,6 +37,7 @@ use tss_esapi::{
3737
},
3838
constants::{
3939
response_code::Tss2ResponseCodeKind, session_type::SessionType,
40+
CapabilityType,
4041
},
4142
handles::{
4243
AuthHandle, KeyHandle, ObjectHandle, PcrHandle, PersistentTpmHandle,
@@ -46,14 +47,14 @@ use tss_esapi::{
4647
algorithm::{AsymmetricAlgorithm, HashingAlgorithm, PublicAlgorithm},
4748
ecc::EccCurve,
4849
key_bits::RsaKeyBits,
49-
resource_handles::Hierarchy,
50+
resource_handles::{Hierarchy, NvAuth},
5051
session_handles::AuthSession,
5152
structure_tags::AttestationType,
5253
},
5354
structures::{
54-
Attest, AttestInfo, Auth, Data, Digest, DigestValues, EccParameter,
55-
EccPoint, EccScheme, EncryptedSecret, HashScheme, IdObject,
56-
KeyDerivationFunctionScheme, Name, PcrSelectionList,
55+
Attest, AttestInfo, Auth, CapabilityData, Data, Digest, DigestValues,
56+
EccParameter, EccPoint, EccScheme, EncryptedSecret, HashScheme,
57+
IdObject, KeyDerivationFunctionScheme, Name, PcrSelectionList,
5758
PcrSelectionListBuilder, PcrSlot, Private as TssPrivate,
5859
Public as TssPublic, PublicBuilder, PublicEccParametersBuilder,
5960
PublicKeyRsa, PublicRsaParametersBuilder, RsaExponent, RsaScheme,
@@ -116,6 +117,9 @@ const IAK_AUTH_POLICY_SHA256: [u8; 32] = [
116117
];
117118
const UNIQUE_IAK: [u8; 3] = [0x49, 0x41, 0x4b];
118119

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

449486
/// Holds the output of create_ak.
@@ -612,10 +649,25 @@ impl Context<'_> {
612649
let (tpm_pub, _, _) = ctx
613650
.read_public(key_handle)
614651
.map_err(|source| TpmError::TSSReadPublicError { source })?;
652+
653+
let chain = match read_ek_ca_chain(&mut ctx) {
654+
Ok(der_data) => {
655+
if !der_data.is_empty() {
656+
info!("Found EK certificate chain in TPM NVRAM")
657+
}
658+
Some(der_data)
659+
}
660+
Err(_) => {
661+
warn!("Failed reading EK certificate chain from TPM NVRAM");
662+
None
663+
}
664+
};
665+
615666
Ok(EKResult {
616667
key_handle,
617668
ek_cert: cert,
618669
public: tpm_pub,
670+
ek_chain: chain,
619671
})
620672
}
621673

@@ -1947,6 +1999,116 @@ pub fn check_pubkey_match_cert(
19471999
}
19482000
}
19492001

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

0 commit comments

Comments
 (0)