use axum::extract::State;
use axum::http::{header, HeaderMap, StatusCode};
use axum::response::IntoResponse;
use axum::Extension;
use base64::{engine::general_purpose::STANDARD, Engine as _};
use crate::auth::basic::parse_basic;
use crate::auth::Principal;
use crate::cms::encode_degenerate;
use crate::est::{CONTENT_TYPE_PKCS10, CONTENT_TYPE_PKCS7_CERTS_ONLY};
use super::EstServer;
pub(super) async fn cacerts(State(s): State<EstServer>) -> impl IntoResponse {
match encode_degenerate(&[s.issuer.ca_cert_der().to_vec()]) {
Ok(der) => pkcs7_response(&der),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
pub(super) async fn simpleenroll(
State(s): State<EstServer>,
headers: HeaderMap,
body: axum::body::Bytes,
) -> axum::response::Response {
if let Err(resp) = require_pkcs10_content_type(&headers) {
return resp;
}
let principal = match authenticate_basic(&s, &headers) {
Ok(p) => p,
Err(resp) => return resp,
};
issue_and_respond(&s, &principal, &body).await
}
pub(super) async fn simplereenroll(
State(s): State<EstServer>,
principal: Option<Extension<Principal>>,
headers: HeaderMap,
body: axum::body::Bytes,
) -> axum::response::Response {
if let Err(resp) = require_pkcs10_content_type(&headers) {
return resp;
}
let Some(Extension(principal)) = principal else {
return (
StatusCode::UNAUTHORIZED,
"missing Principal extension — configure your TLS layer to insert one",
)
.into_response();
};
issue_and_respond(&s, &principal, &body).await
}
fn authenticate_basic(
s: &EstServer,
headers: &HeaderMap,
) -> Result<Principal, axum::response::Response> {
let header = headers
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.ok_or_else(|| {
(
StatusCode::UNAUTHORIZED,
[(header::WWW_AUTHENTICATE, "Basic realm=\"est\"")],
"missing Authorization header",
)
.into_response()
})?;
let (user, pass) = parse_basic(header).ok_or_else(|| {
(StatusCode::UNAUTHORIZED, "malformed Basic auth").into_response()
})?;
s.auth
.verify_bootstrap(&user, &pass)
.map_err(|reason| (StatusCode::FORBIDDEN, reason).into_response())
}
async fn issue_and_respond(
s: &EstServer,
principal: &Principal,
body: &[u8],
) -> axum::response::Response {
let csr_der = match STANDARD.decode(body) {
Ok(d) => d,
Err(_) => body.to_vec(),
};
match s.issuer.sign_csr(&csr_der, &principal.id) {
Ok(leaf_der) => match encode_degenerate(&[leaf_der]) {
Ok(wrapped) => pkcs7_response(&wrapped),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
},
Err(e) => (StatusCode::BAD_REQUEST, e.to_string()).into_response(),
}
}
fn require_pkcs10_content_type(
headers: &HeaderMap,
) -> std::result::Result<(), axum::response::Response> {
let got = headers
.get(header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
let base = got.split(';').next().unwrap_or("").trim();
if base.eq_ignore_ascii_case(CONTENT_TYPE_PKCS10) {
Ok(())
} else {
Err((
StatusCode::UNSUPPORTED_MEDIA_TYPE,
format!("expected Content-Type: {CONTENT_TYPE_PKCS10}"),
)
.into_response())
}
}
fn pkcs7_response(der: &[u8]) -> axum::response::Response {
let body = STANDARD.encode(der);
(
StatusCode::OK,
[
(header::CONTENT_TYPE, CONTENT_TYPE_PKCS7_CERTS_ONLY),
(header::HeaderName::from_static("content-transfer-encoding"), "base64"),
],
body,
)
.into_response()
}