lit_utilities_wasm/
sev_snp.rs1use std::collections::BTreeMap;
2
3use js_sys::Uint8Array;
4use sev::certs::snp::Certificate;
5use sev::firmware::host::TcbVersion;
6use sha2::{Digest, Sha512};
7use wasm_bindgen::prelude::*;
8
9use sev::certs::snp::{builtin::milan, ca, Chain, Verifiable};
10use sev::firmware::guest::AttestationReport;
11
12use crate::abi::{from_js, JsResult};
13
14#[wasm_bindgen(js_name = "sevSnpGetVcekUrl")]
16pub fn sev_snp_get_vcek_url(attestation_report: &[u8]) -> JsResult<String> {
17 let attestation_report = parse_attestation_report(attestation_report)?;
18 let url = get_vcek_url(attestation_report);
19 Ok(url)
20}
21
22fn get_vcek_url(attestation_report: AttestationReport) -> String {
23 const KDS_CERT_SITE: &str = "https://kdsintf.amd.com";
24 #[allow(dead_code)]
25 const KDS_DEV_CERT_SITE: &str = "https://kdsintfdev.amd.com";
26
27 #[allow(dead_code)]
28 const KDS_CEK: &str = "/cek/id/";
29
30 const KDS_VCEK: &str = "/vcek/v1/"; #[allow(dead_code)]
32 const KDS_VCEK_CERT_CHAIN: &str = "cert_chain"; #[allow(dead_code)]
34 const KDS_VCEK_CRL: &str = "crl"; const PRODUCT_NAME: &str = "Milan";
37
38 let AttestationReport {
39 chip_id,
40 reported_tcb:
41 TcbVersion {
42 bootloader,
43 tee,
44 snp,
45 microcode,
46 ..
47 },
48 ..
49 } = attestation_report;
50
51 format!(
52 "{}{}{}/{}?blSPL={bootloader:0>2}&teeSPL={tee:0>2}&snpSPL={snp:0>2}&ucodeSPL={microcode:0>2}",
53 KDS_CERT_SITE, KDS_VCEK, PRODUCT_NAME, hex::encode(chip_id)
54 )
55}
56
57#[wasm_bindgen]
58extern "C" {
59 #[wasm_bindgen(typescript_type = "Record<string, Uint8Array>")]
60 pub type AttestationData;
61}
62
63#[wasm_bindgen(js_name = "sevSnpVerify")]
64pub fn sev_snp_verify(
65 attestation_report: &[u8],
66 attestation_data: AttestationData,
67 signatures: Vec<Uint8Array>,
68 challenge: &[u8],
69 vcek_certificate: &[u8],
70) -> JsResult<()> {
71 let attestation_report = parse_attestation_report(attestation_report)?;
72 let attestation_data = from_js(attestation_data)?;
73 let signatures = signatures
74 .into_iter()
75 .map(from_js::<Vec<u8>>)
76 .collect::<JsResult<Vec<_>>>()?;
77 let vcek_certificate = parse_certificate(vcek_certificate)?;
78
79 verify_certificate(vcek_certificate, attestation_report)?;
80 verify_challenge(challenge, attestation_data, signatures, attestation_report)?;
81
82 Ok(())
83}
84
85fn parse_attestation_report(attestation_report: &[u8]) -> JsResult<AttestationReport> {
86 let report = unsafe { std::ptr::read(attestation_report.as_ptr() as *const _) };
87 Ok(report)
89}
90
91fn parse_certificate(vcek_certificate: &[u8]) -> JsResult<Certificate> {
92 Certificate::from_der(vcek_certificate).map_err(|e| JsError::new(e.to_string().as_str()))
93}
94
95fn verify_certificate(vcek: Certificate, report: AttestationReport) -> JsResult<()> {
96 let ark = milan::ark().unwrap();
97 let ask = milan::ask().unwrap();
98
99 let ca = ca::Chain { ark, ask };
100
101 let chain = Chain { ca, vcek };
102
103 (&chain, &report)
104 .verify()
105 .map_err(|e| JsError::new(e.to_string().as_str()))
106}
107
108fn verify_challenge(
109 challenge: &[u8],
110 data: BTreeMap<String, Vec<u8>>,
111 signatures: Vec<Vec<u8>>,
112 attestation_report: AttestationReport,
113) -> JsResult<()> {
114 let expected_report_data = get_expected_report_data(data, signatures, challenge);
115
116 if attestation_report.report_data != expected_report_data {
117 return Err(
118 JsError::new(
119 "Report data does not match. This generally indicates that the data, challenge/nonce, or signatures are bad."
120 )
121 );
122 }
123 Ok(())
124}
125
126fn get_expected_report_data(
127 data: BTreeMap<String, Vec<u8>>,
128 signatures: Vec<Vec<u8>>,
129 challenge: &[u8],
130) -> [u8; 64] {
131 let mut hasher = Sha512::new();
132
133 hasher.update("noonce");
134 hasher.update(challenge);
135
136 hasher.update("data");
137 for (key, value) in data {
138 hasher.update(key);
139 hasher.update(value);
140 }
141
142 if signatures.is_empty() {
144 hasher.update("signatures");
145
146 for s in &signatures[..signatures.len() - 1] {
148 hasher.update(s);
149 }
150 }
151
152 let result = hasher.finalize();
153 let mut array = [0u8; 64];
154 array.copy_from_slice(&result[..]);
155 array
156}