1use std::sync::Arc;
11
12use axum::extract::State;
13use axum::http::{HeaderValue, StatusCode, header};
14use axum::response::{IntoResponse, Response};
15
16use crate::auth::OptionalAuth;
17use crate::error::KipukaError;
18use crate::routes::LabelExtractor;
19use crate::routes::est::{content_types, encode_est_base64};
20use crate::state::AppState;
21
22pub async fn get_cacerts(
47 _auth: OptionalAuth,
48 label: LabelExtractor,
49 State(state): State<Arc<AppState>>,
50) -> Result<Response, KipukaError> {
51 let ca_id = label.ca_id();
52
53 tracing::debug!(
54 ca_id = %ca_id,
55 label = %label.label,
56 "serving CA certificates"
57 );
58
59 let ca = state.get_ca(ca_id).ok_or_else(|| {
61 tracing::error!(ca_id = %ca_id, "CA not found for cacerts request");
62 KipukaError::NotFound
63 })?;
64
65 let chain: &[Vec<u8>] = if !ca.cert_chain.is_empty() {
66 &ca.cert_chain
67 } else {
68 tracing::warn!(ca_id = %ca_id, "cert_chain empty; serving CA cert only");
69 std::slice::from_ref(&ca.cert_der)
70 };
71 let pkcs7_der = build_certs_only_pkcs7(chain)?;
72
73 let body = encode_est_base64(&pkcs7_der);
75
76 let mut resp = (StatusCode::OK, body).into_response();
77 resp.headers_mut().insert(
78 header::CONTENT_TYPE,
79 HeaderValue::from_static(content_types::PKCS7_CERTS),
80 );
81 resp.headers_mut().insert(
82 header::HeaderName::from_static("content-transfer-encoding"),
83 HeaderValue::from_static(content_types::TRANSFER_ENCODING_BASE64),
84 );
85
86 state
88 .record_audit_event("cacerts", &format!("ca_id={ca_id}"))
89 .await;
90
91 Ok(resp)
92}
93
94pub(crate) fn build_certs_only_pkcs7(
113 certs_der: &[impl AsRef<[u8]>],
114) -> Result<Vec<u8>, KipukaError> {
115 use synta::{Encoder, Encoding, ObjectIdentifier, Tag, tag};
116
117 if certs_der.is_empty() {
118 return Err(KipukaError::Ca(
119 "No certificates provided for PKCS#7".into(),
120 ));
121 }
122
123 let oid_signed_data = ObjectIdentifier::new(synta_certificate::oids::CMS_SIGNED_DATA)
125 .map_err(|e| KipukaError::Ca(format!("id-signedData OID: {e}")))?;
126 let oid_data = ObjectIdentifier::new(synta_certificate::oids::CMS_DATA)
127 .map_err(|e| KipukaError::Ca(format!("id-data OID: {e}")))?;
128
129 let seq_tag = Tag::universal_constructed(tag::TAG_SEQUENCE);
130 let set_tag = Tag::universal_constructed(tag::TAG_SET);
131 let ctx0_tag = Tag::context_specific_constructed(0);
132
133 let mut enc = Encoder::new(Encoding::Der);
134
135 enc.start_constructed_no_guard(seq_tag)
137 .map_err(|e| KipukaError::Ca(format!("ContentInfo SEQUENCE: {e}")))?;
138
139 enc.encode(&oid_signed_data)
141 .map_err(|e| KipukaError::Ca(format!("contentType OID: {e}")))?;
142
143 enc.start_constructed_no_guard(ctx0_tag)
145 .map_err(|e| KipukaError::Ca(format!("[0] EXPLICIT: {e}")))?;
146
147 enc.start_constructed_no_guard(seq_tag)
149 .map_err(|e| KipukaError::Ca(format!("SignedData SEQUENCE: {e}")))?;
150
151 let version = synta::Integer::from_i64(1);
153 enc.encode(&version)
154 .map_err(|e| KipukaError::Ca(format!("version INTEGER: {e}")))?;
155
156 enc.start_constructed_no_guard(set_tag)
158 .map_err(|e| KipukaError::Ca(format!("digestAlgorithms SET: {e}")))?;
159 enc.end_constructed()
160 .map_err(|e| KipukaError::Ca(format!("digestAlgorithms end: {e}")))?;
161
162 enc.start_constructed_no_guard(seq_tag)
164 .map_err(|e| KipukaError::Ca(format!("encapContentInfo SEQUENCE: {e}")))?;
165 enc.encode(&oid_data)
166 .map_err(|e| KipukaError::Ca(format!("id-data OID: {e}")))?;
167 enc.end_constructed()
168 .map_err(|e| KipukaError::Ca(format!("encapContentInfo end: {e}")))?;
169
170 enc.start_constructed_no_guard(ctx0_tag)
172 .map_err(|e| KipukaError::Ca(format!("certificates [0]: {e}")))?;
173 for cert in certs_der {
174 enc.write_bytes(cert.as_ref());
175 }
176 enc.end_constructed()
177 .map_err(|e| KipukaError::Ca(format!("certificates end: {e}")))?;
178
179 enc.start_constructed_no_guard(set_tag)
181 .map_err(|e| KipukaError::Ca(format!("signerInfos SET: {e}")))?;
182 enc.end_constructed()
183 .map_err(|e| KipukaError::Ca(format!("signerInfos end: {e}")))?;
184
185 enc.end_constructed()
187 .map_err(|e| KipukaError::Ca(format!("SignedData end: {e}")))?;
188
189 enc.end_constructed()
191 .map_err(|e| KipukaError::Ca(format!("[0] end: {e}")))?;
192
193 enc.end_constructed()
195 .map_err(|e| KipukaError::Ca(format!("ContentInfo end: {e}")))?;
196
197 let pkcs7_der = enc
198 .finish()
199 .map_err(|e| KipukaError::Ca(format!("PKCS#7 DER finish: {e}")))?;
200
201 tracing::debug!(
202 pkcs7_len = pkcs7_der.len(),
203 cert_count = certs_der.len(),
204 "built PKCS#7 certs-only SignedData"
205 );
206
207 Ok(pkcs7_der)
208}