use super::*;
use core::fmt;
use std::{fs, path::PathBuf, str::FromStr};
use reqwest::blocking::{get, Response};
use sev::firmware::host::CertType;
use certs::{write_cert, CertFormat};
#[derive(StructOpt)]
pub enum FetchCmd {
#[structopt(about = "Fetch the certificate authority (ARK & ASK) from the KDS.")]
CA(cert_authority::Args),
#[structopt(about = "Fetch the VCEK from the KDS.")]
Vcek(vcek::Args),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Endorsement {
Vcek,
Vlek,
}
impl fmt::Display for Endorsement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Endorsement::Vcek => write!(f, "VCEK"),
Endorsement::Vlek => write!(f, "VLEK"),
}
}
}
impl FromStr for Endorsement {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"vcek" => Ok(Self::Vcek),
"vlek" => Ok(Self::Vlek),
_ => Err(anyhow::anyhow!("Endorsement type not found!")),
}
}
}
#[derive(Debug, Clone)]
pub enum ProcType {
Milan,
Genoa,
Bergamo,
Siena,
}
impl ProcType {
fn to_kds_url(&self) -> String {
match self {
ProcType::Genoa | ProcType::Siena | ProcType::Bergamo => &ProcType::Genoa,
_ => self,
}
.to_string()
}
}
impl FromStr for ProcType {
type Err = anyhow::Error;
fn from_str(input: &str) -> Result<ProcType, anyhow::Error> {
match input.to_lowercase().as_str() {
"milan" => Ok(ProcType::Milan),
"genoa" => Ok(ProcType::Genoa),
"bergamo" => Ok(ProcType::Bergamo),
"siena" => Ok(ProcType::Siena),
_ => Err(anyhow::anyhow!("Processor type not found!")),
}
}
}
impl fmt::Display for ProcType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ProcType::Milan => write!(f, "Milan"),
ProcType::Genoa => write!(f, "Genoa"),
ProcType::Bergamo => write!(f, "Bergamo"),
ProcType::Siena => write!(f, "Siena"),
}
}
}
pub fn cmd(cmd: FetchCmd) -> Result<()> {
match cmd {
FetchCmd::CA(args) => cert_authority::fetch_ca(args),
FetchCmd::Vcek(args) => vcek::fetch_vcek(args),
}
}
mod cert_authority {
use super::*;
use openssl::x509::X509;
use reqwest::StatusCode;
#[derive(StructOpt)]
pub struct Args {
#[structopt(help = "Specify encoding to use for certificates. [PEM | DER]")]
pub encoding: CertFormat,
#[structopt(
help = "Specify the processor model for the certificate chain. [Milan | Genoa]"
)]
pub processor_model: ProcType,
#[structopt(help = "Directory to store the certificates in.")]
pub certs_dir: PathBuf,
#[structopt(
long,
short,
default_value = "vcek",
help = "Specify to pull the VLEK certificate chain instead of VCEK."
)]
pub endorser: Endorsement,
}
pub fn request_ca_kds(
processor_model: ProcType,
endorser: &Endorsement,
) -> Result<Vec<X509>, anyhow::Error> {
const KDS_CERT_SITE: &str = "https://kdsintf.amd.com";
const KDS_CERT_CHAIN: &str = "cert_chain";
let url: String = format!(
"{KDS_CERT_SITE}/{}/v1/{}/{KDS_CERT_CHAIN}",
endorser.to_string().to_lowercase(),
processor_model.to_kds_url()
);
let rsp: Response = get(url).context("Unable to send request for certs to URL")?;
match rsp.status() {
StatusCode::OK => {
let body = rsp
.bytes()
.context("Unable to parse AMD certificate chain")?
.to_vec();
let certificates = X509::stack_from_pem(&body)?;
Ok(certificates)
}
status => Err(anyhow::anyhow!("Unable to fetch certificate: {:?}", status)),
}
}
pub fn fetch_ca(args: Args) -> Result<()> {
let certificates = request_ca_kds(args.processor_model, &args.endorser)?;
if !args.certs_dir.exists() {
fs::create_dir(&args.certs_dir).context("Could not create certs folder")?;
}
let ark_cert = &certificates[1];
let ask_cert = &certificates[0];
write_cert(
&args.certs_dir,
&CertType::ARK,
&ark_cert.to_pem()?,
args.encoding,
&args.endorser,
)?;
write_cert(
&args.certs_dir,
&CertType::ASK,
&ask_cert.to_pem()?,
args.encoding,
&args.endorser,
)?;
Ok(())
}
}
mod vcek {
use reqwest::StatusCode;
use super::*;
#[derive(StructOpt)]
pub struct Args {
#[structopt(help = "Specify encoding to use for certificates. [PEM | DER]")]
pub encoding: CertFormat,
#[structopt(
help = "Specify the processor model for the certificate chain. [Milan | Genoa]"
)]
pub processor_model: ProcType,
#[structopt(help = "Directory to store the certificates in.")]
pub certs_dir: PathBuf,
#[structopt(help = "Path to attestation report to use to request VCEK.")]
pub att_report_path: PathBuf,
}
pub fn request_vcek_kds(
processor_model: ProcType,
att_report_path: PathBuf,
) -> Result<Vec<u8>, anyhow::Error> {
const KDS_CERT_SITE: &str = "https://kdsintf.amd.com";
const KDS_VCEK: &str = "/vcek/v1";
let att_report = if !att_report_path.exists() {
return Err(anyhow::anyhow!("No attestation report in provided path."));
} else {
report::read_report(att_report_path).context("Could not open attestation report")?
};
let hw_id: String = hex::encode(att_report.chip_id);
let vcek_url: String = format!(
"{KDS_CERT_SITE}{KDS_VCEK}/{}/\
{hw_id}?blSPL={:02}&teeSPL={:02}&snpSPL={:02}&ucodeSPL={:02}",
processor_model.to_kds_url(),
att_report.reported_tcb.bootloader,
att_report.reported_tcb.tee,
att_report.reported_tcb.snp,
att_report.reported_tcb.microcode
);
let vcek_rsp: Response = get(vcek_url).context("Unable to send request for VCEK")?;
match vcek_rsp.status() {
StatusCode::OK => {
let vcek_rsp_bytes: Vec<u8> =
vcek_rsp.bytes().context("Unable to parse VCEK")?.to_vec();
Ok(vcek_rsp_bytes)
}
status => Err(anyhow::anyhow!("Unable to fetch VCEK from URL: {status:?}")),
}
}
pub fn fetch_vcek(args: Args) -> Result<()> {
let vcek = request_vcek_kds(args.processor_model, args.att_report_path)?;
if !args.certs_dir.exists() {
fs::create_dir(&args.certs_dir).context("Could not create certs folder")?;
}
write_cert(
&args.certs_dir,
&CertType::VCEK,
&vcek,
args.encoding,
&Endorsement::Vcek,
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::ProcType;
#[test]
fn test_kds_prod_name_milan_base() {
let milan_proc: ProcType = ProcType::Milan;
assert_eq!(milan_proc.to_kds_url(), ProcType::Milan.to_string());
}
#[test]
fn test_kds_prod_name_genoa_base() {
assert_eq!(ProcType::Genoa.to_kds_url(), ProcType::Genoa.to_string());
assert_eq!(ProcType::Siena.to_kds_url(), ProcType::Genoa.to_string());
assert_eq!(ProcType::Bergamo.to_kds_url(), ProcType::Genoa.to_string());
}
}