|
| 1 | +use anyhow::{anyhow, bail, Error}; |
| 2 | +use dcap_quote_verifier::cert::{get_x509_subject_cn, parse_certchain}; |
| 3 | +use dcap_quote_verifier::collaterals::IntelCollateral; |
| 4 | +use dcap_quote_verifier::sgx_extensions::extract_sgx_extensions; |
| 5 | +use dcap_types::quotes::CertData; |
| 6 | +use dcap_types::utils::{parse_pem, pem_to_der}; |
| 7 | +use log::*; |
| 8 | + |
| 9 | +/// The URL of the Provisioning Certification Service (PCS). |
| 10 | +pub const INTEL_SGX_PCS_URL: &str = "https://api.trustedservices.intel.com"; |
| 11 | +/// The URL of the Intel SGX Certificates Service. |
| 12 | +pub const INTEL_SGX_CERTS_URL: &str = "https://certificates.trustedservices.intel.com"; |
| 13 | + |
| 14 | +/// PCSClient is a client for the Provisioning Certification Service (PCS) or Provisioning Certification Caching Service (PCCS). |
| 15 | +#[derive(Debug)] |
| 16 | +pub struct PCSClient { |
| 17 | + /// The URL of the Provisioning Certification Service (PCS) or Provisioning Certification Caching Service (PCCS). |
| 18 | + pcs_or_pccs_url: String, |
| 19 | + /// The URL of the Intel SGX Certificates Service. |
| 20 | + certs_service_url: String, |
| 21 | + /// Whether to use the early update or standard update to get the TCB info. |
| 22 | + is_early_update: bool, |
| 23 | +} |
| 24 | + |
| 25 | +impl Default for PCSClient { |
| 26 | + /// Default PCSClient uses Intel's PCS and Certificates Service URLs. |
| 27 | + fn default() -> Self { |
| 28 | + PCSClient::new(INTEL_SGX_PCS_URL, INTEL_SGX_CERTS_URL, false) |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +impl PCSClient { |
| 33 | + /// Create a new PCSClient. |
| 34 | + /// |
| 35 | + /// # Arguments |
| 36 | + /// * `pcs_or_pccs_url` - The URL of the Provisioning Certification Service (PCS) or Provisioning Certification Caching Service (PCCS). |
| 37 | + /// * `certs_service_url` - The URL of the Intel SGX Certificates Service. |
| 38 | + /// * `is_early_update` - Whether to use the early update policy. |
| 39 | + pub fn new(pcs_or_pccs_url: &str, certs_service_url: &str, is_early_update: bool) -> Self { |
| 40 | + PCSClient { |
| 41 | + pcs_or_pccs_url: pcs_or_pccs_url.trim_end_matches('/').to_string(), |
| 42 | + certs_service_url: certs_service_url.trim_end_matches('/').to_string(), |
| 43 | + is_early_update, |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /// Get the collateral required for verifying a DCAP quote. |
| 48 | + /// |
| 49 | + /// # Arguments |
| 50 | + /// * `qe_cert_data` - The certificate data of the QE that generated the quote to be verified. The certificate data type must be 5. |
| 51 | + pub fn get_collateral(&self, qe_cert_data: &CertData) -> Result<IntelCollateral, Error> { |
| 52 | + let pcs_url = self.pcs_or_pccs_url.as_str(); |
| 53 | + let certs_service_url = self.certs_service_url.as_str(); |
| 54 | + let base_url = format!("{pcs_url}/sgx/certification/v4"); |
| 55 | + if qe_cert_data.cert_data_type != 5 { |
| 56 | + bail!("QE Cert Type must be 5".to_string()); |
| 57 | + } |
| 58 | + let certchain_pems = parse_pem(&qe_cert_data.cert_data) |
| 59 | + .map_err(|e| anyhow!("cannot parse QE cert chain: {}", e))?; |
| 60 | + |
| 61 | + let certchain = parse_certchain(&certchain_pems) |
| 62 | + .map_err(|e| anyhow!("cannot parse QE cert chain: {}", e))?; |
| 63 | + if certchain.len() != 3 { |
| 64 | + bail!("QE Cert chain must have 3 certs".to_string()); |
| 65 | + } |
| 66 | + |
| 67 | + // get the pck certificate |
| 68 | + let pck_cert = &certchain[0]; |
| 69 | + let pck_cert_issuer = &certchain[1]; |
| 70 | + |
| 71 | + // get the SGX extension |
| 72 | + let sgx_extensions = extract_sgx_extensions(pck_cert) |
| 73 | + .map_err(|e| anyhow!("cannot extract SGX extensions: {}", e))?; |
| 74 | + |
| 75 | + let update_policy = self.update_policy(); |
| 76 | + |
| 77 | + // get the TCB info of the platform |
| 78 | + let (tcbinfo_bytes, sgx_tcb_signing_der) = { |
| 79 | + let fmspc = hex::encode_upper(sgx_extensions.fmspc); |
| 80 | + let res = http_get(format!( |
| 81 | + "{base_url}/tcb?fmspc={fmspc}&update={update_policy}" |
| 82 | + ))?; |
| 83 | + let issuer_chain = |
| 84 | + extract_raw_certs(get_header(&res, "TCB-Info-Issuer-Chain")?.as_bytes())?; |
| 85 | + (res.bytes()?.to_vec(), issuer_chain[0].clone()) |
| 86 | + }; |
| 87 | + |
| 88 | + // get the QE identity |
| 89 | + let qeidentity_bytes = http_get(format!("{base_url}/qe/identity?update={update_policy}"))? |
| 90 | + .bytes()? |
| 91 | + .to_vec(); |
| 92 | + |
| 93 | + let pck_crl_url = match get_x509_subject_cn(pck_cert_issuer).as_str() { |
| 94 | + "Intel SGX PCK Platform CA" => format!("{base_url}/pckcrl?ca=platform&encoding=der"), |
| 95 | + "Intel SGX PCK Processor CA" => format!("{base_url}/pckcrl?ca=processor&encoding=der"), |
| 96 | + cn => { |
| 97 | + bail!("unknown PCK issuer: {}", cn); |
| 98 | + } |
| 99 | + }; |
| 100 | + let sgx_pck_crl_der = http_get(pck_crl_url)?.bytes()?.to_vec(); |
| 101 | + |
| 102 | + let sgx_root_cert_der = http_get(format!( |
| 103 | + "{certs_service_url}/Intel_SGX_Provisioning_Certification_RootCA.cer" |
| 104 | + ))? |
| 105 | + .bytes()? |
| 106 | + .to_vec(); |
| 107 | + |
| 108 | + let sgx_intel_root_ca_crl_der = |
| 109 | + http_get(format!("{certs_service_url}/IntelSGXRootCA.der"))? |
| 110 | + .bytes()? |
| 111 | + .to_vec(); |
| 112 | + |
| 113 | + Ok(IntelCollateral { |
| 114 | + tcbinfo_bytes, |
| 115 | + qeidentity_bytes, |
| 116 | + sgx_intel_root_ca_der: sgx_root_cert_der, |
| 117 | + sgx_tcb_signing_der, |
| 118 | + sgx_intel_root_ca_crl_der, |
| 119 | + sgx_pck_crl_der, |
| 120 | + }) |
| 121 | + } |
| 122 | + |
| 123 | + /// Get the PCK certificate. |
| 124 | + /// |
| 125 | + /// # Arguments |
| 126 | + /// * `encrypted_ppid` - The PPID encrypted with PPIDEK. |
| 127 | + /// * `cpusvn` - The CPUSVN value. |
| 128 | + /// * `pcesvn` - The PCESVN value. |
| 129 | + /// * `pceid` - The PCE-ID value. |
| 130 | + /// # Returns |
| 131 | + /// * The PCK certificate and the PCK issuer certificate. |
| 132 | + pub fn get_pck_certchain( |
| 133 | + &self, |
| 134 | + encrypted_ppid: &[u8; 384], |
| 135 | + cpusvn: &[u8; 16], |
| 136 | + pcesvn: u16, |
| 137 | + pceid: u16, |
| 138 | + ) -> Result<PckCertResponse, Error> { |
| 139 | + let pcs_url = self.pcs_or_pccs_url.as_str(); |
| 140 | + let base_url = format!("{pcs_url}/sgx/certification/v4/pckcert"); |
| 141 | + let res = reqwest::blocking::Client::new() |
| 142 | + .get(&base_url) |
| 143 | + .query(&[ |
| 144 | + ("encrypted_ppid", hex::encode(encrypted_ppid)), |
| 145 | + ("cpusvn", hex::encode(cpusvn)), |
| 146 | + ("pcesvn", hex::encode(pcesvn.to_le_bytes())), |
| 147 | + ("pceid", hex::encode(pceid.to_le_bytes())), |
| 148 | + ]) |
| 149 | + .send() |
| 150 | + .map_err(|e| anyhow!("cannot get {}: {}", base_url, e))?; |
| 151 | + if !res.status().is_success() { |
| 152 | + bail!("invalid http status: {}", res.status()); |
| 153 | + } |
| 154 | + Ok(PckCertResponse { |
| 155 | + fmspc: hex::decode(get_header(&res, "SGX-FMSPC")?)? |
| 156 | + .try_into() |
| 157 | + .map_err(|e| anyhow!("cannot convert to array: {:?}", e))?, |
| 158 | + pck_ca_type: get_header(&res, "SGX-PCK-Certificate-CA-Type")?, |
| 159 | + pck_cert_issuer_der: extract_raw_certs( |
| 160 | + get_header(&res, "SGX-PCK-Certificate-Issuer-Chain")?.as_bytes(), |
| 161 | + )?[1] |
| 162 | + .clone(), |
| 163 | + pck_cert_der: pem_to_der( |
| 164 | + res.bytes() |
| 165 | + .map_err(|e| anyhow!("cannot get bytes: {}", e))? |
| 166 | + .to_vec() |
| 167 | + .as_ref(), |
| 168 | + )?, |
| 169 | + }) |
| 170 | + } |
| 171 | + |
| 172 | + fn update_policy(&self) -> &str { |
| 173 | + if self.is_early_update { |
| 174 | + "early" |
| 175 | + } else { |
| 176 | + "standard" |
| 177 | + } |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +#[derive(Debug, Clone, Default)] |
| 182 | +pub struct PckCertResponse { |
| 183 | + pub pck_cert_der: Vec<u8>, |
| 184 | + pub pck_cert_issuer_der: Vec<u8>, |
| 185 | + pub fmspc: [u8; 6], |
| 186 | + pub pck_ca_type: String, |
| 187 | +} |
| 188 | + |
| 189 | +fn get_header(res: &reqwest::blocking::Response, name: &str) -> Result<String, Error> { |
| 190 | + let value = res |
| 191 | + .headers() |
| 192 | + .get(name) |
| 193 | + .ok_or_else(|| anyhow!("missing header: {}", name))? |
| 194 | + .to_str() |
| 195 | + .map_err(|e| anyhow!("invalid header value: {}", e))?; |
| 196 | + let value = |
| 197 | + urlencoding::decode(value).map_err(|e| anyhow!("cannot decode header value: {}", e))?; |
| 198 | + Ok(value.into_owned()) |
| 199 | +} |
| 200 | + |
| 201 | +fn extract_raw_certs(cert_chain: &[u8]) -> Result<Vec<Vec<u8>>, Error> { |
| 202 | + Ok(pem::parse_many(cert_chain) |
| 203 | + .map_err(|e| anyhow!("cannot parse PEM: {}", e))? |
| 204 | + .iter() |
| 205 | + .map(|i| i.contents().to_vec()) |
| 206 | + .collect()) |
| 207 | +} |
| 208 | + |
| 209 | +fn http_get(url: String) -> Result<reqwest::blocking::Response, Error> { |
| 210 | + debug!("get collateral from {}", url); |
| 211 | + let res = reqwest::blocking::get(&url).map_err(|e| anyhow!("cannot get {}: {}", url, e))?; |
| 212 | + if !res.status().is_success() { |
| 213 | + bail!("invalid http status: {}", res.status()); |
| 214 | + } |
| 215 | + Ok(res) |
| 216 | +} |
| 217 | + |
| 218 | +#[cfg(test)] |
| 219 | +mod tests { |
| 220 | + use super::*; |
| 221 | + |
| 222 | + #[test] |
| 223 | + fn test_get_collateral() { |
| 224 | + let qe_cert_data_bz = hex::decode("0500dc0d00002d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949456a544343424453674177494241674956414a34674a3835554b6b7a613873504a4847676e4f4b6d5451426e754d416f4743437147534d343942414d430a4d484578497a416842674e5642414d4d476b6c756447567349464e48574342515130736755484a765932567a6332397949454e424d526f77474159445651514b0a4442464a626e526c6243424462334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e560a4241674d416b4e424d517377435159445651514745774a56557a4165467730794e5441784d6a41784d444d7a4e4446614677307a4d6a41784d6a41784d444d7a0a4e4446614d484178496a416742674e5642414d4d47556c756447567349464e4857434251513073675132567964476c6d61574e6864475578476a415942674e560a42416f4d45556c756447567349454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b470a413155454341774351304578437a414a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741450a516a537877644d662b2b3578645553717478343769335952633970504a475434304642774e306e5335557a43314233524b63544875514c3135796b357a4c766c0a5535707a7563552f2b6d674a4e6f55774b6e784942364f434171677767674b6b4d42384741315564497751594d426141464e446f71747031312f6b75535265590a504873555a644456386c6c4e4d477747413155644877526c4d474d77596142666f463247573268306448427a4f693876595842704c6e527964584e305a57527a0a5a584a3261574e6c63793570626e526c6243356a62323076633264344c324e6c636e52705a6d6c6a5958527062323476646a517663474e7259334a7350324e680a5058427962324e6c63334e7663695a6c626d4e765a476c755a7a316b5a584977485159445652304f42425945464f7632356e4f67634c754f693644424b3037470a4d4f5161315a53494d41344741315564447745422f775145417749477744414d42674e5648524d4241663845416a41414d4949423141594a4b6f5a496876684e0a415130424249494278544343416345774867594b4b6f5a496876684e41513042415151514459697469663748386e4277566732482b38504f476a4343415751470a43697147534962345451454e41514977676746554d42414743797147534962345451454e41514942416745564d42414743797147534962345451454e415149430a416745564d42414743797147534962345451454e41514944416745434d42414743797147534962345451454e41514945416745454d42414743797147534962340a5451454e41514946416745424d42454743797147534962345451454e41514947416749416744415142677371686b69472b4530424451454342774942446a41510a42677371686b69472b45304244514543434149424144415142677371686b69472b45304244514543435149424144415142677371686b69472b453042445145430a436749424144415142677371686b69472b45304244514543437749424144415142677371686b69472b45304244514543444149424144415142677371686b69470a2b45304244514543445149424144415142677371686b69472b45304244514543446749424144415142677371686b69472b4530424451454344774942414441510a42677371686b69472b45304244514543454149424144415142677371686b69472b45304244514543455149424454416642677371686b69472b453042445145430a4567515146525543424147414467414141414141414141414144415142676f71686b69472b45304244514544424149414144415542676f71686b69472b4530420a44514545424159416b473756414141774477594b4b6f5a496876684e4151304242516f424144414b42676771686b6a4f5051514441674e4841444245416942750a6846786c7379536f4a373479392f374665436c6679522b544d4a626c43696663364e577538637466424149674c524f6e50584138636d3864577061716f4679680a467559567237396f696f584e63395354677857573332633d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436d444343416a36674177494241674956414e446f71747031312f6b7553526559504873555a644456386c6c4e4d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c756447567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a62334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a45784d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484578497a41680a42674e5642414d4d476b6c756447567349464e48574342515130736755484a765932567a6332397949454e424d526f77474159445651514b4442464a626e526c0a6243424462334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e420a4d517377435159445651514745774a56557a425a4d424d4742797147534d34394167454743437147534d34394177454841304941424c39712b4e4d7032494f670a74646c31626b2f75575a352b5447516d38614369387a373866732b664b435133642b75447a586e56544154325a68444369667949754a77764e33774e427039690a484253534d4a4d4a72424f6a6762737767626777487759445652306a42426777466f4155496d554d316c71644e496e7a6737535655723951477a6b6e427177770a556759445652306642457377535442486f45576751345a426148523063484d364c79396a5a584a3061575a70593246305a584d7564484a316333526c5a484e6c0a636e5a705932567a4c6d6c75644756734c6d4e766253394a626e526c62464e4857464a76623352445153356b5a584977485159445652304f42425945464e446f0a71747031312f6b7553526559504873555a644456386c6c4e4d41344741315564447745422f77514541774942426a415342674e5648524d4241663845434441470a4151482f416745414d416f4743437147534d343942414d43413067414d4555434951434a6754627456714f795a316d336a716941584d365159613672357357530a34792f4737793875494a4778647749675271507642534b7a7a516167424c517135733541373070646f6961524a387a2f3075447a344e675639316b3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a6737535655723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d4267474131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e760a636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a55723951477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b533943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c72486f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a00").unwrap(); |
| 225 | + let cert_data = CertData::from_bytes(&qe_cert_data_bz); |
| 226 | + let client = PCSClient::default(); |
| 227 | + let res = client.get_collateral(&cert_data); |
| 228 | + assert!(res.is_ok(), "{:?}", res); |
| 229 | + } |
| 230 | + |
| 231 | + #[test] |
| 232 | + fn get_pck_cert() { |
| 233 | + let encrypted_ppid = hex::decode("500ddb99c48fe783f9fa0f380757787f48da3807d9d9b04f72bbc4ef7421a1418caeaf5483703568ea7be76ed21e0bd40fffd109b366e58101b8e9fd33c35354caaf5105d45e7f48c6ecca02e81cc86a2b174de4c78b08790d00523f07ab7312b19c0ebf5bf108d21d1410c9bd0c54c3687af21c30a123f12cbea2834eb5046b3447eb539bb18e91298709fc4f923ed2f6e3974487f0529c4c6a9fd28bb283ff958bb9f918506f54e7750be4aa8fde198be7beda22ef91bebfb88731d8e64681a0e6fbd44534a30028170ec6e3fe7df37abc1ec0621137ad298caf0acb5c52439bf85c6a9c1795f889937a23f3ada145dab50011005d0b1e5fec879deeee1041e04d2abfa843e037762002767aff46462dcaf3b6f601ab921758c6a4ea45ede1325cbfb00999598fcd19f5321fb110d20d7574a7e6efa67409a87c062b80db377e7fc22a0f833a20c26e976f44ca45b234c1aedd3a55ee365a1eab489f7e7b5e3ecdd631529660d6d4b6c67e7a753de5d38128cc167a632b5fc0a55b7e009d96").unwrap(); |
| 234 | + let cpusvn = hex::decode("15150b07ff800e000000000000000000").unwrap(); |
| 235 | + let pcesvn = u16::from_le_bytes(hex::decode("1000").unwrap().try_into().unwrap()); |
| 236 | + let pceid = u16::from_le_bytes(hex::decode("0000").unwrap().try_into().unwrap()); |
| 237 | + let client = PCSClient::default(); |
| 238 | + let res = client.get_pck_certchain( |
| 239 | + &encrypted_ppid.try_into().unwrap(), |
| 240 | + &cpusvn.try_into().unwrap(), |
| 241 | + pcesvn, |
| 242 | + pceid, |
| 243 | + ); |
| 244 | + assert!(res.is_ok(), "{:?}", res); |
| 245 | + let res = res.unwrap(); |
| 246 | + assert_eq!(res.pck_ca_type, "processor"); |
| 247 | + assert_eq!(res.fmspc, [0, 144, 110, 213, 0, 0]); |
| 248 | + } |
| 249 | +} |
0 commit comments