1use std::any::Any;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use tokio_util::sync::CancellationToken;
9
10use crate::error::Result;
11
12#[derive(Debug, Clone)]
14pub struct IssuedCertificate {
15 pub certificate: Vec<u8>,
17 pub metadata: Option<serde_json::Value>,
20}
21
22#[derive(Clone, Serialize, Deserialize, Default)]
25pub struct CertificateResource {
26 #[serde(default)]
28 pub sans: Vec<String>,
29 #[serde(skip)]
35 pub certificate_pem: Vec<u8>,
36 #[serde(skip)]
40 pub private_key_pem: Vec<u8>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub issuer_data: Option<serde_json::Value>,
44}
45
46impl std::fmt::Debug for CertificateResource {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.debug_struct("CertificateResource")
49 .field("sans", &self.sans)
50 .field("certificate_bytes", &self.certificate_pem.len())
51 .finish_non_exhaustive()
52 }
53}
54
55impl CertificateResource {
56 #[must_use]
59 pub fn names_key(&self) -> String {
60 let mut names = self.sans.clone();
61 names.sort();
62 let joined = names.join(",");
63 if joined.len() > 1024 {
64 let mut end = 1024;
68 while !joined.is_char_boundary(end) {
69 end -= 1;
70 }
71 format!("{}_trunc", &joined[..end])
72 } else {
73 joined
74 }
75 }
76}
77
78#[async_trait]
80pub trait Issuer: Send + Sync + std::fmt::Debug {
81 async fn issue(
87 &self,
88 ct: &CancellationToken,
89 csr: &crate::issuer::Csr,
90 attempt: u32,
91 ) -> Result<IssuedCertificate>;
92
93 async fn issue_with_replaces(
97 &self,
98 ct: &CancellationToken,
99 csr: &crate::issuer::Csr,
100 attempt: u32,
101 _replaces: Option<&str>,
102 ) -> Result<IssuedCertificate> {
103 self.issue(ct, csr, attempt).await
104 }
105
106 fn issuer_key(&self) -> String;
108
109 fn as_any(&self) -> &dyn Any;
113
114 async fn pre_check(
116 &self,
117 _ct: &CancellationToken,
118 _names: &[String],
119 _interactive: bool,
120 ) -> Result<()> {
121 Ok(())
122 }
123
124 async fn revoke(
126 &self,
127 _ct: &CancellationToken,
128 _resource: &CertificateResource,
129 _reason: RevocationReason,
130 ) -> Result<()> {
131 Err(crate::error::Error::Issuer(
132 crate::error::IssuerError::Other("revocation not supported by this issuer".into()),
133 ))
134 }
135
136 async fn get_renewal_info(
138 &self,
139 _ct: &CancellationToken,
140 _cert: &crate::certificate::Certificate,
141 ) -> Result<crate::certificate::RenewalInfo> {
142 Err(crate::error::Error::Issuer(
143 crate::error::IssuerError::Other("ARI not supported by this issuer".into()),
144 ))
145 }
146}
147
148#[async_trait]
157pub trait PreChecker: Send + Sync {
158 async fn pre_check(
160 &self,
161 ct: &CancellationToken,
162 names: &[String],
163 interactive: bool,
164 ) -> Result<()>;
165}
166
167#[async_trait]
173pub trait Revoker: Send + Sync {
174 async fn revoke(
176 &self,
177 ct: &CancellationToken,
178 resource: &CertificateResource,
179 reason: RevocationReason,
180 ) -> Result<()>;
181}
182
183#[async_trait]
185impl<T: Issuer + ?Sized> PreChecker for T {
186 async fn pre_check(
187 &self,
188 ct: &CancellationToken,
189 names: &[String],
190 interactive: bool,
191 ) -> Result<()> {
192 Issuer::pre_check(self, ct, names, interactive).await
193 }
194}
195
196#[async_trait]
198impl<T: Issuer + ?Sized> Revoker for T {
199 async fn revoke(
200 &self,
201 ct: &CancellationToken,
202 resource: &CertificateResource,
203 reason: RevocationReason,
204 ) -> Result<()> {
205 Issuer::revoke(self, ct, resource, reason).await
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct Csr {
212 pub der: Vec<u8>,
214 pub dns_names: Vec<String>,
216 pub ip_addresses: Vec<std::net::IpAddr>,
218}
219
220impl Csr {
221 #[must_use]
223 pub fn primary_name(&self) -> &str {
224 self.dns_names.first().map(String::as_str).unwrap_or("")
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
230#[repr(u8)]
231pub enum RevocationReason {
232 #[default]
234 Unspecified = 0,
235 KeyCompromise = 1,
237 CaCompromise = 2,
239 AffiliationChanged = 3,
241 Superseded = 4,
243 CessationOfOperation = 5,
245 PrivilegeWithdrawn = 9,
247 AuthorityCompromise = 10,
249}
250
251#[must_use]
254pub fn as_acme_issuer(issuer: &Arc<dyn Issuer>) -> Option<&crate::acme::AcmeIssuer> {
255 issuer.as_any().downcast_ref::<crate::acme::AcmeIssuer>()
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn review_certificate_resource_debug_redacts_secrets() {
264 let resource = CertificateResource {
265 private_key_pem: vec![197, 198, 199],
266 issuer_data: Some(serde_json::json!({"secret": "issuer-secret"})),
267 ..Default::default()
268 };
269 let debug = format!("{resource:?}");
270 assert!(!debug.contains("197"));
271 assert!(!debug.contains("issuer-secret"));
272 }
273
274 #[test]
275 fn names_key_sorts_and_truncates() {
276 let r = CertificateResource {
277 sans: vec!["b.com".into(), "a.com".into()],
278 ..Default::default()
279 };
280 assert_eq!(r.names_key(), "a.com,b.com");
281
282 let long = CertificateResource {
283 sans: vec!["x".repeat(2000)],
284 ..Default::default()
285 };
286 assert_eq!(long.names_key().len(), 1024 + "_trunc".len());
287
288 let unicode = CertificateResource {
289 sans: vec!["例".repeat(600)],
290 ..Default::default()
291 };
292 assert!(unicode.names_key().ends_with("_trunc"));
293 }
294
295 #[test]
296 fn revocation_reason_codes() {
297 assert_eq!(RevocationReason::KeyCompromise as u8, 1);
298 assert_eq!(RevocationReason::Superseded as u8, 4);
299 }
300}