use base64::{engine::general_purpose::STANDARD, Engine as _};
use rcgen::{CertificateParams, DnType, KeyPair};
use crate::cms::{decode_degenerate_first, decode_leaf_from_degenerate};
use crate::error::{Error, Result};
use crate::est::{CONTENT_TYPE_PKCS10, WELL_KNOWN_PREFIX};
pub struct Enrollment {
pub cert_der: Vec<u8>,
pub key_pem: String,
}
pub fn make_csr(common_name: &str) -> Result<(Vec<u8>, String)> {
let key = KeyPair::generate().map_err(|e| Error::Ca(format!("keygen: {e}")))?;
let mut params = CertificateParams::new(vec![common_name.to_string()])
.map_err(|e| Error::Ca(format!("csr params: {e}")))?;
params.distinguished_name.push(DnType::CommonName, common_name);
let csr = params.serialize_request(&key).map_err(|e| Error::Ca(format!("csr build: {e}")))?;
Ok((csr.der().to_vec(), key.serialize_pem()))
}
pub async fn simpleenroll(
http: &reqwest::Client,
base_url: &str,
user: &str,
pass: &str,
csr_der: &[u8],
) -> Result<Vec<u8>> {
let url = format!("{}{}/simpleenroll", base_url.trim_end_matches('/'), WELL_KNOWN_PREFIX);
let body = STANDARD.encode(csr_der);
let resp = http
.post(url)
.basic_auth(user, Some(pass))
.header(reqwest::header::CONTENT_TYPE, CONTENT_TYPE_PKCS10)
.header("content-transfer-encoding", "base64")
.body(body)
.send()
.await?;
unpack_pkcs7(resp, true).await
}
pub async fn simplereenroll(
http: &reqwest::Client,
base_url: &str,
csr_der: &[u8],
) -> Result<Vec<u8>> {
let url = format!("{}{}/simplereenroll", base_url.trim_end_matches('/'), WELL_KNOWN_PREFIX);
let body = STANDARD.encode(csr_der);
let resp = http
.post(url)
.header(reqwest::header::CONTENT_TYPE, CONTENT_TYPE_PKCS10)
.header("content-transfer-encoding", "base64")
.body(body)
.send()
.await?;
unpack_pkcs7(resp, true).await
}
pub async fn cacerts(http: &reqwest::Client, base_url: &str) -> Result<Vec<u8>> {
let url = format!("{}{}/cacerts", base_url.trim_end_matches('/'), WELL_KNOWN_PREFIX);
let resp = http.get(url).send().await?;
unpack_pkcs7(resp, false).await
}
async fn unpack_pkcs7(resp: reqwest::Response, require_leaf: bool) -> Result<Vec<u8>> {
let status = resp.status();
let body = resp.text().await?;
if !status.is_success() {
return Err(Error::Auth(format!("EST server returned {status}: {body}")));
}
let der = STANDARD
.decode(body.trim().as_bytes())
.map_err(|e| Error::Cms(format!("base64 decode EST response: {e}")))?;
if require_leaf {
decode_leaf_from_degenerate(&der)
} else {
decode_degenerate_first(&der)
}
}