Skip to main content

acme_proxy_protocol/acme/
order.rs

1//! The order state machine: creating an order, deactivating an authorization,
2//! claiming and validating a challenge, and finalizing — as operations on
3//! stored rows rather than on HTTP requests.
4//!
5//! Revocation is [`super::revoke`]: it starts from a certificate rather than
6//! from an order, and an operator reaches it without one.
7
8use 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/// A newOrder payload (RFC 8555 §7.4).
49#[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    /// RFC 9773 §5: "A string uniquely identifying a previously issued
58    /// certificate that this order is intended to replace", in the certID form
59    /// of §4.1.
60    pub replaces: Option<String>,
61}
62
63/// A finalize payload (RFC 8555 §7.4).
64#[derive(Debug, Deserialize)]
65pub struct FinalizePayload {
66    pub csr: String,
67}
68
69/// Collapses per-identifier rejections into the one problem the client sees
70/// (RFC 8555 §6.7.1).
71///
72/// A single rejection is returned as itself: wrapping one problem in a
73/// `compound` would bury the type a client actually switches on for no gain.
74/// Several become a `compound` whose status is the most severe of the parts —
75/// so a batch containing a policy refusal reads as 403 rather than being
76/// downgraded to 400 by a malformed name sitting next to it.
77fn 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
92/// Validates a newOrder's `replaces` field (RFC 9773 §5) and returns the certID
93/// to store, so it can be reflected back on this and every later read.
94///
95/// §5 asks for three checks — "that the identified certificate and the newOrder
96/// request correspond to the same ACME Account, that they share at least one
97/// identifier, and that the identified certificate has not already been marked
98/// as replaced by a different Order that is not `invalid`" — and leaves
99/// anything stricter ("such as requiring exact identifier matching") to server
100/// policy. This implements exactly the three, and no more: a renewal that drops
101/// or adds a name is an ordinary, legitimate thing to do.
102///
103/// The statuses differ by design. Only the already-replaced case is pinned by
104/// the RFC (409 + `alreadyReplaced`); the rest are 400 `malformed`, matching
105/// what every other unknown-or-unowned resource in this codebase returns.
106async 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    // Parsed with the same helper `GET /renewalInfo/{certID}` uses: §5 defines
114    // the field as "constructed in the same way as the path component for GET
115    // requests described in Section 4.1", so the two must not drift.
116    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    // Same check the ARI handler makes, for the same reason: a serial alone does
133    // not identify a certificate, and the AKI half must not be decorative.
134    // Certificates issued before the local CA emitted an AKI have none, so a
135    // missing extension means "cannot check" rather than "reject".
136    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    // "…correspond to the same ACME Account". Also what stops one account
148    // probing another's certificates through this field.
149    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    // "…that they share at least one identifier".
157    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    // "…has not already been marked as replaced by a different Order that is
171    // not `invalid`" — the one case §5 gives a status and a type for.
172    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
194/// Whether a failed order INSERT was the `replaces` claim losing a race.
195///
196/// Matched on the offending *column* rather than on "any unique violation": the
197/// same transaction also inserts authorizations and challenges, each under its
198/// own `UNIQUE` constraint, and reporting one of those as `alreadyReplaced`
199/// would send a client chasing something entirely unrelated.
200///
201/// Each dialect names a different half of the same index, so both spellings go
202/// in and `sql::is_unique_violation_on` asks whichever the driver can answer.
203/// SQLite reports a partial unique index violation as `UNIQUE constraint
204/// failed: orders.profile, orders.replaces`, naming the columns and never the
205/// index; PostgreSQL reports `duplicate key value violates unique constraint
206/// "idx_orders_replaces_claim"`, naming the index and never the columns.
207/// Matching on the SQLite text alone made this silently stop recognising the
208/// collision under PostgreSQL, turning a `409 alreadyReplaced` into a `500`.
209/// Pinned by
210/// `acme_proxy_store::db::tests::one_predecessor_can_only_be_claimed_by_one_live_order`,
211/// which asserts on the message this reads.
212fn 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
220/// Builds the `certificate_issue_failed` row shared by `finalize`'s four
221/// refusal arms.
222///
223/// A free function taking `&Order` rather than a closure capturing it, so the
224/// order stays free to be claimed after the refusals are behind it.
225fn 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
244/// The order-side operations of one endpoint.
245///
246/// A borrowed bundle rather than an owned service: every caller already holds
247/// these — the ACME handlers in `AppState`, the web admin in `AdminState`, a
248/// background job in its own state — and building one is three references.
249/// The profile is the endpoint's whole configuration (its signer, filter,
250/// validators and notifier), which is what makes one operation mean the same
251/// thing whichever front end reached it.
252pub struct OrderService<'a> {
253    pub database: &'a Arc<Database>,
254    pub audit: &'a Auditor,
255    pub profile: &'a Profile,
256}
257
258/// The order's identifiers as it will store them — normalized, deduplicated,
259/// first-seen order kept — or every reason they cannot be.
260///
261/// Checked before the account is resolved, so a malformed order is refused as
262/// malformed whoever sent it.
263fn 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    // Before anything is normalized or looked at: the cost this refuses is the
274    // work below, and every bit of it scales with the count.
275    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    // Two spellings of one name are one identifier. `A.example.com` and
300    // `a.example.com.` normalize to the same value, and the order's
301    // `UNIQUE (order_id, identifier)` would answer the second one with a
302    // 500 on the write. Keeping first-seen order leaves the object the
303    // client reads back in the order it asked.
304    let mut seen = std::collections::HashSet::new();
305    identifiers.retain(|identifier| seen.insert(identifier.value.clone()));
306
307    // Every offending name at once, each attributed to itself (RFC 8555 §6.7.1).
308    // Reporting only the first would make a ten-name order a ten-round-trip
309    // guessing game — §6.7.1's own rationale: a client "may choose to submit
310    // another order containing only the eight identifiers not listed".
311    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
356impl OrderService<'_> {
357    /// Creates an order and its authorizations (RFC 8555 §7.4), for the account
358    /// that signed the request.
359    ///
360    /// `cached` is the account the JWS `kid` already resolved, if any. It is
361    /// resolved here, *after* the identifiers are checked, so a malformed order
362    /// is refused as malformed whoever sent it. `request` is where the reverse
363    /// lookup that stamps the order comes from — run late, since every refusal
364    /// above it would have wasted one.
365    pub async fn new_order(
366        &self,
367        payload: NewOrderPayload,
368        cached: Option<Account>,
369        pubkey: &[u8],
370        client_ip: Option<IpAddr>,
371        request: &RequestContext,
372    ) -> Result<(Order, Vec<Uuid>), Error> {
373        let (database, profile, audit) = (self.database, self.profile, self.audit);
374        let identifiers = validated_identifiers(payload.identifiers, profile)?;
375
376        let not_before = match payload.not_before {
377            Some(ref s) => Some(parse_rfc3339("notBefore", s)?),
378            None => None,
379        };
380        let not_after = match payload.not_after {
381            Some(ref s) => Some(parse_rfc3339("notAfter", s)?),
382            None => None,
383        };
384
385        let account = signer_account(cached, &profile.name, pubkey, database).await?;
386
387        check_identifiers(
388            &profile.filter,
389            client_ip,
390            &account.id.to_string(),
391            &profile.name,
392            IdentifierStage::NewOrder,
393            &identifiers,
394            database,
395        )
396        .await?;
397
398        // RFC 9773 §5, run after `signer_account` so the "same ACME Account" check
399        // has an account to compare against.
400        let replaces = match payload.replaces {
401            Some(ref cert_id) => Some(
402                check_replaces(cert_id, &profile.name, account.id, &identifiers, database).await?,
403            ),
404            None => None,
405        };
406
407        let expires = now_secs() + profile.order.validity_seconds as i64;
408
409        // The reverse lookup runs here rather than at the top of the handler: every
410        // refusal above (malformed name, wildcard without dns-01, `alreadyReplaced`)
411        // returns without an order to stamp, and a PTR query for a request that is
412        // about to be turned away buys nothing.
413        let client = audit.client(request).await;
414        let mut order = Order::new(
415            &profile.name,
416            account.id,
417            identifiers,
418            expires,
419            not_before,
420            not_after,
421        )
422        .with_client(&client);
423        order.replaces = replaces;
424        let mut authz_ids = Vec::with_capacity(order.identifiers.len());
425
426        let persisted = async {
427            let mut tx = database.transaction().await?;
428            order.insert(tx.conn()).await?;
429
430            for identifier in &order.identifiers {
431                let authz = Authorization::new(order.id, identifier.clone(), order.expires);
432                authz.insert(tx.conn()).await?;
433                for typ in profile.challenges.types_for(is_wildcard(&identifier.value)) {
434                    Challenge::new(authz.id, typ).insert(tx.conn()).await?;
435                }
436                authz_ids.push(authz.id);
437            }
438
439            tx.commit().await
440        }
441        .await;
442
443        persisted.map_err(|error| {
444            // The predecessor was claimed by another order between `check_replaces`
445            // reading and this transaction committing. The partial unique index on
446            // `(profile, replaces)` is what catches it; without that arm the loser
447            // of the race would get a 500 for a condition RFC 9773 §5 gives a
448            // status and a type for.
449            if is_replaces_conflict(&error) {
450                warn!(event = "replaces_claim_race_lost", outcome = "failure", account_id = %account.id);
451                return Problem::already_replaced(
452                    "This certificate has already been marked as replaced by another order",
453                );
454            }
455            error!(
456                event = "order_creation_failed",
457                outcome = "failure",
458                error = %error,
459                account_id = %account.id
460            );
461            Problem::server_internal("Order persistence failed")
462        })?;
463
464        info!(
465            event = "order_created",
466            outcome = "success",
467            order_id = %order.id,
468            account_id = %account.id,
469            identifiers_count = order.identifiers.len()
470        );
471
472        Ok((order, authz_ids))
473    }
474
475    /// Deactivates `authz` and re-derives its order's status (RFC 8555 §7.5.2).
476    ///
477    /// Already-`deactivated` is a no-op rather than an error: §7.5.2 describes the
478    /// client sending the same static object to *each* authorization of an
479    /// identifier, and a retry after a partial failure must not start reporting
480    /// errors halfway through.
481    pub async fn deactivate_authz(
482        &self,
483        authz: &mut Authorization,
484        order: &mut Order,
485    ) -> Result<(), Error> {
486        let database = self.database;
487        if authz.status == AuthzStatus::Deactivated {
488            return Ok(());
489        }
490
491        // A certificate already exists for this order, so relinquishing the
492        // authorization it was issued under would claim something untrue. §7.5.2 is
493        // about giving up the *ability* to issue, not about undoing issuance —
494        // that is what revocation (§7.6) is for.
495        if order.status == OrderStatus::Valid {
496            warn!(event = "authz_deactivate_refused_order_valid", outcome = "failure", authz_id = %authz.id, order_id = %order.id);
497            return Err(Problem::malformed(
498                "Cannot deactivate an authorization whose order has already been issued; revoke the certificate instead",
499            )
500            .into());
501        }
502
503        // The same, one step earlier: the issuance is queued and may already be
504        // signing. Refusing here keeps the answer honest; the `signer_issue` job
505        // re-checks every authorization before it signs, which is what covers a
506        // finalize that lands between this read and the write below.
507        if order.status == OrderStatus::Processing {
508            warn!(event = "authz_deactivate_refused_order_processing", outcome = "failure", authz_id = %authz.id, order_id = %order.id);
509            return Err(Problem::malformed(
510                "Cannot deactivate an authorization whose order is being issued",
511            )
512            .into());
513        }
514
515        if authz.status != AuthzStatus::Pending && authz.status != AuthzStatus::Valid {
516            warn!(event = "authz_deactivate_refused_terminal", outcome = "failure", authz_id = %authz.id, status = %authz.status);
517            return Err(Problem::malformed(
518                "Authorization is in a terminal state and cannot be deactivated",
519            )
520            .into());
521        }
522
523        // §7.5.2: "The server MUST NOT treat deactivated authorization objects as
524        // sufficient for issuing certificates." For a `pending` order that falls
525        // out of the readiness check on its own, but an order already promoted to
526        // `ready` would still finalize — so demote it.
527        //
528        // Both in one transaction. Between them, an order sits `ready` with a
529        // deactivated authorization under it: finalizable for a name the client has
530        // just given up, which is exactly what §7.5.2 forbids.
531        //
532        // Both writes are guarded, so the in-memory statuses read above only
533        // choose which error to give: a verdict that landed since leaves the
534        // authorization alone, and the order is demoted only if it is `ready`.
535        let outcome = async {
536            let mut tx = database.transaction().await?;
537            let deactivated = Authorization::set_deactivated(authz.id, tx.conn()).await?;
538            let demoted = deactivated && Order::set_pending(order.id, tx.conn()).await?;
539            tx.commit().await?;
540            Ok::<_, sqlx::Error>((deactivated, demoted))
541        }
542        .await;
543
544        let (deactivated, demoted) = outcome.map_err(|error| {
545            error!(event = "authz_deactivate_failed", outcome = "failure", authz_id = %authz.id, error = %error);
546            Problem::server_internal("Authorization deactivation failed")
547        })?;
548        if !deactivated {
549            warn!(event = "authz_deactivate_refused_terminal", outcome = "failure", authz_id = %authz.id, status = %authz.status);
550            return Err(Problem::malformed(
551                "Authorization is in a terminal state and cannot be deactivated",
552            )
553            .into());
554        }
555
556        // Only once the transaction has committed: a rollback must not leave these
557        // objects claiming a status the database never took.
558        authz.status = AuthzStatus::Deactivated;
559        if demoted {
560            order.status = OrderStatus::Pending;
561        }
562
563        info!(event = "authz_deactivated", outcome = "success", authz_id = %authz.id, order_id = %order.id);
564        Ok(())
565    }
566
567    /// Decides whether a challenge trigger (RFC 8555 §7.5.1) starts a
568    /// validation, and if so claims the challenge for it.
569    ///
570    /// [`ValidationClaim::Decided`] is not a refusal: the challenge is already
571    /// decided — here or by a sibling — or another trigger holds the claim, and
572    /// the caller answers with the challenge as it stands. `challenge` is
573    /// refreshed where the row moved under the read, so that answer is the
574    /// current object rather than the one the caller loaded.
575    /// [`ValidationClaim::Claimed`] obliges the caller to follow with
576    /// [`run_validation`](Self::run_validation), and
577    /// [`ValidationClaim::Limited`] is `429 rateLimited`: the account has
578    /// `challenge.max_in_flight_per_account` validations running already.
579    pub async fn claim_challenge(
580        &self,
581        challenge: &mut Challenge,
582        authz: &Authorization,
583        order: &Order,
584    ) -> Result<ValidationClaim, Error> {
585        if authz.status != AuthzStatus::Valid && authz.expires <= now_secs() {
586            warn!(event = "authz_expired", outcome = "failure", authz_id = %authz.id, expires = authz.expires);
587            return Err(Problem::malformed("Authorization has expired").into());
588        }
589
590        // The client gave this authorization up (RFC 8555 §7.5.2). Validating a
591        // challenge under it would walk it straight back to `valid` — which §7.5.2
592        // forbids being sufficient for issuance — so refuse before doing any work.
593        if authz.status == AuthzStatus::Deactivated {
594            warn!(event = "authz_already_deactivated", outcome = "failure", authz_id = %authz.id);
595            return Err(Problem::malformed("Authorization has been deactivated").into());
596        }
597
598        // Already answered, here or by a sibling: §7.5.1's "client requests for
599        // retries do not cause a state change".
600        let decided = challenge.status == ChallengeStatus::Valid
601            || challenge.status == ChallengeStatus::Invalid
602            || authz.status == AuthzStatus::Valid;
603        if decided {
604            return Ok(ValidationClaim::Decided);
605        }
606
607        // Undecided, but a sibling challenge failed (§7.1.6: one failure makes
608        // the authorization `invalid`), or another authorization of the order
609        // did. Nothing this challenge could prove would change that, and
610        // answering with the challenge as it stands would leave the client
611        // polling a `pending` object that can never move.
612        if authz.status == AuthzStatus::Invalid || order.status == OrderStatus::Invalid {
613            // Not before a second look at the challenge. `load_owned_challenge`
614            // reads the three rows one statement at a time, while a verdict
615            // writes all three in one transaction: a request whose challenge
616            // read lands before that commit and whose authorization read lands
617            // after sees an undecided challenge under an `invalid`
618            // authorization — a pair the write never leaves behind. Refusing on
619            // it would answer a client polling its own verdict with a `400`,
620            // where §7.5.1's answer is the object.
621            let fresh = Challenge::find_by_id(challenge.id.to_string().as_str(), self.database)
622                .await
623                .map_err(|error| {
624                    error!(event = "challenge_lookup_failed", outcome = "failure", challenge_id = %challenge.id, error = %error);
625                    Problem::server_internal("Challenge lookup failed")
626                })?;
627            if let Some(fresh) = fresh
628                && (fresh.status == ChallengeStatus::Valid
629                    || fresh.status == ChallengeStatus::Invalid)
630            {
631                *challenge = fresh;
632                return Ok(ValidationClaim::Decided);
633            }
634
635            warn!(event = "challenge_trigger_refused_invalid", outcome = "failure", authz_id = %authz.id, order_id = %order.id);
636            return Err(Problem::malformed(
637                "The authorization or its order is already invalid; create a new order",
638            )
639            .into());
640        }
641
642        // The claim, and the reason it is a claim rather than the status check
643        // above: `challenges.validate` reaches out to an address the *client*
644        // named, so two triggers that both read this row as `pending` become two
645        // probes of that host from this server — bounded only by
646        // `server.max_concurrent_requests`, on a default configuration with no
647        // filter to refuse them. Deciding it in the `UPDATE` makes "one validation
648        // per challenge" a property of the row instead of one of scheduling.
649        //
650        // The loser answers with the challenge as it now stands, which reports
651        // `processing` — §8.2's answer for a challenge the server is still
652        // working on.
653        let claimed = challenge
654            .claim_for_validation(self.profile.challenges.max_in_flight_per_account(), self.database)
655            .await
656            .map_err(|error| {
657                error!(event = "challenge_claim_failed", outcome = "failure", challenge_id = %challenge.id, error = %error);
658                Problem::server_internal("Challenge could not be claimed for validation")
659            })?;
660        if claimed == ValidationClaim::Limited {
661            warn!(event = "challenge_trigger_rate_limited", outcome = "failure", account_id = %order.account_id, challenge_id = %challenge.id);
662        }
663        Ok(claimed)
664    }
665
666    /// Validates a challenge [`claim_challenge`](Self::claim_challenge) claimed,
667    /// and records the answer.
668    ///
669    /// Either outcome is recorded — a failed validation is the challenge's
670    /// answer, not an error of this call — so `Err` means only that the answer
671    /// could not be computed or stored. On failure the operator hears about it
672    /// through `challenge_failed`, after the commit.
673    ///
674    /// `client_ip` is the address the notification names: the client that
675    /// triggered the validation, when there was one.
676    pub async fn run_validation(
677        &self,
678        account: &Account,
679        challenge: &mut Challenge,
680        authz: &mut Authorization,
681        order: &mut Order,
682        client_ip: Option<IpAddr>,
683    ) -> Result<(), Error> {
684        let (database, profile) = (self.database, self.profile);
685        let thumbprint = jwk_thumbprint(&account.pubkey).map_err(|error| {
686            error!(event = "authz_thumbprint_failed", outcome = "failure", account_id = %account.id, error = %error);
687            Problem::server_internal("Key authorization could not be computed")
688        })?;
689        let key_authorization = format!("{}.{}", challenge.token, thumbprint);
690        let challenge_id = challenge.id.to_string();
691
692        let context = ValidationContext {
693            identifier: authz.base_identifier(),
694            wildcard: authz.is_wildcard(),
695            token: &challenge.token,
696            key_authorization: &key_authorization,
697            challenge_id: &challenge_id,
698        };
699
700        match profile.challenges.validate(&challenge.typ, &context).await {
701            Ok(()) => {
702                commit_validation(challenge, authz, order, database).await?;
703            }
704            Err(error) => {
705                let problem =
706                    challenge_problem(&error, &challenge.typ, authz.base_identifier()).to_value();
707                warn!(
708                    event = "challenge_failed",
709                    outcome = "failure",
710                    challenge_id = %challenge_id,
711                    typ = %challenge.typ,
712                    kind = error.kind()
713                );
714
715                let recorded =
716                    commit_validation_failure(challenge, authz, order, &problem, database).await?;
717                if !recorded {
718                    return Ok(());
719                }
720
721                // After the commit, not before. Dispatched first, a persistence
722                // failure would have notified an operator about a failure that
723                // was never recorded — and the client, which gets a 500, would
724                // see the challenge still `pending`.
725                profile
726                    .notify
727                    .dispatch(NotifyEvent::ChallengeFailed(ChallengeFailedData {
728                        profile: profile.name.clone(),
729                        order_id: order.id.to_string(),
730                        account_id: account.id.to_string(),
731                        authz_id: authz.id.to_string(),
732                        challenge_id: challenge.id.clone().to_string(),
733                        challenge_type: challenge.typ.clone(),
734                        identifier: authz.base_identifier().to_string(),
735                        error: error.kind().to_string(),
736                        client_ip: client_ip
737                            .map(|ip| acme_proxy_core::client::canonical(ip).to_string()),
738                    }))
739                    .await;
740            }
741        }
742        Ok(())
743    }
744
745    /// Records a claimed validation the server has given up on.
746    ///
747    /// The queue retires a row when its attempts run out or its deadline passes,
748    /// and a challenge left `processing` at that point would be polled by its
749    /// client, saying nothing, until the authorization expired. This writes the
750    /// same failure `run_validation` writes — challenge, authorization and order
751    /// together in one transaction — so the client sees an `invalid` order and
752    /// stops.
753    ///
754    /// Deliberately **no `challenge_failed` notification**: nothing was learned
755    /// about the client's own setup, which is what that event reports. The
756    /// `challenge_validation_abandoned` log line is about this server instead.
757    pub async fn abandon_validation(
758        &self,
759        challenge: &mut Challenge,
760        authz: &mut Authorization,
761        order: &mut Order,
762        reason: &str,
763    ) -> Result<(), Error> {
764        let problem =
765            Problem::server_internal(format!("Challenge validation was not completed: {reason}"))
766                .to_value();
767        commit_validation_failure(challenge, authz, order, &problem, self.database).await?;
768        Ok(())
769    }
770
771    /// Finalizes `order` with the base64url CSR a client sent (RFC 8555 §7.4):
772    /// checks the CSR, then claims the order and queues its issuance, returning
773    /// it `processing`. The certificate arrives when a worker has run the
774    /// `signer_issue` job ([`super::issue`]); the client polls for it.
775    ///
776    /// `account` must already own `order` (`access::load_owned_order`).
777    pub async fn finalize(
778        &self,
779        account: &Account,
780        mut order: Order,
781        csr: &str,
782        client_ip: Option<IpAddr>,
783        request: &RequestContext,
784        jobs: &JobQueue,
785    ) -> Result<Order, Error> {
786        let (database, profile, audit) = (self.database, self.profile, self.audit);
787        let filter = &profile.filter;
788
789        let id = order.id.to_string();
790        if order.status != OrderStatus::Ready {
791            warn!(event = "order_finalize_not_ready", outcome = "failure", order_id = %id, status = %order.status);
792            return Err(Problem::order_not_ready("Order is not ready").into());
793        }
794
795        // From here down every refusal is a *CA* refusal — the CSR was rejected, or
796        // a filter said no, or issuance failed — so each one is an `audit_log` row
797        // rather than only a log line. Above this point the refusals are protocol
798        // bookkeeping (unknown order, wrong owner, not ready) with no CA action
799        // attempted, and recording them would bury the ones that matter.
800        //
801        // The reverse lookup runs once here and is reused by whichever arm answers.
802        let client = audit.client(request).await;
803        let failed = |order: &Order, reason: &'static str, detail: &str| {
804            issue_failed(&profile.name, account.id, order, &client, reason, detail)
805        };
806
807        let csr_der = match BASE64_URL_SAFE_NO_PAD.decode(csr) {
808            Ok(der) => der,
809            Err(_) => {
810                audit
811                    .record(failed(&order, "badCSR", "CSR base64 invalid"))
812                    .await;
813                return Err(Problem::bad_csr("CSR base64 invalid").into());
814            }
815        };
816        let csr = match parse_csr(&csr_der) {
817            Ok(csr) => csr,
818            Err(problem) => {
819                audit
820                    .record(failed(&order, "badCSR", "CSR is unparsable"))
821                    .await;
822                return Err(problem.into());
823            }
824        };
825
826        // Before the filter chain: this is the most fundamental and least
827        // expensive check, and doing it first guarantees that a filter — or the script
828        // of a `custom` backend — never sees anything but a CSR already in agreement with its
829        // order.
830        if let Err(problem) = check_csr_matches_order(&csr, &csr_der, &order.identifiers) {
831            audit
832                .record(failed(
833                    &order,
834                    "badCSR",
835                    "CSR identifiers do not match the order",
836                ))
837                .await;
838            return Err(problem.into());
839        }
840
841        // Gated on the *identifier* stage specifically: a policy of nothing but
842        // connection-stage rules would otherwise pay for a CSR projection nothing
843        // reads.
844        if filter.has_rules_at(FilterStage::Identifiers) {
845            let requested = csr_identifiers(&csr);
846            if let Err(problem) = check_identifiers(
847                filter,
848                client_ip,
849                order.account_id.to_string().as_str(),
850                &profile.name,
851                IdentifierStage::Csr,
852                &requested,
853                database,
854            )
855            .await
856            {
857                // A refusal and a policy the server could not evaluate are
858                // different things, and the trail said "badCSR" for both until
859                // three-valued verdicts made the second visible. `500` here means
860                // nobody decided anything about this CSR.
861                let (reason, detail) = if problem.status() == StatusCode::BAD_REQUEST {
862                    ("badCSR", "the filter policy refused the CSR identifiers")
863                } else {
864                    (
865                        "serverInternal",
866                        "the filter policy could not be evaluated for the CSR identifiers",
867                    )
868                };
869                audit.record(failed(&order, reason, detail)).await;
870                return Err(problem.into());
871            }
872        }
873
874        // Claimed here rather than at the `ready` check above, so no refusal
875        // above owes a release — and claimed **with** the job that settles it,
876        // in one transaction, so no crash can leave an order `processing` with
877        // nothing coming for it.
878        //
879        // The loser gets §7.4's own answer — `403 orderNotReady`, on which the
880        // client POST-as-GETs the order and sees `processing`, then `valid`. No
881        // audit row: like the not-ready refusal above, this is protocol
882        // bookkeeping with no CA action attempted.
883        let spec = super::issue::signer_issue_spec(&order, &csr_der, &client, client_ip);
884        let claimed = async {
885            let mut tx = database.transaction().await?;
886            if !order.claim_for_finalize_on(tx.conn()).await? {
887                return Ok(false);
888            }
889            jobs.enqueue_in(&spec, tx.conn()).await?;
890            tx.commit().await?;
891            Ok::<bool, sqlx::Error>(true)
892        }
893        .await;
894        match claimed {
895            Ok(true) => {}
896            Ok(false) => {
897                warn!(
898                    event = "order_finalize_claim_refused",
899                    outcome = "failure",
900                    order_id = %id
901                );
902                return Err(Problem::order_not_ready("Order is already being finalized").into());
903            }
904            Err(error) => {
905                error!(
906                    event = "order_mark_processing_failed",
907                    outcome = "failure",
908                    order_id = %id,
909                    error = %error
910                );
911                return Err(Problem::server_internal("Order finalize failed").into());
912            }
913        }
914        jobs.wake();
915
916        info!(event = "order_finalize_queued", outcome = "success", order_id = %id);
917        Ok(order)
918    }
919}
920
921/// Records a successful validation as **one** transaction: the challenge becomes
922/// `valid`, its authorization becomes `valid`, and the order is promoted to
923/// `ready` if that was the last one outstanding.
924///
925/// Three separate statements — which is what this was — can stop between any
926/// two. The gap that matters is the last one: an order left `pending` with every
927/// authorization already `valid` can never be finalized and nothing re-derives
928/// readiness, because the check only ever ran from here and the client has no
929/// challenge left to answer to make it run again. The order is stuck until it
930/// expires. `new_order` has always used one transaction for the same
931/// reason.
932///
933/// It also fixes a second, quieter bug. The readiness check used to re-read the
934/// authorizations *from the pool* after the write above had committed, so two
935/// concurrent validations of two authorizations of one order could each read
936/// before the other's write landed: neither would see a complete set, and
937/// neither would promote. Reading inside the transaction that just wrote means
938/// SQLite serializes the two writers, and whichever commits second is the one
939/// that sees them all `valid`. **PostgreSQL does not**: under its default READ
940/// COMMITTED isolation the two transactions lock different authorization rows,
941/// and each read sees only its own uncommitted write, so the race above is
942/// still open there.
943async fn commit_validation(
944    challenge: &mut Challenge,
945    authz: &mut Authorization,
946    order: &mut Order,
947    database: &Arc<Database>,
948) -> Result<(), Problem> {
949    let validated = now_secs();
950    let outcome = async {
951        let mut tx = database.transaction().await?;
952        // Every write below is guarded on the row still being undecided, and
953        // each reports whether it happened: this runs in a queued job, and the
954        // rows it read may have moved since. A sibling challenge may have
955        // decided the authorization, or the client may have deactivated it; the
956        // verdict is then recorded on the challenge alone and nothing above it
957        // changes.
958        let challenge_written = Challenge::set_valid(challenge.id, validated, tx.conn()).await?;
959        let authz_written =
960            challenge_written && Authorization::set_valid(authz.id, tx.conn()).await?;
961
962        // The guarded writes above have already taken the RESERVED lock by the
963        // time this reads — `transaction()` issues a deferred BEGIN — so this
964        // sees its own write and no other writer can interleave. Putting a read
965        // first here would break that. `set_ready` is guarded on `pending`, so
966        // the order's status as the job read it does not matter.
967        let promoted = authz_written && {
968            let authzs = Authorization::find_by_order_with(order.id, tx.conn()).await?;
969            authzs.len() == order.identifiers.len()
970                && authzs
971                    .iter()
972                    .all(|authz| authz.status == AuthzStatus::Valid)
973                && Order::set_ready(order.id, tx.conn()).await?
974        };
975        tx.commit().await?;
976        Ok::<_, sqlx::Error>((challenge_written, authz_written, promoted))
977    }
978    .await;
979
980    match outcome {
981        Ok((challenge_written, authz_written, promoted)) => {
982            // In-memory sync only after the commit, and only for what was
983            // written; see `Authorization::set_valid`.
984            if challenge_written {
985                challenge.status = ChallengeStatus::Valid;
986                challenge.validated = Some(validated);
987            }
988            if authz_written {
989                authz.status = AuthzStatus::Valid;
990            } else if challenge_written {
991                info!(event = "challenge_verdict_superseded", outcome = "advisory", challenge_id = %challenge.id, authz_id = %authz.id);
992            }
993            if promoted {
994                order.status = OrderStatus::Ready;
995            }
996            Ok(())
997        }
998        Err(error) => {
999            error!(
1000                event = "challenge_validation_persist_failed",
1001                outcome = "failure",
1002                challenge_id = %challenge.id,
1003                authz_id = %authz.id,
1004                order_id = %order.id,
1005                error = %error
1006            );
1007            Err(Problem::server_internal("Challenge validation failed"))
1008        }
1009    }
1010}
1011
1012/// The failure arm of [`commit_validation`], same shape: the challenge takes the
1013/// problem document explaining why, and its authorization and order both become
1014/// `invalid`, in one transaction.
1015///
1016/// Guarded the same way. A failure that lands after a sibling challenge made the
1017/// authorization `valid` is recorded on the challenge only: the authorization
1018/// was proven, and its order — perhaps already `valid`, holding a live
1019/// certificate — must not follow the failed sibling to `invalid`.
1020///
1021/// Returns whether the challenge itself took the verdict, which is what decides
1022/// whether an operator hears about it.
1023async fn commit_validation_failure(
1024    challenge: &mut Challenge,
1025    authz: &mut Authorization,
1026    order: &mut Order,
1027    problem: &Value,
1028    database: &Arc<Database>,
1029) -> Result<bool, Problem> {
1030    let outcome = async {
1031        let mut tx = database.transaction().await?;
1032        let challenge_written = Challenge::set_invalid(challenge.id, problem, tx.conn()).await?;
1033        let authz_written =
1034            challenge_written && Authorization::set_invalid(authz.id, tx.conn()).await?;
1035        let order_written =
1036            authz_written && Order::set_invalid(order.id, problem, tx.conn()).await?;
1037        tx.commit().await?;
1038        Ok::<_, sqlx::Error>((challenge_written, authz_written, order_written))
1039    }
1040    .await;
1041
1042    match outcome {
1043        Ok((challenge_written, authz_written, order_written)) => {
1044            if challenge_written {
1045                challenge.status = ChallengeStatus::Invalid;
1046                challenge.error = Some(problem.clone());
1047            }
1048            if authz_written {
1049                authz.status = AuthzStatus::Invalid;
1050            } else if challenge_written {
1051                info!(event = "challenge_verdict_superseded", outcome = "advisory", challenge_id = %challenge.id, authz_id = %authz.id);
1052            }
1053            if order_written {
1054                order.status = OrderStatus::Invalid;
1055                order.error = Some(problem.clone());
1056            }
1057            Ok(challenge_written)
1058        }
1059        Err(error) => {
1060            error!(
1061                event = "challenge_failure_persist_failed",
1062                outcome = "failure",
1063                challenge_id = %challenge.id,
1064                authz_id = %authz.id,
1065                order_id = %order.id,
1066                error = %error
1067            );
1068            Err(Problem::server_internal("Challenge validation failed"))
1069        }
1070    }
1071}
1072
1073/// `pub(crate)` so the sibling job suite can reuse `profile` and `account`
1074/// rather than growing a second copy of each — the rule `crates/signer/src/testutil.rs`
1075/// exists for, applied to two fixtures too entangled with this module's
1076/// `OrderService` to live there.
1077#[cfg(test)]
1078pub(crate) mod tests {
1079    use super::*;
1080    use crate::profile::ProfileParts;
1081    use acme_proxy_core::identifier::Identifier;
1082    use acme_proxy_jobs::notify::NotifyDispatcher;
1083    use acme_proxy_net::challenge::ChallengeError;
1084    use acme_proxy_net::challenge::ChallengeRegistry;
1085    use acme_proxy_net::challenge::ChallengeValidator;
1086    use std::time::Duration;
1087
1088    /// A `default` profile over `database`: an in-memory CA, no filter, no
1089    /// notifier, and `challenges` as the validators.
1090    pub(crate) fn profile(database: &Arc<Database>, challenges: ChallengeRegistry) -> Profile {
1091        let ca = acme_proxy_signer::local_ca::LocalCa::generate_in_memory(
1092            "ecdsa-p256",
1093            90,
1094            database.clone(),
1095        )
1096        .unwrap();
1097        profile_with(database, challenges, Arc::new(ca))
1098    }
1099
1100    /// [`profile`] over a signer of the caller's choosing.
1101    pub(crate) fn profile_with(
1102        database: &Arc<Database>,
1103        challenges: ChallengeRegistry,
1104        signer: Arc<dyn acme_proxy_signer::SignerBackend>,
1105    ) -> Profile {
1106        Profile::new(
1107            "default",
1108            "http://localhost:3000",
1109            ProfileParts {
1110                signer_info: signer.info(),
1111                filter: Arc::new(acme_proxy_policy::filter::FilterPolicy::default()),
1112                challenges: Arc::new(challenges),
1113                order: acme_proxy_core::config::OrderConfig::default(),
1114                eab: acme_proxy_core::config::EabConfig::default(),
1115                meta: acme_proxy_core::config::MetaConfig::default(),
1116                notify: Arc::new(NotifyDispatcher::disabled(
1117                    acme_proxy_jobs::testutil::idle_job_queue(database.clone()),
1118                )),
1119            },
1120        )
1121    }
1122
1123    /// An account whose stored key is a real SPKI, so a key authorization can
1124    /// be computed from it.
1125    pub(crate) async fn account(database: &Arc<Database>) -> Account {
1126        use rcgen::PublicKeyData;
1127        let key = rcgen::KeyPair::generate().unwrap();
1128        Account::find_or_create(
1129            "default",
1130            &key.subject_public_key_info(),
1131            vec![],
1132            &acme_proxy_core::audit::ClientContext::default(),
1133            database,
1134        )
1135        .await
1136        .unwrap()
1137        .0
1138    }
1139
1140    /// An order for `names`, one pending authorization and `http-01` challenge
1141    /// each.
1142    async fn pending_order(
1143        database: &Arc<Database>,
1144        account: &Account,
1145        names: &[&str],
1146    ) -> (Order, Vec<(Authorization, Challenge)>) {
1147        let order = Order::create(
1148            "default",
1149            account.id,
1150            acme_proxy_store::testutil::dns_identifiers(names),
1151            now_secs() + 3600,
1152            None,
1153            None,
1154            database,
1155        )
1156        .await
1157        .unwrap();
1158        let mut authzs = Vec::new();
1159        for name in names {
1160            let authz =
1161                Authorization::create(order.id, Identifier::dns(*name), order.expires, database)
1162                    .await
1163                    .unwrap();
1164            let challenge = Challenge::create(authz.id, "http-01", database)
1165                .await
1166                .unwrap();
1167            authzs.push((authz, challenge));
1168        }
1169        (order, authzs)
1170    }
1171
1172    async fn reload(database: &Database, order: &Order) -> Order {
1173        Order::find_by_id(&order.id.to_string(), database)
1174            .await
1175            .unwrap()
1176            .unwrap()
1177    }
1178
1179    async fn reload_authz(database: &Database, authz: &Authorization) -> Authorization {
1180        Authorization::find_by_id(&authz.id.to_string(), database)
1181            .await
1182            .unwrap()
1183            .unwrap()
1184    }
1185
1186    /// A validator refusing every attempt.
1187    struct Refusing;
1188
1189    #[async_trait::async_trait]
1190    impl ChallengeValidator for Refusing {
1191        fn typ(&self) -> &'static str {
1192            "http-01"
1193        }
1194        async fn validate(&self, _ctx: &ValidationContext<'_>) -> Result<(), ChallengeError> {
1195            Err(ChallengeError::IncorrectResponse("wrong body".into()))
1196        }
1197    }
1198
1199    #[tokio::test]
1200    async fn deactivating_under_a_ready_order_demotes_it_in_the_same_write() {
1201        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1202        let profile = profile(&database, ChallengeRegistry::default());
1203        let audit = Auditor::offline(database.clone());
1204        let orders = OrderService {
1205            database: &database,
1206            audit: &audit,
1207            profile: &profile,
1208        };
1209        let account = account(&database).await;
1210        let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1211        let (authz, challenge) = &mut authzs[0];
1212
1213        assert_eq!(
1214            orders
1215                .claim_challenge(challenge, authz, &order)
1216                .await
1217                .unwrap(),
1218            ValidationClaim::Claimed
1219        );
1220        orders
1221            .run_validation(&account, challenge, authz, &mut order, None)
1222            .await
1223            .unwrap();
1224        assert_eq!(reload(&database, &order).await.status, OrderStatus::Ready);
1225
1226        orders.deactivate_authz(authz, &mut order).await.unwrap();
1227        assert_eq!(authz.status, AuthzStatus::Deactivated);
1228        assert_eq!(reload(&database, &order).await.status, OrderStatus::Pending);
1229
1230        // A repeat is a no-op, not an error.
1231        orders.deactivate_authz(authz, &mut order).await.unwrap();
1232
1233        // And the challenge under it can no longer be triggered.
1234        let refused = orders
1235            .claim_challenge(challenge, authz, &order)
1236            .await
1237            .unwrap_err();
1238        assert_eq!(
1239            Problem::from(refused).to_value()["detail"],
1240            "Authorization has been deactivated"
1241        );
1242    }
1243
1244    #[tokio::test]
1245    async fn an_issued_order_refuses_deactivation() {
1246        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1247        let profile = profile(&database, ChallengeRegistry::default());
1248        let audit = Auditor::offline(database.clone());
1249        let orders = OrderService {
1250            database: &database,
1251            audit: &audit,
1252            profile: &profile,
1253        };
1254        let account = account(&database).await;
1255        let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1256        order.status = OrderStatus::Valid;
1257
1258        let refused = orders
1259            .deactivate_authz(&mut authzs[0].0, &mut order)
1260            .await
1261            .unwrap_err();
1262        assert_eq!(Problem::from(refused).status(), 400);
1263        assert_eq!(authzs[0].0.status, AuthzStatus::Pending);
1264    }
1265
1266    /// The claim is what makes "one validation per challenge" a property of the
1267    /// row: the second trigger answers without validating.
1268    #[tokio::test]
1269    async fn a_challenge_is_claimed_once() {
1270        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1271        let profile = profile(&database, ChallengeRegistry::default());
1272        let audit = Auditor::offline(database.clone());
1273        let orders = OrderService {
1274            database: &database,
1275            audit: &audit,
1276            profile: &profile,
1277        };
1278        let account = account(&database).await;
1279        let (order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1280        let (authz, challenge) = &mut authzs[0];
1281        let mut twin = Challenge::find_by_id(&challenge.id.to_string(), &database)
1282            .await
1283            .unwrap()
1284            .unwrap();
1285
1286        assert_eq!(
1287            orders
1288                .claim_challenge(challenge, authz, &order)
1289                .await
1290                .unwrap(),
1291            ValidationClaim::Claimed
1292        );
1293        assert_eq!(
1294            orders
1295                .claim_challenge(&mut twin, authz, &order)
1296                .await
1297                .unwrap(),
1298            ValidationClaim::Decided
1299        );
1300    }
1301
1302    /// Two authorizations of one order validated at once: whichever commits
1303    /// second reads both as `valid` inside its own transaction and promotes.
1304    #[tokio::test]
1305    async fn concurrent_validations_of_one_order_promote_it() {
1306        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1307        let profile = profile(&database, ChallengeRegistry::default());
1308        let audit = Auditor::offline(database.clone());
1309        let orders = OrderService {
1310            database: &database,
1311            audit: &audit,
1312            profile: &profile,
1313        };
1314        let account = account(&database).await;
1315        let (order, authzs) =
1316            pending_order(&database, &account, &["a.example.com", "b.example.com"]).await;
1317        let mut authzs = authzs.into_iter();
1318        let (mut authz_a, mut challenge_a) = authzs.next().unwrap();
1319        let (mut authz_b, mut challenge_b) = authzs.next().unwrap();
1320        let (mut order_a, mut order_b) = (
1321            reload(&database, &order).await,
1322            reload(&database, &order).await,
1323        );
1324
1325        let a = async {
1326            assert_eq!(
1327                orders
1328                    .claim_challenge(&mut challenge_a, &authz_a, &order_a)
1329                    .await
1330                    .unwrap(),
1331                ValidationClaim::Claimed
1332            );
1333            orders
1334                .run_validation(&account, &mut challenge_a, &mut authz_a, &mut order_a, None)
1335                .await
1336                .unwrap();
1337        };
1338        let b = async {
1339            assert_eq!(
1340                orders
1341                    .claim_challenge(&mut challenge_b, &authz_b, &order_b)
1342                    .await
1343                    .unwrap(),
1344                ValidationClaim::Claimed
1345            );
1346            orders
1347                .run_validation(&account, &mut challenge_b, &mut authz_b, &mut order_b, None)
1348                .await
1349                .unwrap();
1350        };
1351        tokio::join!(a, b);
1352
1353        assert_eq!(reload(&database, &order).await.status, OrderStatus::Ready);
1354    }
1355
1356    #[tokio::test]
1357    async fn a_failed_validation_invalidates_challenge_authorization_and_order_together() {
1358        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1359        let profile = profile(
1360            &database,
1361            ChallengeRegistry::new(
1362                vec![Arc::new(Refusing)],
1363                vec!["http-01".to_string()],
1364                false,
1365                Duration::from_secs(5),
1366            ),
1367        );
1368        let audit = Auditor::offline(database.clone());
1369        let orders = OrderService {
1370            database: &database,
1371            audit: &audit,
1372            profile: &profile,
1373        };
1374        let account = account(&database).await;
1375        let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1376        let (authz, challenge) = &mut authzs[0];
1377
1378        assert_eq!(
1379            orders
1380                .claim_challenge(challenge, authz, &order)
1381                .await
1382                .unwrap(),
1383            ValidationClaim::Claimed
1384        );
1385        orders
1386            .run_validation(&account, challenge, authz, &mut order, None)
1387            .await
1388            .expect("a refused validation is the challenge's answer, not an error");
1389
1390        let stored = Challenge::find_by_id(&challenge.id.to_string(), &database)
1391            .await
1392            .unwrap()
1393            .unwrap();
1394        assert_eq!(stored.status, ChallengeStatus::Invalid);
1395        assert_eq!(
1396            stored.error.unwrap()["type"],
1397            "urn:ietf:params:acme:error:incorrectResponse"
1398        );
1399        let reloaded = reload(&database, &order).await;
1400        assert_eq!(reloaded.status, OrderStatus::Invalid);
1401        assert_eq!(authz.status, AuthzStatus::Invalid);
1402    }
1403
1404    /// Two spellings of one name are one identifier, not a unique-violation
1405    /// 500 on the write.
1406    #[tokio::test]
1407    async fn duplicate_identifiers_become_one() {
1408        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1409        let profile = profile(&database, ChallengeRegistry::default());
1410        let audit = Auditor::offline(database.clone());
1411        let orders = OrderService {
1412            database: &database,
1413            audit: &audit,
1414            profile: &profile,
1415        };
1416        let account = account(&database).await;
1417        let pubkey = account.pubkey.clone();
1418        let payload = NewOrderPayload {
1419            identifiers: vec![
1420                Identifier::dns("A.example.com"),
1421                Identifier::dns("a.example.com."),
1422            ],
1423            ..Default::default()
1424        };
1425
1426        let (order, authz_ids) = orders
1427            .new_order(
1428                payload,
1429                Some(account),
1430                &pubkey,
1431                None,
1432                &RequestContext::default(),
1433            )
1434            .await
1435            .unwrap();
1436
1437        assert_eq!(
1438            order.identifiers,
1439            acme_proxy_store::testutil::dns_identifiers(&["a.example.com"])
1440        );
1441        assert_eq!(authz_ids.len(), 1);
1442    }
1443
1444    /// A registry that marks every challenge `valid` without a probe.
1445    fn bypassing() -> ChallengeRegistry {
1446        ChallengeRegistry::new(
1447            vec![],
1448            vec!["http-01".to_string(), "dns-01".to_string()],
1449            true,
1450            Duration::from_secs(5),
1451        )
1452    }
1453
1454    /// A registry whose only validator refuses.
1455    fn refusing() -> ChallengeRegistry {
1456        ChallengeRegistry::new(
1457            vec![Arc::new(Refusing)],
1458            vec!["http-01".to_string()],
1459            false,
1460            Duration::from_secs(5),
1461        )
1462    }
1463
1464    /// Two challenges of one authorization, both claimed before either is
1465    /// decided. The first passes and the order goes on to be issued; the second
1466    /// then fails. Its verdict lands on the challenge alone: the order holds a
1467    /// live certificate and must stay `valid`, or `Order::cleanup` would delete
1468    /// the only row that can revoke it.
1469    #[tokio::test]
1470    async fn a_late_sibling_failure_leaves_an_issued_order_valid() {
1471        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1472        let passing = profile(&database, bypassing());
1473        let failing = profile(&database, refusing());
1474        let audit = Auditor::offline(database.clone());
1475        let account = account(&database).await;
1476        let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1477        let (authz, http) = &mut authzs[0];
1478        let mut dns = Challenge::create(authz.id, "dns-01", &database)
1479            .await
1480            .unwrap();
1481
1482        let on = |profile| OrderService {
1483            database: &database,
1484            audit: &audit,
1485            profile,
1486        };
1487        assert_eq!(
1488            on(&passing)
1489                .claim_challenge(&mut dns, authz, &order)
1490                .await
1491                .unwrap(),
1492            ValidationClaim::Claimed
1493        );
1494        assert_eq!(
1495            on(&passing)
1496                .claim_challenge(http, authz, &order)
1497                .await
1498                .unwrap(),
1499            ValidationClaim::Claimed
1500        );
1501
1502        let mut authz_seen_by_second = reload_authz(&database, authz).await;
1503        let mut order_seen_by_second = reload(&database, &order).await;
1504        on(&passing)
1505            .run_validation(&account, &mut dns, authz, &mut order, None)
1506            .await
1507            .unwrap();
1508        assert_eq!(reload(&database, &order).await.status, OrderStatus::Ready);
1509        sqlx::query("UPDATE orders SET status = 'valid' WHERE id = ?;")
1510            .bind(order.id)
1511            .execute(database.raw_pool())
1512            .await
1513            .unwrap();
1514
1515        on(&failing)
1516            .run_validation(
1517                &account,
1518                http,
1519                &mut authz_seen_by_second,
1520                &mut order_seen_by_second,
1521                None,
1522            )
1523            .await
1524            .unwrap();
1525
1526        assert_eq!(http.status, ChallengeStatus::Invalid);
1527        assert_eq!(reload(&database, &order).await.status, OrderStatus::Valid);
1528        assert_eq!(
1529            reload_authz(&database, authz).await.status,
1530            AuthzStatus::Valid
1531        );
1532    }
1533
1534    /// A client deactivates an authorization while its challenge is being
1535    /// validated (§7.5.2). The verdict that arrives afterwards must not walk the
1536    /// authorization back to `valid`, nor promote the order.
1537    #[tokio::test]
1538    async fn a_verdict_after_deactivation_leaves_the_authorization_deactivated() {
1539        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1540        let profile = profile(&database, bypassing());
1541        let audit = Auditor::offline(database.clone());
1542        let orders = OrderService {
1543            database: &database,
1544            audit: &audit,
1545            profile: &profile,
1546        };
1547        let account = account(&database).await;
1548        let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1549        let (authz, challenge) = &mut authzs[0];
1550
1551        assert_eq!(
1552            orders
1553                .claim_challenge(challenge, authz, &order)
1554                .await
1555                .unwrap(),
1556            ValidationClaim::Claimed
1557        );
1558        let mut authz_seen_by_job = reload_authz(&database, authz).await;
1559        orders.deactivate_authz(authz, &mut order).await.unwrap();
1560
1561        orders
1562            .run_validation(
1563                &account,
1564                challenge,
1565                &mut authz_seen_by_job,
1566                &mut order,
1567                None,
1568            )
1569            .await
1570            .unwrap();
1571
1572        assert_eq!(
1573            reload_authz(&database, authz).await.status,
1574            AuthzStatus::Deactivated
1575        );
1576        assert_eq!(reload(&database, &order).await.status, OrderStatus::Pending);
1577    }
1578
1579    /// One account's validations are capped: the trigger over the cap is
1580    /// `429 rateLimited`, and the challenge is left `pending` so the client
1581    /// simply asks again once one of its own has settled.
1582    #[tokio::test]
1583    async fn an_account_over_its_validation_cap_is_rate_limited() {
1584        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1585        let profile = profile(&database, bypassing().with_max_in_flight_per_account(1));
1586        let audit = Auditor::offline(database.clone());
1587        let orders = OrderService {
1588            database: &database,
1589            audit: &audit,
1590            profile: &profile,
1591        };
1592        let account = account(&database).await;
1593        let (first_order, mut first) = pending_order(&database, &account, &["a.example.com"]).await;
1594        let (second_order, mut second) =
1595            pending_order(&database, &account, &["b.example.com"]).await;
1596        let (first_authz, first_challenge) = &mut first[0];
1597        let (second_authz, second_challenge) = &mut second[0];
1598
1599        assert_eq!(
1600            orders
1601                .claim_challenge(first_challenge, first_authz, &first_order)
1602                .await
1603                .unwrap(),
1604            ValidationClaim::Claimed
1605        );
1606        assert_eq!(
1607            orders
1608                .claim_challenge(second_challenge, second_authz, &second_order)
1609                .await
1610                .unwrap(),
1611            ValidationClaim::Limited
1612        );
1613        assert_eq!(second_challenge.status, ChallengeStatus::Pending);
1614
1615        // The first settles, and the second is claimable again.
1616        let mut order = reload(&database, &first_order).await;
1617        orders
1618            .run_validation(&account, first_challenge, first_authz, &mut order, None)
1619            .await
1620            .unwrap();
1621        assert_eq!(
1622            orders
1623                .claim_challenge(second_challenge, second_authz, &second_order)
1624                .await
1625                .unwrap(),
1626            ValidationClaim::Claimed
1627        );
1628    }
1629
1630    /// Once one authorization has failed, its order is `invalid`, and a trigger
1631    /// of a challenge under a sibling authorization is refused rather than
1632    /// probing the client's host for nothing.
1633    #[tokio::test]
1634    async fn a_trigger_under_an_invalid_order_is_refused() {
1635        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1636        let profile = profile(&database, refusing());
1637        let audit = Auditor::offline(database.clone());
1638        let orders = OrderService {
1639            database: &database,
1640            audit: &audit,
1641            profile: &profile,
1642        };
1643        let account = account(&database).await;
1644        let (mut order, mut authzs) =
1645            pending_order(&database, &account, &["a.example.com", "b.example.com"]).await;
1646        let (first, rest) = authzs.split_at_mut(1);
1647        let (authz_a, challenge_a) = &mut first[0];
1648        let (authz_b, challenge_b) = &mut rest[0];
1649
1650        assert_eq!(
1651            orders
1652                .claim_challenge(challenge_a, authz_a, &order)
1653                .await
1654                .unwrap(),
1655            ValidationClaim::Claimed
1656        );
1657        orders
1658            .run_validation(&account, challenge_a, authz_a, &mut order, None)
1659            .await
1660            .unwrap();
1661        assert_eq!(order.status, OrderStatus::Invalid);
1662
1663        let refused = orders
1664            .claim_challenge(challenge_b, authz_b, &order)
1665            .await
1666            .unwrap_err();
1667        assert_eq!(Problem::from(refused).status(), 400);
1668
1669        // And the row-level guard holds on its own, for a caller that read the
1670        // order before it failed.
1671        assert_eq!(
1672            challenge_b
1673                .claim_for_validation(0, &database)
1674                .await
1675                .unwrap(),
1676            ValidationClaim::Decided
1677        );
1678    }
1679
1680    /// A trigger whose reads straddle its **own** verdict is not a sibling
1681    /// failure, and is answered with the challenge (§7.5.1) rather than a `400`.
1682    ///
1683    /// `load_owned_challenge` reads the challenge, the authorization and the
1684    /// order one statement at a time, while the verdict writes all three in one
1685    /// transaction. A request that reads the challenge before that commit and
1686    /// the authorization after it holds an undecided challenge under an
1687    /// `invalid` authorization — the pair reproduced here without any timing, by
1688    /// reading the challenge back while it is still `processing` and claiming
1689    /// with it once the verdict has landed.
1690    #[tokio::test]
1691    async fn a_trigger_that_straddles_its_own_verdict_is_answered_with_the_challenge() {
1692        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1693        let profile = profile(&database, refusing());
1694        let audit = Auditor::offline(database.clone());
1695        let orders = OrderService {
1696            database: &database,
1697            audit: &audit,
1698            profile: &profile,
1699        };
1700        let account = account(&database).await;
1701        let (mut order, mut authzs) = pending_order(&database, &account, &["a.example.com"]).await;
1702        let (authz, challenge) = &mut authzs[0];
1703
1704        assert_eq!(
1705            orders
1706                .claim_challenge(challenge, authz, &order)
1707                .await
1708                .unwrap(),
1709            ValidationClaim::Claimed
1710        );
1711
1712        // The early read: the challenge as the claim left it, which is what a
1713        // concurrent trigger carries into `claim_challenge`.
1714        let mut stale = Challenge::find_by_id(challenge.id.to_string().as_str(), &database)
1715            .await
1716            .unwrap()
1717            .unwrap();
1718        assert_eq!(stale.status, ChallengeStatus::Processing);
1719
1720        orders
1721            .run_validation(&account, challenge, authz, &mut order, None)
1722            .await
1723            .unwrap();
1724        assert_eq!(authz.status, AuthzStatus::Invalid);
1725        assert_eq!(order.status, OrderStatus::Invalid);
1726
1727        // The late read of the authorization now refuses nothing: the second
1728        // look at the challenge finds the verdict and returns it.
1729        assert_eq!(
1730            orders
1731                .claim_challenge(&mut stale, authz, &order)
1732                .await
1733                .unwrap(),
1734            ValidationClaim::Decided
1735        );
1736        assert_eq!(stale.status, ChallengeStatus::Invalid);
1737        assert!(
1738            stale.error.is_some(),
1739            "the refreshed challenge carries the verdict the client came for"
1740        );
1741    }
1742
1743    /// The unique-violation arm in `new_order` only fires when two
1744    /// newOrder requests race — `check_replaces` and the partial index share a
1745    /// predicate, so nothing but real concurrency can make them disagree. This
1746    /// drives the matcher against errors the database actually produces, which
1747    /// is the part that can silently rot: SQLite names the offending *columns*,
1748    /// not the index, so a matcher written against the index name would fall
1749    /// through to a 500 and no test of the happy path would notice.
1750    #[tokio::test]
1751    async fn a_replaces_collision_is_told_apart_from_other_unique_violations() {
1752        let database = Database::connect_in_memory().await.unwrap();
1753        sqlx::query(
1754            "INSERT INTO accounts (id, profile, pubkey, contact, status, created_at) \
1755             VALUES ('acct', 'default', X'00', '[]', 'valid', 0);",
1756        )
1757        .execute(database.raw_pool())
1758        .await
1759        .unwrap();
1760
1761        let order = |id: &'static str, replaces: &'static str| {
1762            let pool = database.raw_pool().clone();
1763            async move {
1764                sqlx::query(
1765                    "INSERT INTO orders (id, profile, account_id, status, identifiers, expires, \
1766                     replaces, created_at) VALUES (?, 'default', 'acct', 'pending', '[]', 0, ?, 0);",
1767                )
1768                .bind(id)
1769                .bind(replaces)
1770                .execute(&pool)
1771                .await
1772            }
1773        };
1774
1775        order("first", "predecessor-cert-id").await.unwrap();
1776        let collision = order("second", "predecessor-cert-id").await.unwrap_err();
1777        assert!(is_replaces_conflict(&collision), "got {collision}");
1778
1779        // The same transaction inserts authorizations under their own
1780        // `UNIQUE(order_id, identifier)`. That must not be reported to a client
1781        // as `alreadyReplaced`.
1782        let authz = |id: &'static str| {
1783            let pool = database.raw_pool().clone();
1784            async move {
1785                sqlx::query(
1786                    "INSERT INTO authorizations (id, order_id, identifier, status, expires, \
1787                     created_at) VALUES (?, 'first', '{\"type\":\"dns\",\"value\":\"a.example.com\"}', \
1788                     'pending', 0, 0);",
1789                )
1790                .bind(id)
1791                .execute(&pool)
1792                .await
1793            }
1794        };
1795        authz("authz-one").await.unwrap();
1796        let other = authz("authz-two").await.unwrap_err();
1797        assert!(
1798            !is_replaces_conflict(&other),
1799            "an authorization collision must not read as alreadyReplaced: {other}"
1800        );
1801
1802        // And an unrelated failure is not swept in either.
1803        let missing = sqlx::query("INSERT INTO orders (id) VALUES ('x');")
1804            .execute(database.raw_pool())
1805            .await
1806            .unwrap_err();
1807        assert!(!is_replaces_conflict(&missing));
1808    }
1809
1810    /// A `ready` order for `a.example.com` plus a CSR matching it, base64url.
1811    pub(crate) async fn ready_order(
1812        database: &Arc<Database>,
1813        account: &Account,
1814    ) -> (Order, String) {
1815        let (order, authzs) = pending_order(database, account, &["a.example.com"]).await;
1816        for (authz, _) in &authzs {
1817            assert!(
1818                Authorization::set_valid(authz.id, database.raw_pool())
1819                    .await
1820                    .unwrap()
1821            );
1822        }
1823        assert!(
1824            Order::set_ready(order.id, database.raw_pool())
1825                .await
1826                .unwrap()
1827        );
1828        let key = rcgen::KeyPair::generate().unwrap();
1829        let csr = rcgen::CertificateParams::new(vec!["a.example.com".to_string()])
1830            .unwrap()
1831            .serialize_request(&key)
1832            .unwrap();
1833        (
1834            reload(database, &order).await,
1835            BASE64_URL_SAFE_NO_PAD.encode(csr.der()),
1836        )
1837    }
1838
1839    /// Finalizes a fresh `ready` order, returning the database, the order as
1840    /// it was, and what `finalize` answered.
1841    async fn finalize_ready() -> (Arc<Database>, Order, Result<Order, Error>) {
1842        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1843        let profile = profile(&database, ChallengeRegistry::default());
1844        let audit = Auditor::offline(database.clone());
1845        let orders = OrderService {
1846            database: &database,
1847            audit: &audit,
1848            profile: &profile,
1849        };
1850        let account = account(&database).await;
1851        let (order, csr) = ready_order(&database, &account).await;
1852        let before = reload(&database, &order).await;
1853        let jobs = acme_proxy_jobs::testutil::idle_job_queue(database.clone());
1854        let outcome = orders
1855            .finalize(
1856                &account,
1857                order,
1858                &csr,
1859                None,
1860                &RequestContext::default(),
1861                &jobs,
1862            )
1863            .await;
1864        (database, before, outcome)
1865    }
1866
1867    /// Finalize signs nothing: it claims the order and queues its issuance in
1868    /// one write, answering `processing` — the process answering ACME holds no
1869    /// backend.
1870    #[tokio::test]
1871    async fn finalize_claims_the_order_and_queues_its_issuance() {
1872        let (database, order, outcome) = finalize_ready().await;
1873        let answered = outcome.unwrap();
1874        assert_eq!(answered.status, OrderStatus::Processing);
1875        let stored = reload(&database, &order).await;
1876        assert_eq!(stored.status, OrderStatus::Processing);
1877        assert!(stored.certificate.is_none(), "nothing was signed here");
1878
1879        let job = acme_proxy_store::job::Job::find_live(
1880            super::super::issue::SIGNER_ISSUE_KIND,
1881            &order.id.to_string(),
1882            &database,
1883        )
1884        .await
1885        .unwrap()
1886        .expect("the issuance is queued");
1887        assert_eq!(job.payload["order_id"], order.id.to_string());
1888        assert_eq!(job.payload["profile"], "default");
1889        assert!(
1890            job.payload["csr"]
1891                .as_str()
1892                .is_some_and(|csr| !csr.is_empty())
1893        );
1894        assert_eq!(job.deadline, Some(order.expires));
1895    }
1896
1897    /// Two finalizes racing on one order: the loser's claim fails, it is told
1898    /// §7.4's `orderNotReady`, and only one issuance is queued.
1899    #[tokio::test]
1900    async fn a_second_finalize_loses_the_claim() {
1901        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1902        let profile = profile(&database, ChallengeRegistry::default());
1903        let audit = Auditor::offline(database.clone());
1904        let orders = OrderService {
1905            database: &database,
1906            audit: &audit,
1907            profile: &profile,
1908        };
1909        let account = account(&database).await;
1910        let (order, csr) = ready_order(&database, &account).await;
1911        let jobs = acme_proxy_jobs::testutil::idle_job_queue(database.clone());
1912
1913        // Both requests read the order `ready`.
1914        let rival = reload(&database, &order).await;
1915        orders
1916            .finalize(
1917                &account,
1918                order,
1919                &csr,
1920                None,
1921                &RequestContext::default(),
1922                &jobs,
1923            )
1924            .await
1925            .unwrap();
1926        let error = orders
1927            .finalize(
1928                &account,
1929                rival,
1930                &csr,
1931                None,
1932                &RequestContext::default(),
1933                &jobs,
1934            )
1935            .await
1936            .unwrap_err();
1937        let problem = Problem::from(error).to_value();
1938        assert_eq!(problem["type"], "urn:ietf:params:acme:error:orderNotReady");
1939        assert_eq!(problem["detail"], "Order is already being finalized");
1940    }
1941
1942    /// The claim and the job are one write: if the job cannot be queued, the
1943    /// order is not claimed either, so it is never `processing` with nothing
1944    /// coming for it.
1945    #[tokio::test]
1946    async fn a_failed_enqueue_leaves_the_order_ready() {
1947        let database = Arc::new(Database::connect_in_memory().await.unwrap());
1948        let profile = profile(&database, ChallengeRegistry::default());
1949        let audit = Auditor::offline(database.clone());
1950        let orders = OrderService {
1951            database: &database,
1952            audit: &audit,
1953            profile: &profile,
1954        };
1955        let account = account(&database).await;
1956        let (order, csr) = ready_order(&database, &account).await;
1957        let jobs = acme_proxy_jobs::testutil::idle_job_queue(database.clone());
1958        sqlx::query("DROP TABLE jobs;")
1959            .execute(database.raw_pool())
1960            .await
1961            .unwrap();
1962
1963        let before = reload(&database, &order).await;
1964        let error = orders
1965            .finalize(
1966                &account,
1967                order,
1968                &csr,
1969                None,
1970                &RequestContext::default(),
1971                &jobs,
1972            )
1973            .await
1974            .unwrap_err();
1975        assert_eq!(
1976            Problem::from(error).to_value()["detail"],
1977            "Order finalize failed"
1978        );
1979        assert_eq!(reload(&database, &before).await.status, OrderStatus::Ready);
1980    }
1981}