Skip to content

Commit 0da0952

Browse files
committed
snp: use cache for VCEKs
Let's use the cache for VCEKs. This will reduce traffic to the KDS, eliminate timeouts when we retry the attestation several times in succession, and allow air-gapped environments to pre-provsion VCEKs. Use the URL for the KDS as the key, since it has all the information needed to define a particular VCEK. Signed-off-by: Tobin Feldman-Fitzthum <[email protected]>
1 parent 45ca821 commit 0da0952

File tree

2 files changed

+99
-73
lines changed

2 files changed

+99
-73
lines changed

deps/verifier/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub fn to_verifier(tee: &Tee, _cache: Arc<dyn Cache + Send + Sync>) -> Result<Bo
8282
Tee::Snp => {
8383
cfg_if::cfg_if! {
8484
if #[cfg(feature = "snp-verifier")] {
85-
let verifier = snp::Snp::default();
85+
let verifier = snp::Snp::new(_cache);
8686
Ok(Box::new(verifier) as Box<dyn Verifier + Send + Sync>)
8787
} else {
8888
bail!("feature `snp-verifier` is not enabled for `verifier` crate.")

deps/verifier/src/snp/mod.rs

Lines changed: 98 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ pub(crate) static CERT_CHAINS: LazyLock<HashMap<ProcessorGeneration, VendorCerti
8888
map
8989
});
9090

91-
#[derive(Default, Debug)]
92-
pub struct Snp {}
91+
pub struct Snp {
92+
cache: Arc<dyn Cache + Send + Sync>,
93+
}
9394

