1use std::net::IpAddr;
9use std::sync::Arc;
10
11use axum::http::StatusCode;
12use base64::prelude::*;
13use serde::Deserialize;
14use serde_json::Value;
15use tracing::{error, info, warn};
16use uuid::Uuid;
17
18use super::access::signer_account;
19use super::error::Error;
20use super::policy::{challenge_problem, check_identifiers};
21use super::rules::{
22 check_csr_matches_order, csr_identifiers, is_wildcard, names_an_ip_address, normalize_dns_name,
23 parse_csr, parse_rfc3339, well_formed_name,
24};
25use crate::profile::Profile;
26use acme_proxy_core::audit::RequestContext;
27use acme_proxy_core::error::Problem;
28use acme_proxy_core::identifier::Identifier;
29use acme_proxy_core::jws::signature::jwk_thumbprint;
30use acme_proxy_jobs::auditor::Auditor;
31use acme_proxy_jobs::jobs::JobQueue;
32use acme_proxy_jobs::notify::ChallengeFailedData;
33use acme_proxy_jobs::notify::NotifyEvent;
34use acme_proxy_net::challenge::ValidationContext;
35use acme_proxy_policy::filter::IdentifierStage;
36use acme_proxy_policy::filter::Stage as FilterStage;
37use acme_proxy_store::account::Account;
38use acme_proxy_store::authz::Authorization;
39use acme_proxy_store::authz::Challenge;
40use acme_proxy_store::authz::ValidationClaim;
41use acme_proxy_store::db::Database;
42use acme_proxy_store::nonce::now_secs;
43use acme_proxy_store::order::Order;
44use acme_proxy_store::status::AuthzStatus;
45use acme_proxy_store::status::ChallengeStatus;
46use acme_proxy_store::status::OrderStatus;
47
48#[derive(Debug, Default, Deserialize)]
50#[serde(default)]
51pub struct NewOrderPayload {
52 pub identifiers: Vec<Identifier>,
53 #[serde(rename = "notBefore")]
54 pub not_before: Option<String>,
55 #[serde(rename = "notAfter")]
56 pub not_after: Option<String>,
57 pub replaces: Option<String>,
61}
62
63#[derive(Debug, Deserialize)]
65pub struct FinalizePayload {
66 pub csr: String,
67}
68
69fn compound_identifier_problem(mut rejections: Vec<Problem>) -> Problem {
78 if rejections.len() == 1 {
79 return rejections.remove(0);
80 }
81
82 let status = rejections
83 .iter()
84 .map(Problem::status)
85 .max()
86 .unwrap_or(StatusCode::BAD_REQUEST);
87
88 Problem::compound(status, "Some of the identifiers requested were rejected")
89 .with_subproblems(rejections)
90}
91
92async fn check_replaces(
107 cert_id: &str,
108 profile: &str,
109 account_id: Uuid,
110 identifiers: &[Identifier],
111 database: &Arc<Database>,
112) -> Result<String, Problem> {
113 let parsed = acme_proxy_core::cert::parse_ari_cert_id(cert_id).map_err(|error| {
117 warn!(event = "replaces_malformed", outcome = "failure", replaces = %cert_id, error = %error);
118 Problem::malformed(format!("Invalid `replaces` certID: {error}"))
119 })?;
120
121 let predecessor = Order::find_by_cert_serial(profile, &parsed.serial_hex(), database)
122 .await
123 .map_err(|error| {
124 error!(event = "replaces_lookup_failed", outcome = "failure", error = %error);
125 Problem::server_internal("Predecessor lookup failed")
126 })?
127 .ok_or_else(|| {
128 warn!(event = "replaces_unknown", outcome = "failure", replaces = %cert_id);
129 Problem::malformed("`replaces` names no certificate issued here")
130 })?;
131
132 if let Some(certificate) = predecessor.certificate.as_ref()
137 && let Ok(leaf_der) = acme_proxy_core::cert::leaf_der_from_chain(certificate)
138 && let Ok((aki, _)) = acme_proxy_core::cert::ari_cert_id_parts(&leaf_der)
139 && aki != parsed.aki
140 {
141 warn!(event = "replaces_aki_mismatch", outcome = "failure", replaces = %cert_id);
142 return Err(Problem::malformed(
143 "`replaces` key identifier does not match the certificate",
144 ));
145 }
146
147 if predecessor.account_id != account_id {
150 warn!(event = "replaces_wrong_account", outcome = "failure", replaces = %cert_id, order_id = %predecessor.id);
151 return Err(Problem::malformed(
152 "`replaces` names a certificate belonging to another account",
153 ));
154 }
155
156 let shares_identifier = identifiers.iter().any(|wanted| {
158 predecessor
159 .identifiers
160 .iter()
161 .any(|had| had.typ == wanted.typ && had.value == wanted.value)
162 });
163 if !shares_identifier {
164 warn!(event = "replaces_no_shared_identifier", outcome = "failure", replaces = %cert_id);
165 return Err(Problem::malformed(
166 "`replaces` names a certificate sharing no identifier with this order",
167 ));
168 }
169
170 if let Some(existing) = Order::find_by_replaces(profile, cert_id, database)
173 .await
174 .map_err(|error| {
175 error!(event = "replaces_conflict_lookup_failed", outcome = "failure", error = %error);
176 Problem::server_internal("Replacement lookup failed")
177 })?
178 {
179 warn!(
180 event = "replaces_already_claimed",
181 outcome = "failure",
182 replaces = %cert_id,
183 existing_order_id = %existing.id,
184 );
185 return Err(Problem::already_replaced(
186 "This certificate has already been marked as replaced by another order",
187 ));
188 }
189
190 info!(event = "replaces_accepted", outcome = "success", replaces = %cert_id, predecessor_order_id = %predecessor.id);
191 Ok(cert_id.to_string())
192}
193
194fn is_replaces_conflict(error: &sqlx::Error) -> bool {
213 acme_proxy_store::sql::is_unique_violation_on(
214 error,
215 "orders.replaces",
216 "idx_orders_replaces_claim",
217 )
218}
219
220fn issue_failed(
226 profile: &str,
227 account_id: Uuid,
228 order: &Order,
229 client: &acme_proxy_core::audit::ClientContext,
230 reason: &'static str,
231 detail: &str,
232) -> acme_proxy_core::audit::AuditRecord {
233 acme_proxy_core::audit::AuditRecord::new(
234 acme_proxy_core::audit::AuditEvent::CertificateIssueFailed,
235 profile,
236 acme_proxy_core::audit::Actor::acme(account_id),
237 )
238 .with_order(order.id, order.account_id, &order.identifiers)
239 .with_client(client.clone())
240 .with_reason(reason)
241 .with_detail(detail)
242}
243
244pub struct OrderService<'a> {
253 pub database: &'a Arc<Database>,
254 pub audit: &'a Auditor,
255 pub profile: &'a Profile,
256}
257
258fn validated_identifiers(
264 mut identifiers: Vec<Identifier>,
265 profile: &Profile,
266) -> Result<Vec<Identifier>, Problem> {
267 let challenges = &profile.challenges;
268
269 if identifiers.is_empty() {
270 warn!(event = "order_no_identifiers", outcome = "failure");
271 return Err(Problem::malformed("No identifiers"));
272 }
273 if identifiers.len() > profile.order.max_identifiers {
276 warn!(
277 event = "order_too_many_identifiers",
278 outcome = "failure",
279 identifiers_count = identifiers.len(),
280 limit = profile.order.max_identifiers
281 );
282 return Err(Problem::malformed(format!(
283 "An order may name at most {} identifiers; this one names {}",
284 profile.order.max_identifiers,
285 identifiers.len()
286 )));
287 }
288 if let Some(bad) = identifiers.iter().find(|id| id.typ != "dns") {
289 warn!(event = "order_identifier_type_unsupported", outcome = "failure", typ = %bad.typ);
290 return Err(Problem::unsupported_identifier(
291 "Only dns identifiers supported",
292 ));
293 }
294
295 for identifier in &mut identifiers {
296 identifier.value = normalize_dns_name(&identifier.value);
297 }
298
299 let mut seen = std::collections::HashSet::new();
305 identifiers.retain(|identifier| seen.insert(identifier.value.clone()));
306
307 let rejections: Vec<Problem> = identifiers
312 .iter()
313 .filter_map(|identifier| {
314 if !well_formed_name(&identifier.value) {
315 warn!(event = "order_identifier_malformed", outcome = "failure", value = %identifier.value);
316 Some(
317 Problem::malformed(format!(
318 "Malformed identifier {}: not a DNS name (a `*` is only legal as a single leading `*.`)",
319 identifier.value
320 ))
321 .with_identifier(identifier),
322 )
323 } else if names_an_ip_address(&identifier.value) {
324 warn!(event = "order_identifier_is_address", outcome = "failure", value = %identifier.value);
325 Some(
326 Problem::rejected_identifier(format!(
327 "Identifier {} is an IP address, which a dns identifier cannot name",
328 identifier.value
329 ))
330 .with_identifier(identifier),
331 )
332 } else if challenges
333 .types_for(is_wildcard(&identifier.value))
334 .is_empty()
335 {
336 warn!(event = "order_identifier_wildcard_rejected", outcome = "failure", value = %identifier.value);
337 Some(
338 Problem::rejected_identifier(format!(
339 "Wildcard identifier {} requires the dns-01 challenge, which is not enabled",
340 identifier.value
341 ))
342 .with_identifier(identifier),
343 )
344 } else {
345 None
346 }
347 })
348 .collect();
349
350 if !rejections.is_empty() {
351 return Err(compound_identifier_problem(rejections));
352 }
353 Ok(identifiers)
354}
355
356pub fn challenge_can_be_triggered(authz: &Authorization, order: &Order, now: i64) -> bool {
362 authz.status == AuthzStatus::Pending
363 && authz.expires > now
364 && order.status != OrderStatus::Invalid
365}
366
367impl OrderService<'_> {
368 pub async fn new_order(
377 &self,
378 payload: NewOrderPayload,
379 cached: Option<Account>,
380 pubkey: &[u8],
381 client_ip: Option<IpAddr>,
382 request: &RequestContext,
383 ) -> Result<(Order, Vec<Uuid>), Error> {
384 let (database, profile, audit) = (self.database, self.profile, self.audit);
385 let identifiers = validated_identifiers(payload.identifiers, profile)?;
386
387 let not_before = match payload.not_before {
388 Some(ref s) => Some(parse_rfc3339("notBefore", s)?),
389 None => None,
390 };
391 let not_after = match payload.not_after {
392 Some(ref s) => Some(parse_rfc3339("notAfter", s)?),
393 None => None,
394 };
395
396 let account = signer_account(cached, &profile.name, pubkey, database).await?;
397
398 check_identifiers(
399 &profile.filter,
400 client_ip,
401 &account.id.to_string(),
402 &profile.name,
403 IdentifierStage::NewOrder,
404 &identifiers,
405 database,
406 )
407 .await?;
408
409 let replaces = match payload.replaces {
412 Some(ref cert_id) => Some(
413 check_replaces(cert_id, &profile.name, account.id, &identifiers, database).await?,
414 ),
415 None => None,
416 };
417
418 let expires = now_secs() + profile.order.validity_seconds as i64;
419
420 let client = audit.client(request).await;
425 let mut order = Order::new(
426 &profile.name,
427 account.id,
428 identifiers,
429 expires,
430 not_before,
431 not_after,
432 )
433 .with_client(&client);
434 order.replaces = replaces;
435 let mut authz_ids = Vec::with_capacity(order.identifiers.len());
436
437 let persisted = async {
438 let mut tx = database.transaction().await?;
439 order.insert(tx.conn()).await?;
440
441 for identifier in &order.identifiers {
442 let authz = Authorization::new(order.id, identifier.clone(), order.expires);
443 authz.insert(tx.conn()).await?;
444 for typ in profile.challenges.types_for(is_wildcard(&identifier.value)) {
445 Challenge::new(authz.id, typ).insert(tx.conn()).await?;
446 }
447 authz_ids.push(authz.id);
448 }
449
450 tx.commit().await
451 }
452 .await;
453
454 persisted.map_err(|error| {
455 if is_replaces_conflict(&error) {
461 warn!(event = "replaces_claim_race_lost", outcome = "failure", account_id = %account.id);
462 return Problem::already_replaced(
463 "This certificate has already been marked as replaced by another order",
464 );
465 }
466 error!(
467 event = "order_creation_failed",
468 outcome = "failure",
469 error = %error,
470 account_id = %account.id
471 );
472 Problem::server_internal("Order persistence failed")
473 })?;
474
475 info!(
476 event = "order_created",
477 outcome = "success",
478 order_id = %order.id,
479 account_id = %account.id,
480 identifiers_count = order.identifiers.len()
481 );
482
483 Ok((order, authz_ids))
484 }
485
486 pub async fn deactivate_authz(
493 &self,
494 authz: &mut Authorization,
495 order: &mut Order,
496 ) -> Result<(), Error> {
497 let database = self.database;
498 if authz.status == AuthzStatus::Deactivated {
499 return Ok(());
500 }
501
502 if order.status == OrderStatus::Valid {
507 warn!(event = "authz_deactivate_refused_order_valid", outcome = "failure", authz_id = %authz.id, order_id = %order.id);
508 return Err(Problem::malformed(
509 "Cannot deactivate an authorization whose order has already been issued; revoke the certificate instead",
510 )
511 .into());
512 }
513
514 if order.status == OrderStatus::Processing {
519 warn!(event = "authz_deactivate_refused_order_processing", outcome = "failure", authz_id = %authz.id, order_id = %order.id);
520 return Err(Problem::malformed(
521 "Cannot deactivate an authorization whose order is being issued",
522 )
523 .into());
524 }
525
526 if authz.status != AuthzStatus::Pending && authz.status != AuthzStatus::Valid {
527 warn!(event = "authz_deactivate_refused_terminal", outcome = "failure", authz_id = %authz.id, status = %authz.status);
528 return Err(Problem::malformed(
529 "Authorization is in a terminal state and cannot be deactivated",
530 )
531 .into());
532 }
533
534 let outcome = async {
547 let mut tx = database.transaction().await?;
548 let deactivated = Authorization::set_deactivated(authz.id, tx.conn()).await?;
549 let demoted = deactivated && Order::set_pending(order.id, tx.conn()).await?;
550 tx.commit().await?;
551 Ok::<_, sqlx::Error>((deactivated, demoted))
552 }
553 .await;
554
555 let (deactivated, demoted) = outcome.map_err(|error| {
556 error!(event = "authz_deactivate_failed", outcome = "failure", authz_id = %authz.id, error = %error);
557 Problem::server_internal("Authorization deactivation failed")
558 })?;
559 if !deactivated {
560 warn!(event = "authz_deactivate_refused_terminal", outcome = "failure", authz_id = %authz.id, status = %authz.status);
561 return Err(Problem::malformed(
562 "Authorization is in a terminal state and cannot be deactivated",
563 )
564 .into());
565 }
566
567 authz.status = AuthzStatus::Deactivated;
570 if demoted {
571 order.status = OrderStatus::Pending;
572 }
573
574 info!(event = "authz_deactivated", outcome = "success", authz_id = %authz.id, order_id = %order.id);
575 Ok(())
576 }
577
578 pub async fn claim_challenge(
594 &self,
595 challenge: &mut Challenge,
596 authz: &Authorization,
597 order: &Order,
598 ) -> Result<ValidationClaim, Error> {
599 if authz.status != AuthzStatus::Valid && authz.expires <= now_secs() {
600 warn!(event = "authz_expired", outcome = "failure", authz_id = %authz.id, expires = authz.expires);
601 return Err(Problem::malformed("Authorization has expired").into());
602 }
603
604 if authz.status == AuthzStatus::Deactivated {
608 warn!(event = "authz_already_deactivated", outcome = "failure", authz_id = %authz.id);
609 return Err(Problem::malformed("Authorization has been deactivated").into());
610 }
611
612 let decided = challenge.status == ChallengeStatus::Valid
615 || challenge.status == ChallengeStatus::Invalid
616 || authz.status == AuthzStatus::Valid;
617 if decided {
618 return Ok(ValidationClaim::Decided);
619 }
620
621 if authz.status == AuthzStatus::Invalid || order.status == OrderStatus::Invalid {
627 let fresh = Challenge::find_by_id(challenge.id.to_string().as_str(), self.database)
636 .await
637 .map_err(|error| {
638 error!(event = "challenge_lookup_failed", outcome = "failure", challenge_id = %challenge.id, error = %error);
639 Problem::server_internal("Challenge lookup failed")
640 })?;
641 if let Some(fresh) = fresh
642 && (fresh.status == ChallengeStatus::Valid
643 || fresh.status == ChallengeStatus::Invalid)
644 {
645 *challenge = fresh;
646 return Ok(ValidationClaim::Decided);
647 }
648
649 warn!(event = "challenge_trigger_refused_invalid", outcome = "failure", authz_id = %authz.id, order_id = %order.id);
650 return Err(Problem::malformed(
651 "The authorization or its order is already invalid; create a new order",
652 )
653 .into());
654 }
655
656 let claimed = challenge
668 .claim_for_validation(self.profile.challenges.max_in_flight_per_account(), self.database)
669 .await
670 .map_err(|error| {
671 error!(event = "challenge_claim_failed", outcome = "failure", challenge_id = %challenge.id, error = %error);
672 Problem::server_internal("Challenge could not be claimed for validation")
673 })?;
674 if claimed == ValidationClaim::Limited {
675 warn!(event = "challenge_trigger_rate_limited", outcome = "failure", account_id = %order.account_id, challenge_id = %challenge.id);
676 }
677 Ok(claimed)
678 }
679
680 pub async fn run_validation(
691 &self,
692 account: &Account,
693 challenge: &mut Challenge,
694 authz: &mut Authorization,
695 order: &mut Order,
696 client_ip: Option<IpAddr>,
697 ) -> Result<(), Error> {
698 let (database, profile) = (self.database, self.profile);
699 let thumbprint = jwk_thumbprint(&account.pubkey).map_err(|error| {
700 error!(event = "authz_thumbprint_failed", outcome = "failure", account_id = %account.id, error = %error);
701 Problem::server_internal("Key authorization could not be computed")
702 })?;
703 let key_authorization = format!("{}.{}", challenge.token, thumbprint);
704 let challenge_id = challenge.id.to_string();
705
706 let context = ValidationContext {
707 identifier: authz.base_identifier(),
708 wildcard: authz.is_wildcard(),
709 token: &challenge.token,
710 key_authorization: &key_authorization,
711 challenge_id: &challenge_id,
712 };
713
714 match profile.challenges.validate(&challenge.typ, &context).await {
715 Ok(()) => {
716 commit_validation(challenge, authz, order, database).await?;
717 }
718 Err(error) => {
719 let problem =
720 challenge_problem(&error, &challenge.typ, authz.base_identifier()).to_value();
721 warn!(
722 event = "challenge_failed",
723 outcome = "failure",
724 challenge_id = %challenge_id,
725 typ = %challenge.typ,
726 kind = error.kind()
727 );
728
729 let recorded =
730 commit_validation_failure(challenge, authz, order, &problem, database).await?;
731 if !recorded {
732 return Ok(());
733 }
734
735 profile
740 .notify
741 .dispatch(NotifyEvent::ChallengeFailed(ChallengeFailedData {
742 profile: profile.name.clone(),
743 order_id: order.id.to_string(),
744 account_id: account.id.to_string(),
745 authz_id: authz.id.to_string(),
746 challenge_id: challenge.id.clone().to_string(),
747 challenge_type: challenge.typ.clone(),
748 identifier: authz.base_identifier().to_string(),
749 error: error.kind().to_string(),
750 client_ip: client_ip
751 .map(|ip| acme_proxy_core::client::canonical(ip).to_string()),
752 }))
753 .await;
754 }
755 }
756 Ok(())
757 }
758
759 pub async fn abandon_validation(
772 &self,
773 challenge: &mut Challenge,
774 authz: &mut Authorization,
775 order: &mut Order,
776 reason: &str,
777 ) -> Result<(), Error> {
778 let problem =
779 Problem::server_internal(format!("Challenge validation was not completed: {reason}"))
780 .to_value();
781 commit_validation_failure(challenge, authz, order, &problem, self.database).await?;
782 Ok(())
783 }
784
785 pub async fn finalize(
792 &self,
793 account: &Account,
794 mut order: Order,
795 csr: &str,
796 client_ip: Option<IpAddr>,
797 request: &RequestContext,
798 jobs: &JobQueue,
799 ) -> Result<Order, Error> {
800 let (database, profile, audit) = (self.database, self.profile, self.audit);
801 let filter = &profile.filter;
802
803 let id = order.id.to_string();
804 if order.status != OrderStatus::Ready {
805 warn!(event = "order_finalize_not_ready", outcome = "failure", order_id = %id, status = %order.status);
806 return Err(Problem::order_not_ready("Order is not ready").into());
807 }
808
809 let client = audit.client(request).await;
817 let failed = |order: &Order, reason: &'static str, detail: &str| {
818 issue_failed(&profile.name, account.id, order, &client, reason, detail)
819 };
820
821 let csr_der = match BASE64_URL_SAFE_NO_PAD.decode(csr) {
822 Ok(der) => der,
823 Err(_) => {
824 audit
825 .record(failed(&order, "badCSR", "CSR base64 invalid"))
826 .await;
827 return Err(Problem::bad_csr("CSR base64 invalid").into());
828 }
829 };
830 let csr = match parse_csr(&csr_der) {
831 Ok(csr) => csr,
832 Err(problem) => {
833 audit
834 .record(failed(&order, "badCSR", "CSR is unparsable"))
835 .await;
836 return Err(problem.into());
837 }
838 };
839
840 if let Err(problem) = check_csr_matches_order(&csr, &csr_der, &order.identifiers) {
845 audit
846 .record(failed(
847 &order,
848 "badCSR",
849 "CSR identifiers do not match the order",
850 ))
851 .await;
852 return Err(problem.into());
853 }
854
855 if filter.has_rules_at(FilterStage::Identifiers) {
859 let requested = csr_identifiers(&csr);
860 if let Err(problem) = check_identifiers(
861 filter,
862 client_ip,
863 order.account_id.to_string().as_str(),
864 &profile.name,
865 IdentifierStage::Csr,
866 &requested,
867 database,
868 )
869 .await
870 {
871 let (reason, detail) = if problem.status() == StatusCode::BAD_REQUEST {
876 ("badCSR", "the filter policy refused the CSR identifiers")
877 } else {
878 (
879 "serverInternal",
880 "the filter policy could not be evaluated for the CSR identifiers",
881 )
882 };
883 audit.record(failed(&order, reason, detail)).await;
884 return Err(problem.into());
885 }
886 }
887
888 let spec = super::issue::signer_issue_spec(&order, &csr_der, &client, client_ip);
898 let claimed = async {
899 let mut tx = database.transaction().await?;
900 if !order.claim_for_finalize_on(tx.conn()).await? {
901 return Ok(false);
902 }
903 jobs.enqueue_in(&spec, tx.conn()).await?;
904 tx.commit().await?;
905 Ok::<bool, sqlx::Error>(true)
906 }
907 .await;
908 match claimed {
909 Ok(true) => {}
910 Ok(false) => {
911 warn!(
912 event = "order_finalize_claim_refused",
913 outcome = "failure",
914 order_id = %id
915 );
916 return Err(Problem::order_not_ready("Order is already being finalized").into());
917 }
918 Err(error) => {
919 error!(
920 event = "order_mark_processing_failed",
921 outcome = "failure",
922 order_id = %id,
923 error = %error
924 );
925 return Err(Problem::server_internal("Order finalize failed").into());
926 }
927 }
928 jobs.wake();
929
930 info!(event = "order_finalize_queued", outcome = "success", order_id = %id);
931 Ok(order)
932 }
933}
934
935async fn commit_validation(
958 challenge: &mut Challenge,
959 authz: &mut Authorization,
960 order: &mut Order,
961 database: &Arc<Database>,
962) -> Result<(), Problem> {
963 let validated = now_secs();
964 let outcome = async {
965 let mut tx = database.transaction().await?;
966 let challenge_written = Challenge::set_valid(challenge.id, validated, tx.conn()).await?;
973 let authz_written =
974 challenge_written && Authorization::set_valid(authz.id, tx.conn()).await?;
975
976 let promoted = authz_written && {
982 let authzs = Authorization::find_by_order_with(order.id, tx.conn()).await?;
983 authzs.len() == order.identifiers.len()
984 && authzs
985 .iter()
986 .all(|authz| authz.status == AuthzStatus::Valid)
987 && Order::set_ready(order.id, tx.conn()).await?
988 };
989 tx.commit().await?;
990 Ok::<_, sqlx::Error>((challenge_written, authz_written, promoted))
991 }
992 .await;
993
994 match outcome {
995 Ok((challenge_written, authz_written, promoted)) => {
996 if challenge_written {
999 challenge.status = ChallengeStatus::Valid;
1000 challenge.validated = Some(validated);
1001 }
1002 if authz_written {
1003 authz.status = AuthzStatus::Valid;
1004 } else if challenge_written {
1005 info!(event = "challenge_verdict_superseded", outcome = "advisory", challenge_id = %challenge.id, authz_id = %authz.id);
1006 }
1007 if promoted {
1008 order.status = OrderStatus::Ready;
1009 }
1010 Ok(())
1011 }
1012 Err(error) => {
1013 error!(
1014 event = "challenge_validation_persist_failed",
1015 outcome = "failure",
1016 challenge_id = %challenge.id,
1017 authz_id = %authz.id,
1018 order_id = %order.id,
1019 error = %error
1020 );
1021 Err(Problem::server_internal("Challenge validation failed"))
1022 }
1023 }
1024}
1025
1026async fn commit_validation_failure(
1038 challenge: &mut Challenge,
1039 authz: &mut Authorization,
1040 order: &mut Order,
1041 problem: &Value,
1042 database: &Arc<Database>,
1043) -> Result<bool, Problem> {
1044 let outcome = async {
1045 let mut tx = database.transaction().await?;
1046 let challenge_written = Challenge::set_invalid(challenge.id, problem, tx.conn()).await?;
1047 let authz_written =
1048 challenge_written && Authorization::set_invalid(authz.id, tx.conn()).await?;
1049 let order_written =
1050 authz_written && Order::set_invalid(order.id, problem, tx.conn()).await?;
1051 tx.commit().await?;
1052 Ok::<_, sqlx::Error>((challenge_written, authz_written, order_written))
1053 }
1054 .await;
1055
1056 match outcome {
1057 Ok((challenge_written, authz_written, order_written)) => {
1058 if challenge_written {
1059 challenge.status = ChallengeStatus::Invalid;
1060 challenge.error = Some(problem.clone());
1061 }
1062 if authz_written {
1063 authz.status = AuthzStatus::Invalid;
1064 } else if challenge_written {
1065 info!(event = "challenge_verdict_superseded", outcome = "advisory", challenge_id = %challenge.id, authz_id = %authz.id);
1066 }
1067 if order_written {
1068 order.status = OrderStatus::Invalid;
1069 order.error = Some(problem.clone());
1070 }
1071 Ok(challenge_written)
1072 }
1073 Err(error) => {
1074 error!(
1075 event = "challenge_failure_persist_failed",
1076 outcome = "failure",
1077 challenge_id = %challenge.id,
1078 authz_id = %authz.id,
1079 order_id = %order.id,
1080 error = %error
1081 );
1082 Err(Problem::server_internal("Challenge validation failed"))
1083 }
1084 }
1085}
1086
1087#[cfg(test)]
1092pub(crate) mod tests {
1093 use super::*;
1094 use crate::profile::ProfileParts;
1095 use acme_proxy_core::identifier::Identifier;
1096 use acme_proxy_jobs::notify::NotifyDispatcher;
1097 use acme_proxy_net::challenge::ChallengeError;
1098 use acme_proxy_net::challenge::ChallengeRegistry;
1099 use acme_proxy_net::challenge::ChallengeValidator;
1100 use std::time::Duration;
1101
1102 pub(crate) fn profile(database: &Arc<Database>, challenges: ChallengeRegistry) -> Profile {
1105 let ca = acme_proxy_signer::local_ca::LocalCa::generate_in_memory(
1106 "ecdsa-p256",
1107 90,
1108 database.clone(),
1109 )
1110 .unwrap();
1111 profile_with(database, challenges, Arc::new(ca))
1112 }
1113
1114 pub(crate) fn profile_with(
1116 database: &Arc<Database>,
1117 challenges: ChallengeRegistry,
1118 signer: Arc<dyn acme_proxy_signer::SignerBackend>,
1119 ) -> Profile {
1120 Profile::new(
1121 "default",
1122 "http://localhost:3000",
1123 ProfileParts {
1124 signer_info: signer.info(),
1125 filter: Arc::new(acme_proxy_policy::filter::FilterPolicy::default()),
1126 challenges: Arc::new(challenges),
1127 order: acme_proxy_core::config::OrderConfig::default(),
1128 eab: acme_proxy_core::config::EabConfig::default(),
1129 meta: acme_proxy_core::config::MetaConfig::default(),
1130 notify: Arc::new(NotifyDispatcher::disabled(
1131 acme_proxy_jobs::testutil::idle_job_queue(database.clone()),
1132 )),
1133 },
1134 )
1135 }
1136
1137 pub(crate) async fn account(database: &Arc<Database>) -> Account {
1140 use rcgen::PublicKeyData;
1141 let key = rcgen::KeyPair::generate().unwrap();
1142 Account::find_or_create(
1143 "default",
1144 &key.subject_public_key_info(),
1145 vec![],
1146 &acme_proxy_core::audit::ClientContext::default(),
1147 database,
1148 )
1149 .await
1150 .unwrap()
1151 .0
1152 }
1153
1154 async fn pending_order(
1157 database: &Arc<Database>,
1158 account: &Account,
1159 names: &[&str],
1160 ) -> (Order, Vec<(Authorization, Challenge)>) {
1161 let order = Order::create(
1162 "default",
1163 account.id,
1164 acme_proxy_store::testutil::dns_identifiers(names),
1165 now_secs() + 3600,
1166 None,
1167 None,
1168 database,
1169 )
1170 .await
1171 .unwrap();
1172 let mut authzs = Vec::new();
1173 for name in names {
1174 let authz =
1175 Authorization::create(order.id, Identifier::dns(*name), order.expires, database)
1176 .await
1177 .unwrap();
1178 let challenge = Challenge::create(authz.id, "http-01", database)
1179 .await
1180 .unwrap();
1181 authzs.push((authz, challenge));
1182 }
1183 (order, authzs)
1184 }
1185
1186 async fn reload(database: &Database, order: &Order) -> Order {
1187 Order::find_by_id(&order.id.to_string(), database)
1188 .await
1189 .unwrap()
1190 .unwrap()
1191 }
1192
1193 async fn reload_authz(database: &Database, authz: &Authorization) -> Authorization {
1194 Authorization::find_by_id(&authz.id.to_string(), database)
1195 .await
1196 .unwrap()
1197 .unwrap()
1198 }
1199
1200 struct Refusing;
1202
1203 #[async_trait::async_trait]
1204 impl ChallengeValidator for Refusing {
1205 fn typ(&self) -> &'static str {
1206 "http-01"
1207 }
1208 async fn validate(&self, _ctx: &ValidationContext<'_>) -> Result<(), ChallengeError> {
1209 Err(ChallengeError::IncorrectResponse("wrong body".into()))
1210 }
1211 }
1212
1213 #[tokio::test]
1214 async fn deactivating_under_a_ready_order_demotes_it_in_the_same_write() {
1215 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1216 let profile = profile(&database, ChallengeRegistry::default());
1217 let audit = Auditor::offline(database.clone());
1218 let orders = OrderService {
1219 database: &database,
1220 audit: &audit,
1221 profile: &profile,
1222 };
1223 let account = account(&database).await;
1224 let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1225 let (authz, challenge) = &mut authzs[0];
1226
1227 assert_eq!(
1228 orders
1229 .claim_challenge(challenge, authz, &order)
1230 .await
1231 .unwrap(),
1232 ValidationClaim::Claimed
1233 );
1234 orders
1235 .run_validation(&account, challenge, authz, &mut order, None)
1236 .await
1237 .unwrap();
1238 assert_eq!(reload(&database, &order).await.status, OrderStatus::Ready);
1239
1240 orders.deactivate_authz(authz, &mut order).await.unwrap();
1241 assert_eq!(authz.status, AuthzStatus::Deactivated);
1242 assert_eq!(reload(&database, &order).await.status, OrderStatus::Pending);
1243
1244 orders.deactivate_authz(authz, &mut order).await.unwrap();
1246
1247 let refused = orders
1249 .claim_challenge(challenge, authz, &order)
1250 .await
1251 .unwrap_err();
1252 assert_eq!(
1253 Problem::from(refused).to_value()["detail"],
1254 "Authorization has been deactivated"
1255 );
1256 }
1257
1258 #[tokio::test]
1259 async fn an_issued_order_refuses_deactivation() {
1260 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1261 let profile = profile(&database, ChallengeRegistry::default());
1262 let audit = Auditor::offline(database.clone());
1263 let orders = OrderService {
1264 database: &database,
1265 audit: &audit,
1266 profile: &profile,
1267 };
1268 let account = account(&database).await;
1269 let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1270 order.status = OrderStatus::Valid;
1271
1272 let refused = orders
1273 .deactivate_authz(&mut authzs[0].0, &mut order)
1274 .await
1275 .unwrap_err();
1276 assert_eq!(Problem::from(refused).status(), 400);
1277 assert_eq!(authzs[0].0.status, AuthzStatus::Pending);
1278 }
1279
1280 #[tokio::test]
1283 async fn a_challenge_is_claimed_once() {
1284 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1285 let profile = profile(&database, ChallengeRegistry::default());
1286 let audit = Auditor::offline(database.clone());
1287 let orders = OrderService {
1288 database: &database,
1289 audit: &audit,
1290 profile: &profile,
1291 };
1292 let account = account(&database).await;
1293 let (order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1294 let (authz, challenge) = &mut authzs[0];
1295 let mut twin = Challenge::find_by_id(&challenge.id.to_string(), &database)
1296 .await
1297 .unwrap()
1298 .unwrap();
1299
1300 assert_eq!(
1301 orders
1302 .claim_challenge(challenge, authz, &order)
1303 .await
1304 .unwrap(),
1305 ValidationClaim::Claimed
1306 );
1307 assert_eq!(
1308 orders
1309 .claim_challenge(&mut twin, authz, &order)
1310 .await
1311 .unwrap(),
1312 ValidationClaim::Decided
1313 );
1314 }
1315
1316 #[tokio::test]
1319 async fn concurrent_validations_of_one_order_promote_it() {
1320 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1321 let profile = profile(&database, ChallengeRegistry::default());
1322 let audit = Auditor::offline(database.clone());
1323 let orders = OrderService {
1324 database: &database,
1325 audit: &audit,
1326 profile: &profile,
1327 };
1328 let account = account(&database).await;
1329 let (order, authzs) =
1330 pending_order(&database, &account, &["a.example.com", "b.example.com"]).await;
1331 let mut authzs = authzs.into_iter();
1332 let (mut authz_a, mut challenge_a) = authzs.next().unwrap();
1333 let (mut authz_b, mut challenge_b) = authzs.next().unwrap();
1334 let (mut order_a, mut order_b) = (
1335 reload(&database, &order).await,
1336 reload(&database, &order).await,
1337 );
1338
1339 let a = async {
1340 assert_eq!(
1341 orders
1342 .claim_challenge(&mut challenge_a, &authz_a, &order_a)
1343 .await
1344 .unwrap(),
1345 ValidationClaim::Claimed
1346 );
1347 orders
1348 .run_validation(&account, &mut challenge_a, &mut authz_a, &mut order_a, None)
1349 .await
1350 .unwrap();
1351 };
1352 let b = async {
1353 assert_eq!(
1354 orders
1355 .claim_challenge(&mut challenge_b, &authz_b, &order_b)
1356 .await
1357 .unwrap(),
1358 ValidationClaim::Claimed
1359 );
1360 orders
1361 .run_validation(&account, &mut challenge_b, &mut authz_b, &mut order_b, None)
1362 .await
1363 .unwrap();
1364 };
1365 tokio::join!(a, b);
1366
1367 assert_eq!(reload(&database, &order).await.status, OrderStatus::Ready);
1368 }
1369
1370 #[tokio::test]
1371 async fn a_failed_validation_invalidates_challenge_authorization_and_order_together() {
1372 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1373 let profile = profile(
1374 &database,
1375 ChallengeRegistry::new(
1376 vec![Arc::new(Refusing)],
1377 vec!["http-01".to_string()],
1378 false,
1379 Duration::from_secs(5),
1380 ),
1381 );
1382 let audit = Auditor::offline(database.clone());
1383 let orders = OrderService {
1384 database: &database,
1385 audit: &audit,
1386 profile: &profile,
1387 };
1388 let account = account(&database).await;
1389 let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1390 let (authz, challenge) = &mut authzs[0];
1391
1392 assert_eq!(
1393 orders
1394 .claim_challenge(challenge, authz, &order)
1395 .await
1396 .unwrap(),
1397 ValidationClaim::Claimed
1398 );
1399 orders
1400 .run_validation(&account, challenge, authz, &mut order, None)
1401 .await
1402 .expect("a refused validation is the challenge's answer, not an error");
1403
1404 let stored = Challenge::find_by_id(&challenge.id.to_string(), &database)
1405 .await
1406 .unwrap()
1407 .unwrap();
1408 assert_eq!(stored.status, ChallengeStatus::Invalid);
1409 assert_eq!(
1410 stored.error.unwrap()["type"],
1411 "urn:ietf:params:acme:error:incorrectResponse"
1412 );
1413 let reloaded = reload(&database, &order).await;
1414 assert_eq!(reloaded.status, OrderStatus::Invalid);
1415 assert_eq!(authz.status, AuthzStatus::Invalid);
1416 }
1417
1418 #[tokio::test]
1421 async fn duplicate_identifiers_become_one() {
1422 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1423 let profile = profile(&database, ChallengeRegistry::default());
1424 let audit = Auditor::offline(database.clone());
1425 let orders = OrderService {
1426 database: &database,
1427 audit: &audit,
1428 profile: &profile,
1429 };
1430 let account = account(&database).await;
1431 let pubkey = account.pubkey.clone();
1432 let payload = NewOrderPayload {
1433 identifiers: vec![
1434 Identifier::dns("A.example.com"),
1435 Identifier::dns("a.example.com."),
1436 ],
1437 ..Default::default()
1438 };
1439
1440 let (order, authz_ids) = orders
1441 .new_order(
1442 payload,
1443 Some(account),
1444 &pubkey,
1445 None,
1446 &RequestContext::default(),
1447 )
1448 .await
1449 .unwrap();
1450
1451 assert_eq!(
1452 order.identifiers,
1453 acme_proxy_store::testutil::dns_identifiers(&["a.example.com"])
1454 );
1455 assert_eq!(authz_ids.len(), 1);
1456 }
1457
1458 fn bypassing() -> ChallengeRegistry {
1460 ChallengeRegistry::new(
1461 vec![],
1462 vec!["http-01".to_string(), "dns-01".to_string()],
1463 true,
1464 Duration::from_secs(5),
1465 )
1466 }
1467
1468 fn refusing() -> ChallengeRegistry {
1470 ChallengeRegistry::new(
1471 vec![Arc::new(Refusing)],
1472 vec!["http-01".to_string()],
1473 false,
1474 Duration::from_secs(5),
1475 )
1476 }
1477
1478 #[tokio::test]
1484 async fn a_late_sibling_failure_leaves_an_issued_order_valid() {
1485 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1486 let passing = profile(&database, bypassing());
1487 let failing = profile(&database, refusing());
1488 let audit = Auditor::offline(database.clone());
1489 let account = account(&database).await;
1490 let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1491 let (authz, http) = &mut authzs[0];
1492 let mut dns = Challenge::create(authz.id, "dns-01", &database)
1493 .await
1494 .unwrap();
1495
1496 let on = |profile| OrderService {
1497 database: &database,
1498 audit: &audit,
1499 profile,
1500 };
1501 assert_eq!(
1502 on(&passing)
1503 .claim_challenge(&mut dns, authz, &order)
1504 .await
1505 .unwrap(),
1506 ValidationClaim::Claimed
1507 );
1508 assert_eq!(
1509 on(&passing)
1510 .claim_challenge(http, authz, &order)
1511 .await
1512 .unwrap(),
1513 ValidationClaim::Claimed
1514 );
1515
1516 let mut authz_seen_by_second = reload_authz(&database, authz).await;
1517 let mut order_seen_by_second = reload(&database, &order).await;
1518 on(&passing)
1519 .run_validation(&account, &mut dns, authz, &mut order, None)
1520 .await
1521 .unwrap();
1522 assert_eq!(reload(&database, &order).await.status, OrderStatus::Ready);
1523 sqlx::query("UPDATE orders SET status = 'valid' WHERE id = ?;")
1524 .bind(order.id)
1525 .execute(database.raw_pool())
1526 .await
1527 .unwrap();
1528
1529 on(&failing)
1530 .run_validation(
1531 &account,
1532 http,
1533 &mut authz_seen_by_second,
1534 &mut order_seen_by_second,
1535 None,
1536 )
1537 .await
1538 .unwrap();
1539
1540 assert_eq!(http.status, ChallengeStatus::Invalid);
1541 assert_eq!(reload(&database, &order).await.status, OrderStatus::Valid);
1542 assert_eq!(
1543 reload_authz(&database, authz).await.status,
1544 AuthzStatus::Valid
1545 );
1546 }
1547
1548 #[tokio::test]
1552 async fn a_verdict_after_deactivation_leaves_the_authorization_deactivated() {
1553 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1554 let profile = profile(&database, bypassing());
1555 let audit = Auditor::offline(database.clone());
1556 let orders = OrderService {
1557 database: &database,
1558 audit: &audit,
1559 profile: &profile,
1560 };
1561 let account = account(&database).await;
1562 let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1563 let (authz, challenge) = &mut authzs[0];
1564
1565 assert_eq!(
1566 orders
1567 .claim_challenge(challenge, authz, &order)
1568 .await
1569 .unwrap(),
1570 ValidationClaim::Claimed
1571 );
1572 let mut authz_seen_by_job = reload_authz(&database, authz).await;
1573 orders.deactivate_authz(authz, &mut order).await.unwrap();
1574
1575 orders
1576 .run_validation(
1577 &account,
1578 challenge,
1579 &mut authz_seen_by_job,
1580 &mut order,
1581 None,
1582 )
1583 .await
1584 .unwrap();
1585
1586 assert_eq!(
1587 reload_authz(&database, authz).await.status,
1588 AuthzStatus::Deactivated
1589 );
1590 assert_eq!(reload(&database, &order).await.status, OrderStatus::Pending);
1591 }
1592
1593 #[tokio::test]
1597 async fn an_account_over_its_validation_cap_is_rate_limited() {
1598 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1599 let profile = profile(&database, bypassing().with_max_in_flight_per_account(1));
1600 let audit = Auditor::offline(database.clone());
1601 let orders = OrderService {
1602 database: &database,
1603 audit: &audit,
1604 profile: &profile,
1605 };
1606 let account = account(&database).await;
1607 let (first_order, mut first) = pending_order(&database, &account, &["a.example.com"]).await;
1608 let (second_order, mut second) =
1609 pending_order(&database, &account, &["b.example.com"]).await;
1610 let (first_authz, first_challenge) = &mut first[0];
1611 let (second_authz, second_challenge) = &mut second[0];
1612
1613 assert_eq!(
1614 orders
1615 .claim_challenge(first_challenge, first_authz, &first_order)
1616 .await
1617 .unwrap(),
1618 ValidationClaim::Claimed
1619 );
1620 assert_eq!(
1621 orders
1622 .claim_challenge(second_challenge, second_authz, &second_order)
1623 .await
1624 .unwrap(),
1625 ValidationClaim::Limited
1626 );
1627 assert_eq!(second_challenge.status, ChallengeStatus::Pending);
1628
1629 let mut order = reload(&database, &first_order).await;
1631 orders
1632 .run_validation(&account, first_challenge, first_authz, &mut order, None)
1633 .await
1634 .unwrap();
1635 assert_eq!(
1636 orders
1637 .claim_challenge(second_challenge, second_authz, &second_order)
1638 .await
1639 .unwrap(),
1640 ValidationClaim::Claimed
1641 );
1642 }
1643
1644 #[tokio::test]
1648 async fn a_trigger_under_an_invalid_order_is_refused() {
1649 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1650 let profile = profile(&database, refusing());
1651 let audit = Auditor::offline(database.clone());
1652 let orders = OrderService {
1653 database: &database,
1654 audit: &audit,
1655 profile: &profile,
1656 };
1657 let account = account(&database).await;
1658 let (mut order, mut authzs) =
1659 pending_order(&database, &account, &["a.example.com", "b.example.com"]).await;
1660 let (first, rest) = authzs.split_at_mut(1);
1661 let (authz_a, challenge_a) = &mut first[0];
1662 let (authz_b, challenge_b) = &mut rest[0];
1663
1664 assert_eq!(
1665 orders
1666 .claim_challenge(challenge_a, authz_a, &order)
1667 .await
1668 .unwrap(),
1669 ValidationClaim::Claimed
1670 );
1671 orders
1672 .run_validation(&account, challenge_a, authz_a, &mut order, None)
1673 .await
1674 .unwrap();
1675 assert_eq!(order.status, OrderStatus::Invalid);
1676
1677 let refused = orders
1678 .claim_challenge(challenge_b, authz_b, &order)
1679 .await
1680 .unwrap_err();
1681 assert_eq!(Problem::from(refused).status(), 400);
1682
1683 assert_eq!(
1686 challenge_b
1687 .claim_for_validation(0, &database)
1688 .await
1689 .unwrap(),
1690 ValidationClaim::Decided
1691 );
1692 }
1693
1694 #[tokio::test]
1705 async fn a_trigger_that_straddles_its_own_verdict_is_answered_with_the_challenge() {
1706 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1707 let profile = profile(&database, refusing());
1708 let audit = Auditor::offline(database.clone());
1709 let orders = OrderService {
1710 database: &database,
1711 audit: &audit,
1712 profile: &profile,
1713 };
1714 let account = account(&database).await;
1715 let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1716 let (authz, challenge) = &mut authzs[0];
1717
1718 assert_eq!(
1719 orders
1720 .claim_challenge(challenge, authz, &order)
1721 .await
1722 .unwrap(),
1723 ValidationClaim::Claimed
1724 );
1725
1726 let mut stale = Challenge::find_by_id(challenge.id.to_string().as_str(), &database)
1729 .await
1730 .unwrap()
1731 .unwrap();
1732 assert_eq!(stale.status, ChallengeStatus::Processing);
1733
1734 orders
1735 .run_validation(&account, challenge, authz, &mut order, None)
1736 .await
1737 .unwrap();
1738 assert_eq!(authz.status, AuthzStatus::Invalid);
1739 assert_eq!(order.status, OrderStatus::Invalid);
1740
1741 assert_eq!(
1744 orders
1745 .claim_challenge(&mut stale, authz, &order)
1746 .await
1747 .unwrap(),
1748 ValidationClaim::Decided
1749 );
1750 assert_eq!(stale.status, ChallengeStatus::Invalid);
1751 assert!(
1752 stale.error.is_some(),
1753 "the refreshed challenge carries the verdict the client came for"
1754 );
1755 }
1756
1757 #[tokio::test]
1765 async fn a_replaces_collision_is_told_apart_from_other_unique_violations() {
1766 let database = Database::connect_in_memory().await.unwrap();
1767 sqlx::query(
1768 "INSERT INTO accounts (id, profile, pubkey, contact, status, created_at) \
1769 VALUES ('acct', 'default', X'00', '[]', 'valid', 0);",
1770 )
1771 .execute(database.raw_pool())
1772 .await
1773 .unwrap();
1774
1775 let order = |id: &'static str, replaces: &'static str| {
1776 let pool = database.raw_pool().clone();
1777 async move {
1778 sqlx::query(
1779 "INSERT INTO orders (id, profile, account_id, status, identifiers, expires, \
1780 replaces, created_at) VALUES (?, 'default', 'acct', 'pending', '[]', 0, ?, 0);",
1781 )
1782 .bind(id)
1783 .bind(replaces)
1784 .execute(&pool)
1785 .await
1786 }
1787 };
1788
1789 order("first", "predecessor-cert-id").await.unwrap();
1790 let collision = order("second", "predecessor-cert-id").await.unwrap_err();
1791 assert!(is_replaces_conflict(&collision), "got {collision}");
1792
1793 let authz = |id: &'static str| {
1797 let pool = database.raw_pool().clone();
1798 async move {
1799 sqlx::query(
1800 "INSERT INTO authorizations (id, order_id, identifier, status, expires, \
1801 created_at) VALUES (?, 'first', '{\"type\":\"dns\",\"value\":\"a.example.com\"}', \
1802 'pending', 0, 0);",
1803 )
1804 .bind(id)
1805 .execute(&pool)
1806 .await
1807 }
1808 };
1809 authz("authz-one").await.unwrap();
1810 let other = authz("authz-two").await.unwrap_err();
1811 assert!(
1812 !is_replaces_conflict(&other),
1813 "an authorization collision must not read as alreadyReplaced: {other}"
1814 );
1815
1816 let missing = sqlx::query("INSERT INTO orders (id) VALUES ('x');")
1818 .execute(database.raw_pool())
1819 .await
1820 .unwrap_err();
1821 assert!(!is_replaces_conflict(&missing));
1822 }
1823
1824 pub(crate) async fn ready_order(
1826 database: &Arc<Database>,
1827 account: &Account,
1828 ) -> (Order, String) {
1829 let (order, authzs) = pending_order(database, account, &["a.example.com"]).await;
1830 for (authz, _) in &authzs {
1831 assert!(
1832 Authorization::set_valid(authz.id, database.raw_pool())
1833 .await
1834 .unwrap()
1835 );
1836 }
1837 assert!(
1838 Order::set_ready(order.id, database.raw_pool())
1839 .await
1840 .unwrap()
1841 );
1842 let key = rcgen::KeyPair::generate().unwrap();
1843 let csr = rcgen::CertificateParams::new(vec!["a.example.com".to_string()])
1844 .unwrap()
1845 .serialize_request(&key)
1846 .unwrap();
1847 (
1848 reload(database, &order).await,
1849 BASE64_URL_SAFE_NO_PAD.encode(csr.der()),
1850 )
1851 }
1852
1853 async fn finalize_ready() -> (Arc<Database>, Order, Result<Order, Error>) {
1856 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1857 let profile = profile(&database, ChallengeRegistry::default());
1858 let audit = Auditor::offline(database.clone());
1859 let orders = OrderService {
1860 database: &database,
1861 audit: &audit,
1862 profile: &profile,
1863 };
1864 let account = account(&database).await;
1865 let (order, csr) = ready_order(&database, &account).await;
1866 let before = reload(&database, &order).await;
1867 let jobs = acme_proxy_jobs::testutil::idle_job_queue(database.clone());
1868 let outcome = orders
1869 .finalize(
1870 &account,
1871 order,
1872 &csr,
1873 None,
1874 &RequestContext::default(),
1875 &jobs,
1876 )
1877 .await;
1878 (database, before, outcome)
1879 }
1880
1881 #[tokio::test]
1885 async fn finalize_claims_the_order_and_queues_its_issuance() {
1886 let (database, order, outcome) = finalize_ready().await;
1887 let answered = outcome.unwrap();
1888 assert_eq!(answered.status, OrderStatus::Processing);
1889 let stored = reload(&database, &order).await;
1890 assert_eq!(stored.status, OrderStatus::Processing);
1891 assert!(stored.certificate.is_none(), "nothing was signed here");
1892
1893 let job = acme_proxy_store::job::Job::find_live(
1894 super::super::issue::SIGNER_ISSUE_KIND,
1895 &order.id.to_string(),
1896 &database,
1897 )
1898 .await
1899 .unwrap()
1900 .expect("the issuance is queued");
1901 assert_eq!(job.payload["order_id"], order.id.to_string());
1902 assert_eq!(job.payload["profile"], "default");
1903 assert!(
1904 job.payload["csr"]
1905 .as_str()
1906 .is_some_and(|csr| !csr.is_empty())
1907 );
1908 assert_eq!(job.deadline, Some(order.expires));
1909 }
1910
1911 #[tokio::test]
1914 async fn a_second_finalize_loses_the_claim() {
1915 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1916 let profile = profile(&database, ChallengeRegistry::default());
1917 let audit = Auditor::offline(database.clone());
1918 let orders = OrderService {
1919 database: &database,
1920 audit: &audit,
1921 profile: &profile,
1922 };
1923 let account = account(&database).await;
1924 let (order, csr) = ready_order(&database, &account).await;
1925 let jobs = acme_proxy_jobs::testutil::idle_job_queue(database.clone());
1926
1927 let rival = reload(&database, &order).await;
1929 orders
1930 .finalize(
1931 &account,
1932 order,
1933 &csr,
1934 None,
1935 &RequestContext::default(),
1936 &jobs,
1937 )
1938 .await
1939 .unwrap();
1940 let error = orders
1941 .finalize(
1942 &account,
1943 rival,
1944 &csr,
1945 None,
1946 &RequestContext::default(),
1947 &jobs,
1948 )
1949 .await
1950 .unwrap_err();
1951 let problem = Problem::from(error).to_value();
1952 assert_eq!(problem["type"], "urn:ietf:params:acme:error:orderNotReady");
1953 assert_eq!(problem["detail"], "Order is already being finalized");
1954 }
1955
1956 #[tokio::test]
1960 async fn a_failed_enqueue_leaves_the_order_ready() {
1961 let database = Arc::new(Database::connect_in_memory().await.unwrap());
1962 let profile = profile(&database, ChallengeRegistry::default());
1963 let audit = Auditor::offline(database.clone());
1964 let orders = OrderService {
1965 database: &database,
1966 audit: &audit,
1967 profile: &profile,
1968 };
1969 let account = account(&database).await;
1970 let (order, csr) = ready_order(&database, &account).await;
1971 let jobs = acme_proxy_jobs::testutil::idle_job_queue(database.clone());
1972 sqlx::query("DROP TABLE jobs;")
1973 .execute(database.raw_pool())
1974 .await
1975 .unwrap();
1976
1977 let before = reload(&database, &order).await;
1978 let error = orders
1979 .finalize(
1980 &account,
1981 order,
1982 &csr,
1983 None,
1984 &RequestContext::default(),
1985 &jobs,
1986 )
1987 .await
1988 .unwrap_err();
1989 assert_eq!(
1990 Problem::from(error).to_value()["detail"],
1991 "Order finalize failed"
1992 );
1993 assert_eq!(reload(&database, &before).await.status, OrderStatus::Ready);
1994 }
1995
1996 #[tokio::test]
2000 async fn the_trigger_predicate_agrees_with_the_claim() {
2001 let database = Arc::new(Database::connect_in_memory().await.unwrap());
2002 let profile = profile(&database, ChallengeRegistry::default());
2003 let audit = Auditor::offline(database.clone());
2004 let orders = OrderService {
2005 database: &database,
2006 audit: &audit,
2007 profile: &profile,
2008 };
2009 let account = account(&database).await;
2010 type Mutation = fn(&mut Authorization, &mut Order);
2011 let cases: [(&str, Mutation); 6] = [
2012 ("pending", |_, _| {}),
2013 ("expired", |authz, _| authz.expires = now_secs() - 1),
2014 ("deactivated", |authz, _| {
2015 authz.status = AuthzStatus::Deactivated
2016 }),
2017 ("invalid authorization", |authz, _| {
2018 authz.status = AuthzStatus::Invalid
2019 }),
2020 ("invalid order", |_, order| {
2021 order.status = OrderStatus::Invalid
2022 }),
2023 ("valid authorization", |authz, _| {
2024 authz.status = AuthzStatus::Valid
2025 }),
2026 ];
2027 for (case, mutate) in cases {
2028 let (mut order, mut authzs) =
2029 pending_order(&database, &account, &["example.com"]).await;
2030 let (mut authz, mut challenge) = authzs.remove(0);
2031 mutate(&mut authz, &mut order);
2032 let predicted = challenge_can_be_triggered(&authz, &order, now_secs());
2033 let claimed = matches!(
2034 orders.claim_challenge(&mut challenge, &authz, &order).await,
2035 Ok(ValidationClaim::Claimed)
2036 );
2037 assert_eq!(predicted, claimed, "{case}");
2038 assert_eq!(predicted, case == "pending", "{case}");
2039 }
2040 }
2041}