1use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use base64::Engine;
8use chrono::{Duration, Utc};
9use http::StatusCode;
10use parking_lot::RwLock;
11use serde_json::{json, Value};
12use sha2::{Digest, Sha256};
13use uuid::Uuid;
14
15use fakecloud_aws::arn::Arn;
16use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
17
18use crate::state::{
19 AccountState, AcmAccounts, CertificateOptions, DomainValidation, SharedAcmState,
20 StoredCertificate,
21};
22
23const SUPPORTED_ACTIONS: &[&str] = &[
24 "RequestCertificate",
25 "DescribeCertificate",
26 "ListCertificates",
27 "DeleteCertificate",
28 "ImportCertificate",
29 "ExportCertificate",
30 "GetCertificate",
31 "RenewCertificate",
32 "RevokeCertificate",
33 "ResendValidationEmail",
34 "AddTagsToCertificate",
35 "RemoveTagsFromCertificate",
36 "ListTagsForCertificate",
37 "GetAccountConfiguration",
38 "PutAccountConfiguration",
39 "UpdateCertificateOptions",
40 "SearchCertificates",
41];
42
43pub struct AcmService {
44 state: SharedAcmState,
45}
46
47impl AcmService {
48 pub fn new(state: SharedAcmState) -> Self {
49 Self { state }
50 }
51
52 pub fn shared_state(&self) -> SharedAcmState {
53 Arc::clone(&self.state)
54 }
55}
56
57impl Default for AcmService {
58 fn default() -> Self {
59 Self::new(Arc::new(RwLock::new(AcmAccounts::new())))
60 }
61}
62
63#[async_trait]
64impl AwsService for AcmService {
65 fn service_name(&self) -> &str {
66 "acm"
67 }
68
69 fn supported_actions(&self) -> &[&str] {
70 SUPPORTED_ACTIONS
71 }
72
73 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
74 match req.action.as_str() {
75 "RequestCertificate" => self.request_certificate(&req),
76 "DescribeCertificate" => self.describe_certificate(&req),
77 "ListCertificates" => self.list_certificates(&req),
78 "DeleteCertificate" => self.delete_certificate(&req),
79 "ImportCertificate" => self.import_certificate(&req),
80 "ExportCertificate" => self.export_certificate(&req),
81 "GetCertificate" => self.get_certificate(&req),
82 "RenewCertificate" => self.renew_certificate(&req),
83 "RevokeCertificate" => self.revoke_certificate(&req),
84 "ResendValidationEmail" => self.resend_validation_email(&req),
85 "AddTagsToCertificate" => self.add_tags_to_certificate(&req),
86 "RemoveTagsFromCertificate" => self.remove_tags_from_certificate(&req),
87 "ListTagsForCertificate" => self.list_tags_for_certificate(&req),
88 "GetAccountConfiguration" => self.get_account_configuration(&req),
89 "PutAccountConfiguration" => self.put_account_configuration(&req),
90 "UpdateCertificateOptions" => self.update_certificate_options(&req),
91 "SearchCertificates" => self.search_certificates(&req),
92 other => Err(AwsServiceError::action_not_implemented("acm", other)),
93 }
94 }
95}
96
97impl AcmService {
100 fn request_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
101 let body = req.json_body();
102 let domain_name = body
103 .get("DomainName")
104 .and_then(Value::as_str)
105 .ok_or_else(|| invalid_param("DomainName is required"))?
106 .to_string();
107 let validation_method = body
108 .get("ValidationMethod")
109 .and_then(Value::as_str)
110 .unwrap_or("DNS")
111 .to_string();
112 let sans: Vec<String> = body
113 .get("SubjectAlternativeNames")
114 .and_then(Value::as_array)
115 .map(|v| {
116 v.iter()
117 .filter_map(|s| s.as_str().map(|s| s.to_string()))
118 .collect()
119 })
120 .unwrap_or_default();
121 let key_algorithm = body
122 .get("KeyAlgorithm")
123 .and_then(Value::as_str)
124 .unwrap_or("RSA_2048")
125 .to_string();
126 let idempotency_token = body
127 .get("IdempotencyToken")
128 .and_then(Value::as_str)
129 .map(|s| s.to_string());
130 let managed_by = body
131 .get("ManagedBy")
132 .and_then(Value::as_str)
133 .map(|s| s.to_string());
134 let ca_arn = body
135 .get("CertificateAuthorityArn")
136 .and_then(Value::as_str)
137 .map(|s| s.to_string());
138 let tags = parse_tags(body.get("Tags"))?;
139 let options = parse_options(body.get("Options"));
140
141 let mut state = self.state.write();
142 let account = account_mut(&mut state, &req.account_id);
143
144 if let Some(token) = &idempotency_token {
148 if let Some(existing) = account.certificates.values().find(|c| {
149 c.idempotency_token.as_deref() == Some(token)
150 && c.domain_name == domain_name
151 && c.subject_alternative_names == effective_sans(&domain_name, &sans)
152 }) {
153 return Ok(AwsResponse::ok_json(
154 json!({ "CertificateArn": existing.arn }),
155 ));
156 }
157 }
158
159 let arn = synth_certificate_arn(&req.account_id, &req.region);
160 let now = Utc::now();
161 let cert = StoredCertificate {
162 arn: arn.clone(),
163 domain_name: domain_name.clone(),
164 subject_alternative_names: effective_sans(&domain_name, &sans),
165 status: "PENDING_VALIDATION".to_string(),
166 cert_type: "AMAZON_ISSUED".to_string(),
167 certificate_pem: None,
168 certificate_chain_pem: None,
169 private_key_pem: None,
170 idempotency_token,
171 serial: synth_serial(&arn),
172 subject: format!("CN={domain_name}"),
173 issuer: "Amazon".to_string(),
174 key_algorithm,
175 signature_algorithm: "SHA256WITHRSA".to_string(),
176 created_at: now,
177 issued_at: None,
178 imported_at: None,
179 revoked_at: None,
180 revocation_reason: None,
181 not_before: now,
183 not_after: now + Duration::days(395),
184 validation_method: Some(validation_method.clone()),
185 domain_validation: synth_domain_validation(&domain_name, &sans, &validation_method),
186 options,
187 renewal_eligibility: "INELIGIBLE".to_string(),
188 managed_by,
189 certificate_authority_arn: ca_arn,
190 tags,
191 in_use_by: Vec::new(),
192 };
193 account.certificates.insert(arn.clone(), cert);
194 Ok(AwsResponse::ok_json(json!({ "CertificateArn": arn })))
195 }
196
197 fn describe_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
198 let arn = require_certificate_arn(req)?;
199 let state = self.state.read();
200 let cert = state
201 .accounts
202 .get(&req.account_id)
203 .and_then(|a| a.certificates.get(&arn))
204 .ok_or_else(|| no_such_certificate(&arn))?
205 .clone();
206 Ok(AwsResponse::ok_json(json!({
207 "Certificate": certificate_detail_json(&cert),
208 })))
209 }
210
211 fn list_certificates(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
212 let body = req.json_body();
213 let max_items: usize = body
214 .get("MaxItems")
215 .and_then(Value::as_u64)
216 .map(|n| n as usize)
217 .unwrap_or(100);
218 let next_token = body
219 .get("NextToken")
220 .and_then(Value::as_str)
221 .map(|s| s.to_string());
222 let statuses: Vec<String> = body
223 .get("CertificateStatuses")
224 .and_then(Value::as_array)
225 .map(|v| {
226 v.iter()
227 .filter_map(|s| s.as_str().map(|s| s.to_string()))
228 .collect()
229 })
230 .unwrap_or_default();
231 let includes = body.get("Includes");
232 let key_types: Vec<String> = includes
233 .and_then(|i| i.get("keyTypes"))
234 .and_then(Value::as_array)
235 .map(|v| {
236 v.iter()
237 .filter_map(|s| s.as_str().map(|s| s.to_string()))
238 .collect()
239 })
240 .unwrap_or_default();
241
242 let state = self.state.read();
243 let mut all: Vec<StoredCertificate> = state
244 .accounts
245 .get(&req.account_id)
246 .map(|a| a.certificates.values().cloned().collect())
247 .unwrap_or_default();
248 drop(state);
249 all.sort_by(|a, b| a.arn.cmp(&b.arn));
250 all.retain(|c| {
251 (statuses.is_empty() || statuses.contains(&c.status))
252 && (key_types.is_empty() || key_types.contains(&c.key_algorithm))
253 });
254
255 let start = next_token
256 .and_then(|t| t.parse::<usize>().ok())
257 .unwrap_or(0);
258 let end = (start + max_items).min(all.len());
259 let page: Vec<&StoredCertificate> = all.iter().skip(start).take(max_items).collect();
260 let next = if end < all.len() {
261 Some(end.to_string())
262 } else {
263 None
264 };
265 let mut response = json!({
266 "CertificateSummaryList": page
267 .iter()
268 .map(|c| certificate_summary_json(c))
269 .collect::<Vec<_>>(),
270 });
271 if let Some(t) = next {
272 response
273 .as_object_mut()
274 .unwrap()
275 .insert("NextToken".to_string(), Value::String(t));
276 }
277 Ok(AwsResponse::ok_json(response))
278 }
279
280 fn delete_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
281 let arn = require_certificate_arn(req)?;
282 let mut state = self.state.write();
283 let account = account_mut(&mut state, &req.account_id);
284 let cert = account
285 .certificates
286 .get(&arn)
287 .ok_or_else(|| no_such_certificate(&arn))?;
288 if !cert.in_use_by.is_empty() {
289 return Err(AwsServiceError::aws_error(
290 StatusCode::BAD_REQUEST,
291 "ResourceInUseException",
292 format!(
293 "Certificate {arn} is in use by {} resource(s)",
294 cert.in_use_by.len()
295 ),
296 ));
297 }
298 account.certificates.remove(&arn);
299 Ok(AwsResponse::ok_json(json!({})))
300 }
301
302 fn import_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
303 let body = req.json_body();
304 let cert_pem = decode_blob(body.get("Certificate"))
305 .ok_or_else(|| invalid_param("Certificate is required"))?;
306 let key_pem = decode_blob(body.get("PrivateKey"))
307 .ok_or_else(|| invalid_param("PrivateKey is required"))?;
308 let chain_pem = decode_blob(body.get("CertificateChain"));
309 let arn_in = body
310 .get("CertificateArn")
311 .and_then(Value::as_str)
312 .map(|s| s.to_string());
313 let tags = parse_tags(body.get("Tags"))?;
314
315 let domain_name = parse_cn_from_pem(&cert_pem).unwrap_or_else(|| "imported".to_string());
316 let now = Utc::now();
317 let mut state = self.state.write();
318 let account = account_mut(&mut state, &req.account_id);
319
320 let arn = match arn_in {
321 Some(existing) => {
322 let cert = account
323 .certificates
324 .get_mut(&existing)
325 .ok_or_else(|| no_such_certificate(&existing))?;
326 if cert.cert_type != "IMPORTED" {
327 return Err(invalid_param(
328 "Reimport is only supported for IMPORTED certificates",
329 ));
330 }
331 cert.certificate_pem = Some(cert_pem.clone());
332 cert.private_key_pem = Some(key_pem);
333 cert.certificate_chain_pem = chain_pem;
334 cert.imported_at = Some(now);
335 cert.not_before = now;
336 cert.not_after = now + Duration::days(395);
337 cert.subject = format!("CN={domain_name}");
338 cert.domain_name = domain_name.clone();
342 cert.subject_alternative_names = vec![domain_name.clone()];
343 if !tags.is_empty() {
344 for (k, v) in tags {
345 cert.tags.insert(k, v);
346 }
347 }
348 existing
349 }
350 None => {
351 let arn = synth_certificate_arn(&req.account_id, &req.region);
352 let cert = StoredCertificate {
353 arn: arn.clone(),
354 domain_name: domain_name.clone(),
355 subject_alternative_names: vec![domain_name.clone()],
356 status: "ISSUED".to_string(),
357 cert_type: "IMPORTED".to_string(),
358 certificate_pem: Some(cert_pem),
359 certificate_chain_pem: chain_pem,
360 private_key_pem: Some(key_pem),
361 idempotency_token: None,
362 serial: synth_serial(&arn),
363 subject: format!("CN={domain_name}"),
364 issuer: "fakecloud-imported".to_string(),
365 key_algorithm: "RSA_2048".to_string(),
366 signature_algorithm: "SHA256WITHRSA".to_string(),
367 created_at: now,
368 issued_at: Some(now),
369 imported_at: Some(now),
370 revoked_at: None,
371 revocation_reason: None,
372 not_before: now,
373 not_after: now + Duration::days(395),
374 validation_method: None,
375 domain_validation: Vec::new(),
376 options: CertificateOptions {
377 certificate_transparency_logging_preference: "ENABLED".to_string(),
378 export: "DISABLED".to_string(),
379 },
380 renewal_eligibility: "INELIGIBLE".to_string(),
381 managed_by: None,
382 certificate_authority_arn: None,
383 tags,
384 in_use_by: Vec::new(),
385 };
386 account.certificates.insert(arn.clone(), cert);
387 arn
388 }
389 };
390 Ok(AwsResponse::ok_json(json!({ "CertificateArn": arn })))
391 }
392
393 fn export_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
394 let body = req.json_body();
395 let arn = body
396 .get("CertificateArn")
397 .and_then(Value::as_str)
398 .ok_or_else(|| invalid_param("CertificateArn is required"))?
399 .to_string();
400 let passphrase_b64 = body
401 .get("Passphrase")
402 .and_then(Value::as_str)
403 .ok_or_else(|| invalid_param("Passphrase is required"))?;
404 if passphrase_b64.is_empty() {
405 return Err(invalid_param("Passphrase must not be empty"));
406 }
407 let state = self.state.read();
408 let cert = state
409 .accounts
410 .get(&req.account_id)
411 .and_then(|a| a.certificates.get(&arn))
412 .ok_or_else(|| no_such_certificate(&arn))?
413 .clone();
414 if cert.options.export != "ENABLED" && cert.cert_type != "IMPORTED" {
415 return Err(AwsServiceError::aws_error(
416 StatusCode::BAD_REQUEST,
417 "RequestInProgressException",
418 "Certificate is not exportable",
419 ));
420 }
421 let cert_pem = cert
422 .certificate_pem
423 .clone()
424 .unwrap_or_else(|| placeholder_cert_pem(&arn));
425 let chain_pem = cert
426 .certificate_chain_pem
427 .clone()
428 .unwrap_or_else(|| placeholder_chain_pem(&arn));
429 let key_pem = cert
430 .private_key_pem
431 .clone()
432 .unwrap_or_else(|| placeholder_key_pem(&arn));
433 Ok(AwsResponse::ok_json(json!({
434 "Certificate": cert_pem,
435 "CertificateChain": chain_pem,
436 "PrivateKey": key_pem,
437 })))
438 }
439
440 fn get_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
441 let arn = require_certificate_arn(req)?;
442 let state = self.state.read();
443 let cert = state
444 .accounts
445 .get(&req.account_id)
446 .and_then(|a| a.certificates.get(&arn))
447 .ok_or_else(|| no_such_certificate(&arn))?
448 .clone();
449 let cert_pem = cert
450 .certificate_pem
451 .clone()
452 .unwrap_or_else(|| placeholder_cert_pem(&arn));
453 let chain_pem = cert
454 .certificate_chain_pem
455 .clone()
456 .unwrap_or_else(|| placeholder_chain_pem(&arn));
457 Ok(AwsResponse::ok_json(json!({
458 "Certificate": cert_pem,
459 "CertificateChain": chain_pem,
460 })))
461 }
462
463 fn renew_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
464 let arn = require_certificate_arn(req)?;
465 let mut state = self.state.write();
466 let account = account_mut(&mut state, &req.account_id);
467 let cert = account
468 .certificates
469 .get_mut(&arn)
470 .ok_or_else(|| no_such_certificate(&arn))?;
471 if cert.cert_type == "IMPORTED" {
472 return Err(invalid_param(
473 "Imported certificates cannot be renewed via ACM",
474 ));
475 }
476 let now = Utc::now();
477 cert.not_before = now;
478 cert.not_after = now + Duration::days(395);
479 cert.issued_at = Some(now);
480 cert.status = "ISSUED".to_string();
481 Ok(AwsResponse::ok_json(json!({})))
482 }
483
484 fn revoke_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
485 let body = req.json_body();
486 let arn = body
487 .get("CertificateArn")
488 .and_then(Value::as_str)
489 .ok_or_else(|| invalid_param("CertificateArn is required"))?
490 .to_string();
491 let reason = body
492 .get("RevocationReason")
493 .and_then(Value::as_str)
494 .ok_or_else(|| invalid_param("RevocationReason is required"))?
495 .to_string();
496 let mut state = self.state.write();
497 let account = account_mut(&mut state, &req.account_id);
498 let cert = account
499 .certificates
500 .get_mut(&arn)
501 .ok_or_else(|| no_such_certificate(&arn))?;
502 if cert.cert_type != "AMAZON_ISSUED" {
503 return Err(invalid_param(
504 "Only AMAZON_ISSUED certificates can be revoked",
505 ));
506 }
507 cert.status = "REVOKED".to_string();
508 cert.revoked_at = Some(Utc::now());
509 cert.revocation_reason = Some(reason);
510 Ok(AwsResponse::ok_json(json!({})))
511 }
512
513 fn resend_validation_email(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
514 let body = req.json_body();
515 let arn = body
516 .get("CertificateArn")
517 .and_then(Value::as_str)
518 .ok_or_else(|| invalid_param("CertificateArn is required"))?
519 .to_string();
520 let _ = body
521 .get("Domain")
522 .and_then(Value::as_str)
523 .ok_or_else(|| invalid_param("Domain is required"))?;
524 let _ = body
525 .get("ValidationDomain")
526 .and_then(Value::as_str)
527 .ok_or_else(|| invalid_param("ValidationDomain is required"))?;
528 let state = self.state.read();
529 let cert = state
530 .accounts
531 .get(&req.account_id)
532 .and_then(|a| a.certificates.get(&arn))
533 .ok_or_else(|| no_such_certificate(&arn))?;
534 if cert.validation_method.as_deref() != Some("EMAIL") {
535 return Err(invalid_param(
536 "Certificate is not configured for EMAIL validation",
537 ));
538 }
539 Ok(AwsResponse::ok_json(json!({})))
540 }
541
542 fn add_tags_to_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
543 let body = req.json_body();
544 let arn = body
545 .get("CertificateArn")
546 .and_then(Value::as_str)
547 .ok_or_else(|| invalid_param("CertificateArn is required"))?
548 .to_string();
549 let tags = parse_tags(body.get("Tags"))?;
550 if tags.is_empty() {
551 return Err(invalid_param("Tags must contain at least one entry"));
552 }
553 let mut state = self.state.write();
554 let account = account_mut(&mut state, &req.account_id);
555 let cert = account
556 .certificates
557 .get_mut(&arn)
558 .ok_or_else(|| no_such_certificate(&arn))?;
559 for (k, v) in tags {
560 cert.tags.insert(k, v);
561 }
562 Ok(AwsResponse::ok_json(json!({})))
563 }
564
565 fn remove_tags_from_certificate(
566 &self,
567 req: &AwsRequest,
568 ) -> Result<AwsResponse, AwsServiceError> {
569 let body = req.json_body();
570 let arn = body
571 .get("CertificateArn")
572 .and_then(Value::as_str)
573 .ok_or_else(|| invalid_param("CertificateArn is required"))?
574 .to_string();
575 let tags = parse_tags(body.get("Tags"))?;
576 if tags.is_empty() {
577 return Err(invalid_param("Tags must contain at least one entry"));
578 }
579 let mut state = self.state.write();
580 let account = account_mut(&mut state, &req.account_id);
581 let cert = account
582 .certificates
583 .get_mut(&arn)
584 .ok_or_else(|| no_such_certificate(&arn))?;
585 for (k, v) in tags {
588 if let Some(existing) = cert.tags.get(&k) {
589 if v.is_empty() || *existing == v {
590 cert.tags.remove(&k);
591 }
592 }
593 }
594 Ok(AwsResponse::ok_json(json!({})))
595 }
596
597 fn list_tags_for_certificate(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
598 let arn = require_certificate_arn(req)?;
599 let state = self.state.read();
600 let cert = state
601 .accounts
602 .get(&req.account_id)
603 .and_then(|a| a.certificates.get(&arn))
604 .ok_or_else(|| no_such_certificate(&arn))?;
605 let mut tags: Vec<(String, String)> = cert
606 .tags
607 .iter()
608 .map(|(k, v)| (k.clone(), v.clone()))
609 .collect();
610 tags.sort_by(|a, b| a.0.cmp(&b.0));
611 let tag_list: Vec<Value> = tags
612 .into_iter()
613 .map(|(k, v)| json!({ "Key": k, "Value": v }))
614 .collect();
615 Ok(AwsResponse::ok_json(json!({ "Tags": tag_list })))
616 }
617
618 fn get_account_configuration(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
619 let state = self.state.read();
620 let cfg = state
621 .accounts
622 .get(&req.account_id)
623 .map(|a| a.account_config.clone())
624 .unwrap_or_default();
625 let mut expiry = json!({});
626 if let Some(d) = cfg.expiry_events_days_before_expiry {
627 expiry
628 .as_object_mut()
629 .unwrap()
630 .insert("DaysBeforeExpiry".to_string(), json!(d));
631 }
632 Ok(AwsResponse::ok_json(json!({
633 "ExpiryEvents": expiry,
634 })))
635 }
636
637 fn put_account_configuration(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
638 let body = req.json_body();
639 let _ = body
640 .get("IdempotencyToken")
641 .and_then(Value::as_str)
642 .ok_or_else(|| invalid_param("IdempotencyToken is required"))?;
643 let days = body
644 .get("ExpiryEvents")
645 .and_then(|v| v.get("DaysBeforeExpiry"))
646 .and_then(Value::as_i64)
647 .map(|n| n as i32);
648 let mut state = self.state.write();
649 let account = account_mut(&mut state, &req.account_id);
650 account.account_config.expiry_events_days_before_expiry = days;
651 Ok(AwsResponse::ok_json(json!({})))
652 }
653
654 fn update_certificate_options(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
655 let body = req.json_body();
656 let arn = body
657 .get("CertificateArn")
658 .and_then(Value::as_str)
659 .ok_or_else(|| invalid_param("CertificateArn is required"))?
660 .to_string();
661 let options = body
662 .get("Options")
663 .ok_or_else(|| invalid_param("Options is required"))?;
664 let new_opts = CertificateOptions {
665 certificate_transparency_logging_preference: options
666 .get("CertificateTransparencyLoggingPreference")
667 .and_then(Value::as_str)
668 .unwrap_or("ENABLED")
669 .to_string(),
670 export: options
671 .get("Export")
672 .and_then(Value::as_str)
673 .unwrap_or("DISABLED")
674 .to_string(),
675 };
676 let mut state = self.state.write();
677 let account = account_mut(&mut state, &req.account_id);
678 let cert = account
679 .certificates
680 .get_mut(&arn)
681 .ok_or_else(|| no_such_certificate(&arn))?;
682 cert.options = new_opts;
683 Ok(AwsResponse::ok_json(json!({})))
684 }
685
686 fn search_certificates(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
687 let body = req.json_body();
694 let max_results: usize = body
695 .get("MaxResults")
696 .and_then(Value::as_u64)
697 .map(|n| n as usize)
698 .unwrap_or(100);
699 let next_token = body
700 .get("NextToken")
701 .and_then(Value::as_str)
702 .map(|s| s.to_string());
703 let key_types: Vec<String> = body
704 .get("FilterStatement")
705 .and_then(|f| f.get("Filter"))
706 .and_then(|f| f.get("KeyTypes"))
707 .and_then(Value::as_array)
708 .map(|v| {
709 v.iter()
710 .filter_map(|s| s.as_str().map(|s| s.to_string()))
711 .collect()
712 })
713 .unwrap_or_default();
714
715 let state = self.state.read();
716 let mut all: Vec<StoredCertificate> = state
717 .accounts
718 .get(&req.account_id)
719 .map(|a| a.certificates.values().cloned().collect())
720 .unwrap_or_default();
721 drop(state);
722 all.sort_by(|a, b| a.arn.cmp(&b.arn));
723 if !key_types.is_empty() {
724 all.retain(|c| key_types.contains(&c.key_algorithm));
725 }
726 let start = next_token
727 .and_then(|t| t.parse::<usize>().ok())
728 .unwrap_or(0);
729 let end = (start + max_results).min(all.len());
730 let page: Vec<&StoredCertificate> = all.iter().skip(start).take(max_results).collect();
731 let next = if end < all.len() {
732 Some(end.to_string())
733 } else {
734 None
735 };
736 let mut response = json!({
737 "Results": page
738 .iter()
739 .map(|c| certificate_search_result_json(c))
740 .collect::<Vec<_>>(),
741 });
742 if let Some(t) = next {
743 response
744 .as_object_mut()
745 .unwrap()
746 .insert("NextToken".to_string(), Value::String(t));
747 }
748 Ok(AwsResponse::ok_json(response))
749 }
750}
751
752fn account_mut<'a>(state: &'a mut AcmAccounts, account_id: &str) -> &'a mut AccountState {
755 state.accounts.entry(account_id.to_string()).or_default()
756}
757
758fn require_certificate_arn(req: &AwsRequest) -> Result<String, AwsServiceError> {
759 req.json_body()
760 .get("CertificateArn")
761 .and_then(Value::as_str)
762 .map(|s| s.to_string())
763 .ok_or_else(|| invalid_param("CertificateArn is required"))
764}
765
766fn invalid_param(msg: impl Into<String>) -> AwsServiceError {
767 AwsServiceError::aws_error(StatusCode::BAD_REQUEST, "InvalidParameterException", msg)
768}
769
770fn no_such_certificate(arn: &str) -> AwsServiceError {
771 AwsServiceError::aws_error(
772 StatusCode::BAD_REQUEST,
773 "ResourceNotFoundException",
774 format!("Could not find certificate with arn {arn}"),
775 )
776}
777
778fn synth_certificate_arn(account_id: &str, region: &str) -> String {
779 let region = if region.is_empty() {
780 "us-east-1"
781 } else {
782 region
783 };
784 let id = Uuid::new_v4();
785 Arn::new("acm", region, account_id, &format!("certificate/{id}")).to_string()
786}
787
788fn synth_serial(arn: &str) -> String {
789 let mut hasher = Sha256::new();
790 hasher.update(arn.as_bytes());
791 let digest = hasher.finalize();
792 hex::encode(&digest[..16])
793}
794
795fn parse_tags(value: Option<&Value>) -> Result<BTreeMap<String, String>, AwsServiceError> {
796 let mut out = BTreeMap::new();
797 let Some(arr) = value.and_then(Value::as_array) else {
798 return Ok(out);
799 };
800 for tag in arr {
801 let key = tag
802 .get("Key")
803 .and_then(Value::as_str)
804 .ok_or_else(|| invalid_param("Tag.Key is required"))?
805 .to_string();
806 let value = tag
807 .get("Value")
808 .and_then(Value::as_str)
809 .unwrap_or_default()
810 .to_string();
811 out.insert(key, value);
812 }
813 Ok(out)
814}
815
816fn parse_options(value: Option<&Value>) -> CertificateOptions {
817 let v = match value {
818 Some(v) => v,
819 None => {
820 return CertificateOptions {
821 certificate_transparency_logging_preference: "ENABLED".to_string(),
822 export: "DISABLED".to_string(),
823 };
824 }
825 };
826 CertificateOptions {
827 certificate_transparency_logging_preference: v
828 .get("CertificateTransparencyLoggingPreference")
829 .and_then(Value::as_str)
830 .unwrap_or("ENABLED")
831 .to_string(),
832 export: v
833 .get("Export")
834 .and_then(Value::as_str)
835 .unwrap_or("DISABLED")
836 .to_string(),
837 }
838}
839
840fn effective_sans(domain: &str, extras: &[String]) -> Vec<String> {
844 let mut all = vec![domain.to_string()];
845 for s in extras {
846 if !all.contains(s) {
847 all.push(s.clone());
848 }
849 }
850 all
851}
852
853fn synth_domain_validation(domain: &str, sans: &[String], method: &str) -> Vec<DomainValidation> {
854 effective_sans(domain, sans)
855 .iter()
856 .map(|d| {
857 if method == "DNS" {
858 let token = synth_dns_token(d);
859 DomainValidation {
860 domain_name: d.clone(),
861 validation_status: "PENDING_VALIDATION".to_string(),
862 validation_method: "DNS".to_string(),
863 resource_record_name: Some(format!("_{token}.{d}.")),
864 resource_record_type: Some("CNAME".to_string()),
865 resource_record_value: Some(format!("_{token}.acm-validations.aws.")),
866 }
867 } else {
868 DomainValidation {
869 domain_name: d.clone(),
870 validation_status: "PENDING_VALIDATION".to_string(),
871 validation_method: "EMAIL".to_string(),
872 resource_record_name: None,
873 resource_record_type: None,
874 resource_record_value: None,
875 }
876 }
877 })
878 .collect()
879}
880
881fn synth_dns_token(domain: &str) -> String {
884 let mut hasher = Sha256::new();
885 hasher.update(domain.as_bytes());
886 let digest = hasher.finalize();
887 hex::encode(&digest[..16])
888}
889
890fn decode_blob(value: Option<&Value>) -> Option<String> {
891 let v = value?;
892 if let Some(s) = v.as_str() {
893 if let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(s) {
897 if let Ok(text) = String::from_utf8(bytes) {
898 return Some(text);
899 }
900 }
901 return Some(s.to_string());
902 }
903 None
904}
905
906fn parse_cn_from_pem(pem: &str) -> Option<String> {
911 pem.lines()
912 .find_map(|line| line.split("CN=").nth(1))
913 .map(|rest| {
914 rest.split(['/', ',', '\n', ' '])
915 .next()
916 .unwrap_or("")
917 .to_string()
918 })
919 .filter(|s| !s.is_empty())
920}
921
922fn placeholder_cert_pem(arn: &str) -> String {
923 let body = base64::engine::general_purpose::STANDARD.encode(arn.as_bytes());
924 format!("-----BEGIN CERTIFICATE-----\n{body}\n-----END CERTIFICATE-----\n")
925}
926
927fn placeholder_chain_pem(arn: &str) -> String {
928 let body =
929 base64::engine::general_purpose::STANDARD.encode(format!("chain-of-{arn}").as_bytes());
930 format!("-----BEGIN CERTIFICATE-----\n{body}\n-----END CERTIFICATE-----\n")
931}
932
933fn placeholder_key_pem(arn: &str) -> String {
934 let body = base64::engine::general_purpose::STANDARD.encode(format!("key-of-{arn}").as_bytes());
935 format!("-----BEGIN RSA PRIVATE KEY-----\n{body}\n-----END RSA PRIVATE KEY-----\n")
936}
937
938fn certificate_summary_json(c: &StoredCertificate) -> Value {
939 let mut s = json!({
940 "CertificateArn": c.arn,
941 "DomainName": c.domain_name,
942 "SubjectAlternativeNameSummaries": c.subject_alternative_names,
943 "HasAdditionalSubjectAlternativeNames": false,
944 "Status": c.status,
945 "Type": c.cert_type,
946 "KeyAlgorithm": c.key_algorithm,
947 "KeyUsages": ["DIGITAL_SIGNATURE", "KEY_ENCIPHERMENT"],
948 "ExtendedKeyUsages": ["TLS_WEB_SERVER_AUTHENTICATION", "TLS_WEB_CLIENT_AUTHENTICATION"],
949 "InUse": !c.in_use_by.is_empty(),
950 "Exported": false,
951 "RenewalEligibility": c.renewal_eligibility,
952 "NotBefore": c.not_before.timestamp() as f64,
953 "NotAfter": c.not_after.timestamp() as f64,
954 "CreatedAt": c.created_at.timestamp() as f64,
955 });
956 if let Some(t) = c.issued_at {
957 s.as_object_mut()
958 .unwrap()
959 .insert("IssuedAt".to_string(), json!(t.timestamp() as f64));
960 }
961 if let Some(t) = c.imported_at {
962 s.as_object_mut()
963 .unwrap()
964 .insert("ImportedAt".to_string(), json!(t.timestamp() as f64));
965 }
966 if let Some(t) = c.revoked_at {
967 s.as_object_mut()
968 .unwrap()
969 .insert("RevokedAt".to_string(), json!(t.timestamp() as f64));
970 if let Some(r) = &c.revocation_reason {
971 s.as_object_mut()
972 .unwrap()
973 .insert("RevocationReason".to_string(), json!(r));
974 }
975 }
976 if let Some(m) = &c.managed_by {
977 s.as_object_mut()
978 .unwrap()
979 .insert("ManagedBy".to_string(), json!(m));
980 }
981 s
982}
983
984fn certificate_detail_json(c: &StoredCertificate) -> Value {
985 let mut d = json!({
986 "CertificateArn": c.arn,
987 "DomainName": c.domain_name,
988 "SubjectAlternativeNames": c.subject_alternative_names,
989 "Status": c.status,
990 "Type": c.cert_type,
991 "Serial": c.serial,
992 "Subject": c.subject,
993 "Issuer": c.issuer,
994 "KeyAlgorithm": c.key_algorithm,
995 "SignatureAlgorithm": c.signature_algorithm,
996 "InUseBy": c.in_use_by,
997 "RenewalEligibility": c.renewal_eligibility,
998 "Options": {
999 "CertificateTransparencyLoggingPreference":
1000 c.options.certificate_transparency_logging_preference,
1001 "Export": c.options.export,
1002 },
1003 "DomainValidationOptions": c
1004 .domain_validation
1005 .iter()
1006 .map(domain_validation_json)
1007 .collect::<Vec<_>>(),
1008 "NotBefore": c.not_before.timestamp() as f64,
1009 "NotAfter": c.not_after.timestamp() as f64,
1010 "CreatedAt": c.created_at.timestamp() as f64,
1011 "KeyUsages": [{"Name": "DIGITAL_SIGNATURE"}, {"Name": "KEY_ENCIPHERMENT"}],
1012 "ExtendedKeyUsages": [
1013 {"Name": "TLS_WEB_SERVER_AUTHENTICATION", "OID": "1.3.6.1.5.5.7.3.1"},
1014 {"Name": "TLS_WEB_CLIENT_AUTHENTICATION", "OID": "1.3.6.1.5.5.7.3.2"},
1015 ],
1016 });
1017 if let Some(t) = c.issued_at {
1018 d.as_object_mut()
1019 .unwrap()
1020 .insert("IssuedAt".to_string(), json!(t.timestamp() as f64));
1021 }
1022 if let Some(t) = c.imported_at {
1023 d.as_object_mut()
1024 .unwrap()
1025 .insert("ImportedAt".to_string(), json!(t.timestamp() as f64));
1026 }
1027 if let Some(t) = c.revoked_at {
1028 d.as_object_mut()
1029 .unwrap()
1030 .insert("RevokedAt".to_string(), json!(t.timestamp() as f64));
1031 }
1032 if let Some(r) = &c.revocation_reason {
1033 d.as_object_mut()
1034 .unwrap()
1035 .insert("RevocationReason".to_string(), json!(r));
1036 }
1037 if let Some(m) = &c.managed_by {
1038 d.as_object_mut()
1039 .unwrap()
1040 .insert("ManagedBy".to_string(), json!(m));
1041 }
1042 if let Some(ca) = &c.certificate_authority_arn {
1043 d.as_object_mut()
1044 .unwrap()
1045 .insert("CertificateAuthorityArn".to_string(), json!(ca));
1046 }
1047 d
1048}
1049
1050fn certificate_search_result_json(c: &StoredCertificate) -> Value {
1051 let san_objects: Vec<Value> = c
1052 .subject_alternative_names
1053 .iter()
1054 .map(|s| json!({ "DnsName": s }))
1055 .collect();
1056 let cn = c
1057 .subject
1058 .strip_prefix("CN=")
1059 .unwrap_or(c.subject.as_str())
1060 .to_string();
1061 json!({
1062 "CertificateArn": c.arn,
1063 "X509Attributes": {
1064 "Subject": { "CommonName": cn },
1065 "Issuer": { "CommonName": c.issuer },
1066 "SubjectAlternativeNames": san_objects,
1067 "KeyAlgorithm": c.key_algorithm,
1068 "KeyUsages": ["DIGITAL_SIGNATURE", "KEY_ENCIPHERMENT"],
1069 "ExtendedKeyUsages": ["TLS_WEB_SERVER_AUTHENTICATION", "TLS_WEB_CLIENT_AUTHENTICATION"],
1070 "SerialNumber": c.serial,
1071 "NotBefore": c.not_before.timestamp() as f64,
1072 "NotAfter": c.not_after.timestamp() as f64,
1073 },
1074 "CertificateMetadata": {
1075 "AcmCertificateMetadata": {
1076 "DomainName": c.domain_name,
1077 "Status": c.status,
1078 "Type": c.cert_type,
1079 "InUse": !c.in_use_by.is_empty(),
1080 "Exported": false,
1081 "RenewalEligibility": c.renewal_eligibility,
1082 "CreatedAt": c.created_at.timestamp() as f64,
1083 "ManagedBy": c.managed_by.clone().unwrap_or_default(),
1084 "ValidationMethod": c.validation_method.clone().unwrap_or_default(),
1085 },
1086 },
1087 })
1088}
1089
1090fn domain_validation_json(v: &DomainValidation) -> Value {
1091 let mut out = json!({
1092 "DomainName": v.domain_name,
1093 "ValidationStatus": v.validation_status,
1094 "ValidationMethod": v.validation_method,
1095 });
1096 if let (Some(name), Some(rtype), Some(value)) = (
1097 &v.resource_record_name,
1098 &v.resource_record_type,
1099 &v.resource_record_value,
1100 ) {
1101 out.as_object_mut().unwrap().insert(
1102 "ResourceRecord".to_string(),
1103 json!({
1104 "Name": name,
1105 "Type": rtype,
1106 "Value": value,
1107 }),
1108 );
1109 }
1110 out
1111}