|
3 | 3 | use crate::VerifyOptions; |
4 | 4 | use anyhow::{Context, Result}; |
5 | 5 | use asn1_rs::{Oid, oid}; |
6 | | -use openssl::{ecdsa::EcdsaSig, sha::Sha384}; |
| 6 | +use openssl::{ |
| 7 | + ecdsa::EcdsaSig, |
| 8 | + sha::Sha384, |
| 9 | + x509::{X509, X509Crl}, |
| 10 | +}; |
7 | 11 | use sev::certs::snp::{Certificate, Verifiable}; |
8 | 12 | use sev::firmware::guest::AttestationReport; |
9 | 13 | use sev::parser::ByteParser; |
@@ -71,6 +75,8 @@ pub fn verify_report( |
71 | 75 | println!("VCEK was signed by ASK"); |
72 | 76 | } |
73 | 77 |
|
| 78 | + verify_revocation_list(certs_dir, &ark, &ask, &vcek, opts)?; |
| 79 | + |
74 | 80 | let vcek_pubkey = vcek |
75 | 81 | .public_key() |
76 | 82 | .context("Failed to get public key from VCEK")? |
@@ -119,6 +125,75 @@ fn find_cert_in_dir(dir: &Path, name: &str) -> Result<PathBuf> { |
119 | 125 | )) |
120 | 126 | } |
121 | 127 |
|
| 128 | +fn load_crl(path: &Path) -> Result<X509Crl> { |
| 129 | + let data = fs::read(path).context(format!("Failed to read certificate {}", path.display()))?; |
| 130 | + let is_pem = path.extension().map(|ext| ext == "pem").unwrap_or(false); |
| 131 | + if is_pem { |
| 132 | + X509Crl::from_pem(&data).context("Failed to parse PEM certificate") |
| 133 | + } else { |
| 134 | + X509Crl::from_der(&data).context("Failed to parse DER certificate") |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +/// Checks that the ASK and VCEK have not been revoked by consulting the CRL |
| 139 | +fn verify_revocation_list( |
| 140 | + certs_dir: &Path, |
| 141 | + ark: &Certificate, |
| 142 | + ask: &Certificate, |
| 143 | + vcek: &Certificate, |
| 144 | + opts: &VerifyOptions, |
| 145 | +) -> Result<()> { |
| 146 | + let Ok(path) = find_cert_in_dir(certs_dir, "crl") else { |
| 147 | + eprintln!( |
| 148 | + "Warning: no CRL found in {}; ASK revocation status not checked", |
| 149 | + certs_dir.display() |
| 150 | + ); |
| 151 | + return Ok(()); |
| 152 | + }; |
| 153 | + |
| 154 | + let crl = load_crl(&path)?; |
| 155 | + |
| 156 | + let ark_pubkey = ark.public_key().context("Failed to get ARK public key")?; |
| 157 | + if !crl |
| 158 | + .verify(&ark_pubkey) |
| 159 | + .context("Failed to verify CRL signature")? |
| 160 | + { |
| 161 | + return Err(anyhow::anyhow!("CRL is not signed by ARK")); |
| 162 | + } |
| 163 | + |
| 164 | + let ask_serial = X509::from(ask) |
| 165 | + .serial_number() |
| 166 | + .to_bn() |
| 167 | + .context("Failed to read ASK serial number")?; |
| 168 | + let vcek_serial = X509::from(vcek) |
| 169 | + .serial_number() |
| 170 | + .to_bn() |
| 171 | + .context("Failed to read VCEK serial number")?; |
| 172 | + |
| 173 | + // AMD uses TCB versioning rather than CRL entries to supersede old VCEKs, so |
| 174 | + // VCEK revocation is not expected in practice, but check anyway for completeness. |
| 175 | + if let Some(revoked) = crl.get_revoked() { |
| 176 | + for entry in revoked { |
| 177 | + let entry_serial = entry |
| 178 | + .serial_number() |
| 179 | + .to_bn() |
| 180 | + .context("Failed to read revoked entry serial number")?; |
| 181 | + if entry_serial == ask_serial { |
| 182 | + return Err(anyhow::anyhow!("ASK certificate has been revoked")); |
| 183 | + } |
| 184 | + if entry_serial == vcek_serial { |
| 185 | + return Err(anyhow::anyhow!("VCEK certificate has been revoked")); |
| 186 | + } |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + if !opts.quiet { |
| 191 | + println!("ASK and VCEK are not revoked"); |
| 192 | + } |
| 193 | + |
| 194 | + Ok(()) |
| 195 | +} |
| 196 | + |
122 | 197 | fn load_certificate(path: &Path) -> Result<Certificate> { |
123 | 198 | let data = fs::read(path).context(format!("Failed to read certificate {}", path.display()))?; |
124 | 199 |
|
|
0 commit comments