9495
#[derive(Clone, Debug)]
9596
pub(crate) enum VendorEndorsementKey {
@@ -220,7 +221,7 @@ impl Verifier for Snp {
220221
// No certificate chain provided, so we need to request the VCEK from KDS
221222
_ => {
222223
// Get VCEK from KDS
223-
let vcek_buf = fetch_vcek_from_kds(report, proc_gen.clone())
224+
let vcek_buf = self.fetch_vcek_from_kds(report, proc_gen.clone())
224225
.await
225226
.context("Failed to fetch VCEK from KDS")?;
226227
let vcek = Certificate::from_bytes(&vcek_buf)
@@ -287,6 +288,100 @@ impl Verifier for Snp {
287288
}
288289
}
289290

291+
impl Snp {
292+
pub fn new(cache: Arc<dyn Cache + Send + Sync>) -> Self {
293+
Self {
294+
cache,
295+
}
296+
}
297+
298+
/// Fetches VCEK in DER format from AMD KDS.
299+
/// If the VCEK is retrieved successfully, it is added to the cache,
300+
/// which is cheked prior to fetching the cert.
301+
/// If the VCEK cannot be fetched from the VCEK, this method
302+
/// does not retry.
303+
async fn fetch_vcek_from_kds(
304+
&self,
305+
att_report: AttestationReport,
306+
proc_gen: ProcessorGeneration,
307+
) -> Result<Vec<u8>> {
308+
// Use attestation report to get data for URL
309+
let hw_id: String = if att_report.chip_id.as_slice() != [0; 64] {
310+
match proc_gen {
311+
ProcessorGeneration::Turin => {
312+
let shorter_bytes: &[u8] = &att_report.chip_id[0..8];
313+
hex::encode(shorter_bytes)
314+
}
315+
_ => hex::encode(att_report.chip_id),
316+
}
317+
} else {
318+
bail!("Hardware ID is 0s on attestation report. Confirm that MASK_CHIP_ID is set to 0 to request from VCEK from KDS.");
319+
};
320+
321+
// Request VCEK from KDS
322+
let vcek_url: String = match proc_gen {
323+
ProcessorGeneration::Turin => {
324+
let fmc = if let Some(fmc) = att_report.reported_tcb.fmc {
325+
fmc
326+
} else {
327+
bail!("A Turin processor must have a fmc value");
328+
};
329+
format!(
330+
"{KDS_CERT_SITE}{KDS_VCEK}/{}/\
331+
{hw_id}?fmcSPL={:02}&blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",
332+
proc_gen,
333+
fmc,
334+
att_report.reported_tcb.bootloader,
335+
att_report.reported_tcb.tee,
336+
att_report.reported_tcb.snp,
337+
att_report.reported_tcb.microcode
338+
)
339+
}
340+
_ => {
341+
format!(
342+
"{KDS_CERT_SITE}{KDS_VCEK}/{}/\
343+
{hw_id}?blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",
344+
proc_gen,
345+
att_report.reported_tcb.bootloader,
346+
att_report.reported_tcb.tee,
347+
att_report.reported_tcb.snp,
348+
att_report.reported_tcb.microcode
349+
)
350+
}
351+
};
352+
353+
// Check cache for VCEK
354+
let vcek_cache_key = format!("snp_verifier_{vcek_url}");
355+
if let Some(cached_vcek_b64) = self.cache.get(vcek_cache_key.clone()).await {
356+
let vcek: Vec<u8> = STANDARD.decode(cached_vcek_b64)?;
357+
return Ok(vcek)
358+
}
359+
360+
// VCEK in DER format
361+
let vcek_rsp: ReqwestResponse = get(vcek_url.clone())
362+
.await
363+
.context("Unable to send request for VCEK")?;
364+
365+
match vcek_rsp.status() {
366+
StatusCode::OK => {
367+
let vcek_rsp_bytes: Vec<u8> = vcek_rsp
368+
.bytes()
369+
.await
370+
.context("Unable to parse VCEK")?
371+
.to_vec();
372+
373+
// Add fetched VCEK to cache
374+
let vcek_b64 = STANDARD.encode(&vcek_rsp_bytes);
375+
self.cache.set(vcek_cache_key, vcek_b64).await?;
376+
377+
Ok(vcek_rsp_bytes)
378+
}
379+
380+
status => bail!("Unable to fetch VCEK from URL: {status:?}, {vcek_url:?}"),
381+
}
382+
}
383+
}
384+
290385
/// Retrieves the octet string value for a given OID from a certificate's extensions.
291386
/// Supports both raw and DER-encoded formats.
292387
pub(crate) fn get_oid_octets<const N: usize>(
@@ -421,75 +516,6 @@ pub(crate) fn get_common_name(cert: &x509::X509) -> Result<String> {
421516
Ok(e.data().as_utf8()?.to_string())
422517
}
423518

424-
/// Asynchronously fetches the VCEK from the Key Distribution Service (KDS) using the provided attestation report.
425-
/// Returns the VCEK in DER format as part of a certificate table entry.
426-
async fn fetch_vcek_from_kds(
427-
att_report: AttestationReport,
428-
proc_gen: ProcessorGeneration,
429-
) -> Result<Vec<u8>> {
430-
// Use attestation report to get data for URL
431-
let hw_id: String = if att_report.chip_id.as_slice() != [0; 64] {
432-
match proc_gen {
433-
ProcessorGeneration::Turin => {
434-
let shorter_bytes: &[u8] = &att_report.chip_id[0..8];
435-
hex::encode(shorter_bytes)
436-
}
437-
_ => hex::encode(att_report.chip_id),
438-
}
439-
} else {
440-
bail!("Hardware ID is 0s on attestation report. Confirm that MASK_CHIP_ID is set to 0 to request from VCEK from KDS.");
441-
};
442-
443-
// Request VCEK from KDS
444-
let vcek_url: String = match proc_gen {
445-
ProcessorGeneration::Turin => {
446-
let fmc = if let Some(fmc) = att_report.reported_tcb.fmc {
447-
fmc
448-
} else {
449-
bail!("A Turin processor must have a fmc value");
450-
};
451-
format!(
452-
"{KDS_CERT_SITE}{KDS_VCEK}/{}/\
453-
{hw_id}?fmcSPL={:02}&blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",
454-
proc_gen,
455-
fmc,
456-
att_report.reported_tcb.bootloader,
457-
att_report.reported_tcb.tee,
458-
att_report.reported_tcb.snp,
459-
att_report.reported_tcb.microcode
460-
)
461-
}
462-
_ => {
463-
format!(
464-
"{KDS_CERT_SITE}{KDS_VCEK}/{}/\
465-
{hw_id}?blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",
466-
proc_gen,
467-
att_report.reported_tcb.bootloader,
468-
att_report.reported_tcb.tee,
469-
att_report.reported_tcb.snp,
470-
att_report.reported_tcb.microcode
471-
)
472-
}
473-
};
474-
// VCEK in DER format
475-
let vcek_rsp: ReqwestResponse = get(vcek_url.clone())
476-
.await
477-
.context("Unable to send request for VCEK")?;
478-
479-
match vcek_rsp.status() {
480-
StatusCode::OK => {
481-
let vcek_rsp_bytes: Vec<u8> = vcek_rsp
482-
.bytes()
483-
.await
484-
.context("Unable to parse VCEK")?
485-
.to_vec();
486-
Ok(vcek_rsp_bytes)
487-
}
488-
489-
status => bail!("Unable to fetch VCEK from URL: {status:?}, {vcek_url:?}"),
490-
}
491-
}
492-
493519
/// Determines the processor model based on the family and model IDs from the attestation report.
494520
fn get_processor_generation(att_report: &AttestationReport) -> Result<ProcessorGeneration> {
495521
let cpu_fam = att_report

0 commit comments

Comments
 (0)