Skip to main content

kipuka/star/
mod.rs

1//! STAR (Short-Term Automatic Renewal) certificate management (RFC 8739).
2//!
3//! STAR issues short-lived certificates that auto-renew without client
4//! interaction.  The server pre-generates renewal certificates before the
5//! current one expires, so clients just fetch the latest.
6//!
7//! This is the server-side answer to the CA/B Forum 47-day certificate
8//! validity mandate taking effect March 2029.
9
10pub mod renewal;
11
12use std::time::Duration;
13
14use chrono::{DateTime, Utc};
15use dashmap::DashMap;
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18use tracing::{debug, info, warn};
19use uuid::Uuid;
20
21/// Errors from STAR order and certificate operations.
22///
23/// Maps to the problem document types defined in RFC 8739 §3.4.
24#[derive(Debug, Error)]
25pub enum StarError {
26    /// The requested STAR order does not exist.
27    #[error("STAR order not found: {0}")]
28    OrderNotFound(String),
29
30    /// The STAR order has been cancelled by the subscriber or IdO.
31    ///
32    /// RFC 8739 §3.1.1: a cancelled order MUST NOT issue further certificates.
33    #[error("STAR order cancelled: {0}")]
34    OrderCancelled(String),
35
36    /// The STAR order has exceeded its lifetime window.
37    #[error("STAR order expired: {0}")]
38    OrderExpired(String),
39
40    /// The maximum number of renewals for this order has been reached.
41    ///
42    /// RFC 8739 §3.1: `auto-renewal-end-date` determines when renewals stop.
43    #[error("STAR order {order_id} reached maximum renewals ({max})")]
44    MaxRenewalsReached { order_id: String, max: u32 },
45
46    /// The server has reached its maximum number of active STAR orders.
47    ///
48    /// This is a resource-exhaustion guard configured via `[star].max_active_orders`.
49    #[error("maximum active STAR orders reached ({limit})")]
50    MaxOrdersReached { limit: usize },
51
52    /// The requested renewal interval is outside the configured bounds.
53    ///
54    /// RFC 8739 §3.1: the server advertises acceptable interval ranges.
55    #[error("invalid renewal interval: {requested}s (allowed {min}s–{max}s)")]
56    InvalidInterval { requested: u64, min: u64, max: u64 },
57
58    /// Certificate issuance failed during renewal.
59    #[error("issuance error: {0}")]
60    IssuanceError(String),
61
62    /// Database or storage operation failed.
63    #[error("database error: {0}")]
64    DatabaseError(String),
65}
66
67/// A certificate issued as part of a STAR renewal cycle.
68///
69/// Each renewal produces a new `StarCertificate` that replaces the previous
70/// one.  Clients fetch the latest via the STAR certificate URL
71/// (RFC 8739 §3.3).
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct StarCertificate {
74    /// DER-encoded X.509 certificate (current renewal).
75    pub certificate_der: Vec<u8>,
76    /// Serial number of this certificate (hex string).
77    pub serial_number: String,
78    /// Validity start (Not Before).
79    pub not_before: DateTime<Utc>,
80    /// Validity end (Not After).
81    pub not_after: DateTime<Utc>,
82    /// Which renewal produced this certificate (0 = initial).
83    pub renewal_number: u32,
84    /// Parent STAR order identifier.
85    pub star_order_id: String,
86}
87
88/// Lifecycle status of a STAR order.
89///
90/// RFC 8739 §3.1.1 defines the order state machine.
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
92pub enum StarOrderStatus {
93    /// The order is actively renewing certificates.
94    Active,
95    /// The subscriber or IdO cancelled the order; no further renewals.
96    Cancelled,
97    /// The current certificate covers the complete order lifetime.
98    Completed,
99    /// The order's `lifetime_end` has passed.
100    Expired,
101}
102
103impl StarOrderStatus {
104    /// Return a lowercase string representation for logging and API responses.
105    pub fn as_str(&self) -> &'static str {
106        match self {
107            Self::Active => "active",
108            Self::Cancelled => "cancelled",
109            Self::Completed => "completed",
110            Self::Expired => "expired",
111        }
112    }
113}
114
115/// A STAR order representing a recurring certificate renewal agreement.
116///
117/// RFC 8739 §3.1: the order captures the renewal interval, total lifetime,
118/// subject identity, and the CA responsible for issuance.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct StarOrder {
121    /// Unique order identifier (UUID v4).
122    pub id: String,
123    /// Subject distinguished name for issued certificates.
124    pub subject_dn: String,
125    /// Key algorithm, e.g., `"ec:P-256"` or `"rsa:2048"`.
126    pub key_type: String,
127    /// Enrollment profile name (maps to `EnrollmentProfile`).
128    pub profile: String,
129    /// How often to renew the certificate.
130    ///
131    /// RFC 8739 §3.1: `auto-renewal-lifetime` in the order resource.
132    #[serde(with = "humantime_serde")]
133    pub renewal_interval: Duration,
134    /// When the STAR order expires and no more renewals are issued.
135    ///
136    /// RFC 8739 §3.1: `auto-renewal-end-date`.
137    pub lifetime_end: DateTime<Utc>,
138    /// Maximum number of certificates this order will produce.
139    pub max_renewals: u32,
140    /// How many renewals have been issued so far.
141    pub current_renewals: u32,
142    /// Current lifecycle status.
143    pub status: StarOrderStatus,
144    /// DN of the entity that requested this order (from TLS client cert).
145    pub requestor_dn: Option<String>,
146    /// CA identifier that signs renewal certificates.
147    pub ca_id: String,
148    /// DER-encoded PKCS#10 CSR used for each renewal.
149    pub csr_der: Vec<u8>,
150    /// Most recently issued certificate (if any).
151    pub current_certificate: Option<StarCertificate>,
152    /// When the order was created.
153    pub created_at: DateTime<Utc>,
154    /// When the order was cancelled (only set for `Cancelled` status).
155    pub cancelled_at: Option<DateTime<Utc>>,
156}
157
158/// Manages STAR orders and certificate renewal state.
159///
160/// `StarManager` is the server-side implementation of the STAR protocol
161/// (RFC 8739).  It tracks active orders in a concurrent map and provides
162/// methods for the renewal loop to query which orders need new certificates.
163pub struct StarManager {
164    /// Active STAR orders keyed by order ID.
165    orders: DashMap<String, StarOrder>,
166    operation_lock: tokio::sync::Mutex<()>,
167    /// STAR subsystem configuration.
168    config: crate::config::StarConfig,
169}
170
171impl StarManager {
172    /// Serialize admission, cancellation and renewal across awaits.
173    pub async fn operation_guard(&self) -> tokio::sync::MutexGuard<'_, ()> {
174        self.operation_lock.lock().await
175    }
176
177    /// Restore committed orders and their latest certificates before serving requests.
178    pub async fn restore(&self, db: &sqlx::AnyPool) -> Result<(), StarError> {
179        use sqlx::Row;
180        let rows = sqlx::query("SELECT id, subject_dn, key_type, profile, renewal_interval_secs, lifetime_end, max_renewals, current_renewals, CASE WHEN status = 'active' THEN 'active' WHEN status = 'completed' THEN 'completed' WHEN status = 'cancelled' THEN 'cancelled' ELSE 'expired' END AS status, requestor_dn, ca_id, csr_der, created_at, cancelled_at FROM star_orders").fetch_all(db).await.map_err(|e| StarError::DatabaseError(e.to_string()))?;
181        let parse = |s: String| {
182            DateTime::parse_from_rfc3339(&s)
183                .map(|v| v.with_timezone(&Utc))
184                .map_err(|e| StarError::DatabaseError(e.to_string()))
185        };
186        let mut restored = Vec::new();
187        for row in rows {
188            let get = |key: &str| {
189                row.try_get::<String, _>(key)
190                    .map_err(|e| StarError::DatabaseError(e.to_string()))
191            };
192            let num = |key: &str| {
193                row.try_get::<i64, _>(key)
194                    .map_err(|e| StarError::DatabaseError(e.to_string()))
195            };
196            let id = get("id")?;
197            let status = match get("status")?.as_str() {
198                "active" => StarOrderStatus::Active,
199                "completed" => StarOrderStatus::Completed,
200                "cancelled" => StarOrderStatus::Cancelled,
201                "expired" => StarOrderStatus::Expired,
202                _ => {
203                    return Err(StarError::DatabaseError(
204                        "invalid stored order status".into(),
205                    ));
206                }
207            };
208            let latest = sqlx::query(crate::db::pg_sql("SELECT serial_number, certificate_der, not_before, not_after, renewal_number FROM star_certificates WHERE star_order_id = ? ORDER BY renewal_number DESC LIMIT 1")).bind(&id).fetch_optional(db).await.map_err(|e| StarError::DatabaseError(e.to_string()))?;
209            let certificate = if let Some(c) = latest {
210                Some(StarCertificate {
211                    serial_number: c
212                        .try_get("serial_number")
213                        .map_err(|e| StarError::DatabaseError(e.to_string()))?,
214                    certificate_der: c
215                        .try_get("certificate_der")
216                        .map_err(|e| StarError::DatabaseError(e.to_string()))?,
217                    not_before: parse(
218                        c.try_get("not_before")
219                            .map_err(|e| StarError::DatabaseError(e.to_string()))?,
220                    )?,
221                    not_after: parse(
222                        c.try_get("not_after")
223                            .map_err(|e| StarError::DatabaseError(e.to_string()))?,
224                    )?,
225                    renewal_number: c
226                        .try_get::<i64, _>("renewal_number")
227                        .map_err(|e| StarError::DatabaseError(e.to_string()))?
228                        as u32,
229                    star_order_id: id.clone(),
230                })
231            } else {
232                None
233            };
234            let cancelled: Option<String> = row
235                .try_get("cancelled_at")
236                .map_err(|e| StarError::DatabaseError(e.to_string()))?;
237            restored.push(StarOrder {
238                id,
239                subject_dn: get("subject_dn")?,
240                key_type: get("key_type")?,
241                profile: get("profile")?,
242                renewal_interval: Duration::from_secs(num("renewal_interval_secs")? as u64),
243                lifetime_end: parse(get("lifetime_end")?)?,
244                max_renewals: num("max_renewals")? as u32,
245                current_renewals: num("current_renewals")? as u32,
246                status,
247                requestor_dn: row
248                    .try_get("requestor_dn")
249                    .map_err(|e| StarError::DatabaseError(e.to_string()))?,
250                ca_id: get("ca_id")?,
251                csr_der: row
252                    .try_get("csr_der")
253                    .map_err(|e| StarError::DatabaseError(e.to_string()))?,
254                created_at: parse(get("created_at")?)?,
255                cancelled_at: cancelled.map(parse).transpose()?,
256                current_certificate: certificate,
257            });
258        }
259        self.orders.clear();
260        for mut order in restored {
261            if order.status == StarOrderStatus::Completed
262                && order.lifetime_end > Utc::now()
263                && order
264                    .current_certificate
265                    .as_ref()
266                    .is_none_or(|cert| cert.not_after.timestamp() < order.lifetime_end.timestamp())
267            {
268                sqlx::query(crate::db::pg_sql("UPDATE star_orders SET status = 'active' WHERE id = ? AND status = 'completed'"))
269                    .bind(&order.id).execute(db).await.map_err(|e| StarError::DatabaseError(e.to_string()))?;
270                order.status = StarOrderStatus::Active;
271            }
272            self.orders.insert(order.id.clone(), order);
273        }
274        self.cleanup_expired();
275        Ok(())
276    }
277
278    /// Remove a provisional admission after its database transaction fails.
279    pub fn remove_order(&self, id: &str) {
280        self.orders.remove(id);
281    }
282
283    /// Create a new `StarManager` with the given configuration.
284    pub fn new(config: crate::config::StarConfig) -> Self {
285        info!(
286            min_interval = config.min_renewal_interval_secs,
287            max_interval = config.max_renewal_interval_secs,
288            max_orders = config.max_active_orders,
289            pre_renewal = config.pre_renewal_factor,
290            "STAR manager initialised"
291        );
292        Self {
293            orders: DashMap::new(),
294            operation_lock: tokio::sync::Mutex::new(()),
295            config,
296        }
297    }
298
299    /// Create a new STAR order.
300    ///
301    /// Validates the renewal interval against configured bounds, checks the
302    /// active-order limit, and computes the total number of renewals from
303    /// the requested lifetime.
304    ///
305    /// RFC 8739 §3.1: the server MUST validate `auto-renewal-lifetime` and
306    /// `auto-renewal-end-date` against its policy before accepting the order.
307    #[allow(clippy::too_many_arguments)]
308    pub fn create_order(
309        &self,
310        subject_dn: String,
311        key_type: String,
312        profile: String,
313        renewal_interval_secs: u64,
314        lifetime_days: u32,
315        ca_id: String,
316        csr_der: Vec<u8>,
317        requestor_dn: Option<String>,
318    ) -> Result<StarOrder, StarError> {
319        if lifetime_days == 0 || lifetime_days > self.config.max_lifetime_days {
320            return Err(StarError::IssuanceError(
321                "STAR lifetime is outside configured bounds".into(),
322            ));
323        }
324        // Validate renewal interval against configured bounds.
325        if renewal_interval_secs < self.config.min_renewal_interval_secs
326            || renewal_interval_secs > self.config.max_renewal_interval_secs
327        {
328            warn!(
329                requested = renewal_interval_secs,
330                min = self.config.min_renewal_interval_secs,
331                max = self.config.max_renewal_interval_secs,
332                "STAR renewal interval out of bounds"
333            );
334            return Err(StarError::InvalidInterval {
335                requested: renewal_interval_secs,
336                min: self.config.min_renewal_interval_secs,
337                max: self.config.max_renewal_interval_secs,
338            });
339        }
340
341        // Check resource-exhaustion limit.
342        let active_count = self.active_order_count();
343        if active_count >= self.config.max_active_orders {
344            warn!(
345                active = active_count,
346                limit = self.config.max_active_orders,
347                "STAR order limit reached"
348            );
349            return Err(StarError::MaxOrdersReached {
350                limit: self.config.max_active_orders,
351            });
352        }
353
354        let now = Utc::now();
355        let lifetime_end = now + chrono::Duration::days(i64::from(lifetime_days));
356        let total_lifetime_secs = (lifetime_end - now).num_seconds().max(0) as u64;
357        // Informational estimate includes overlap. Completion is governed by
358        // certificate coverage of lifetime_end, never this estimate.
359        let cadence = ((renewal_interval_secs as f64) * (1.0 - self.config.pre_renewal_factor))
360            .floor()
361            .max(1.0) as u64;
362        let max_renewals = total_lifetime_secs
363            .div_ceil(cadence)
364            .saturating_add(1)
365            .min(u32::MAX as u64) as u32;
366
367        let id = Uuid::new_v4().to_string();
368        let order = StarOrder {
369            id: id.clone(),
370            subject_dn: subject_dn.clone(),
371            key_type,
372            profile,
373            renewal_interval: Duration::from_secs(renewal_interval_secs),
374            lifetime_end,
375            max_renewals,
376            current_renewals: 0,
377            status: StarOrderStatus::Active,
378            requestor_dn,
379            ca_id,
380            csr_der,
381            current_certificate: None,
382            created_at: now,
383            cancelled_at: None,
384        };
385
386        info!(
387            order_id = %id,
388            subject = %subject_dn,
389            interval_secs = renewal_interval_secs,
390            max_renewals = max_renewals,
391            lifetime_end = %lifetime_end,
392            "STAR order created"
393        );
394
395        self.orders.insert(id, order.clone());
396        Ok(order)
397    }
398
399    /// Retrieve the current certificate for a STAR order.
400    ///
401    /// RFC 8739 §3.3: clients GET the STAR certificate URL to obtain
402    /// the latest renewal.
403    pub fn get_current_certificate(&self, star_id: &str) -> Result<StarCertificate, StarError> {
404        let order = self
405            .orders
406            .get(star_id)
407            .ok_or_else(|| StarError::OrderNotFound(star_id.to_owned()))?;
408
409        if order.lifetime_end <= Utc::now() {
410            return Err(StarError::OrderExpired(star_id.to_owned()));
411        }
412
413        match order.status {
414            StarOrderStatus::Cancelled => {
415                return Err(StarError::OrderCancelled(star_id.to_owned()));
416            }
417            StarOrderStatus::Expired => {
418                return Err(StarError::OrderExpired(star_id.to_owned()));
419            }
420            StarOrderStatus::Active | StarOrderStatus::Completed => {}
421        }
422
423        order
424            .current_certificate
425            .clone()
426            .ok_or_else(|| StarError::OrderNotFound(star_id.to_owned()))
427    }
428
429    /// Store a newly renewed certificate in the order.
430    ///
431    /// Increments the renewal counter and transitions the order to
432    /// `Completed` once the certificate covers the complete order lifetime.
433    pub fn store_renewed_certificate(
434        &self,
435        star_id: &str,
436        cert: StarCertificate,
437    ) -> Result<(), StarError> {
438        let mut order = self
439            .orders
440            .get_mut(star_id)
441            .ok_or_else(|| StarError::OrderNotFound(star_id.to_owned()))?;
442
443        if order.status == StarOrderStatus::Cancelled {
444            return Err(StarError::OrderCancelled(star_id.to_owned()));
445        }
446        if order.status == StarOrderStatus::Expired {
447            return Err(StarError::OrderExpired(star_id.to_owned()));
448        }
449
450        order.current_renewals += 1;
451        let renewal_num = order.current_renewals;
452
453        debug!(
454            order_id = %star_id,
455            renewal = renewal_num,
456            serial = %cert.serial_number,
457            not_after = %cert.not_after,
458            "stored renewed STAR certificate"
459        );
460
461        let covers_lifetime = cert.not_after.timestamp() >= order.lifetime_end.timestamp();
462        order.current_certificate = Some(cert);
463
464        if covers_lifetime {
465            info!(
466                order_id = %star_id,
467                renewals = order.current_renewals,
468                "STAR order completed (certificate covers lifetime)"
469            );
470            order.status = StarOrderStatus::Completed;
471        }
472
473        Ok(())
474    }
475
476    /// Cancel a STAR order.
477    ///
478    /// RFC 8739 §3.1.2: the subscriber or IdO may cancel an active order.
479    /// After cancellation, no further certificates are issued.
480    pub fn cancel_order(&self, star_id: &str) -> Result<(), StarError> {
481        let mut order = self
482            .orders
483            .get_mut(star_id)
484            .ok_or_else(|| StarError::OrderNotFound(star_id.to_owned()))?;
485
486        if order.status != StarOrderStatus::Active {
487            warn!(
488                order_id = %star_id,
489                status = order.status.as_str(),
490                "cannot cancel non-active STAR order"
491            );
492            // Idempotent: cancelling an already-cancelled order is fine.
493            if order.status == StarOrderStatus::Cancelled {
494                return Ok(());
495            }
496        }
497
498        info!(order_id = %star_id, "STAR order cancelled");
499        order.status = StarOrderStatus::Cancelled;
500        order.cancelled_at = Some(Utc::now());
501        Ok(())
502    }
503
504    /// Remove orders whose `lifetime_end` has passed.
505    ///
506    /// Should be called periodically (e.g., from a background task) to
507    /// reclaim memory.  Orders past their lifetime are marked `Expired`
508    /// first, then removed entirely.
509    ///
510    /// Returns the number of orders that were cleaned up.
511    pub fn cleanup_expired(&self) -> usize {
512        let now = Utc::now();
513        let mut expired_ids = Vec::new();
514
515        for entry in self.orders.iter() {
516            if entry.lifetime_end <= now {
517                expired_ids.push(entry.id.clone());
518            }
519        }
520
521        for id in &expired_ids {
522            // Mark expired before removal so any concurrent reader sees the
523            // terminal state rather than a vanished order.
524            if let Some(mut order) = self.orders.get_mut(id)
525                && order.status == StarOrderStatus::Active
526            {
527                order.status = StarOrderStatus::Expired;
528            }
529            self.orders.remove(id);
530        }
531
532        let count = expired_ids.len();
533        if count > 0 {
534            info!(count, "cleaned up expired STAR orders");
535        }
536        count
537    }
538
539    /// Count of currently active (not cancelled/completed/expired) orders.
540    pub fn active_order_count(&self) -> usize {
541        self.orders
542            .iter()
543            .filter(|e| e.status == StarOrderStatus::Active)
544            .count()
545    }
546
547    /// Return order IDs that need a renewal certificate issued.
548    ///
549    /// An order needs renewal when:
550    /// 1. Its status is `Active`.
551    /// 2. It has not completed coverage of the order lifetime.
552    /// 3. The current certificate's expiry minus the pre-renewal window
553    ///    is in the past (or no certificate has been issued yet).
554    ///
555    /// The pre-renewal window is `renewal_interval * pre_renewal_factor`.
556    /// For example, with a 24-hour interval and factor 0.5, renewal
557    /// triggers when 12 hours remain on the current certificate.
558    pub fn orders_needing_renewal(&self) -> Vec<String> {
559        self.orders_needing_renewal_at(Utc::now())
560    }
561
562    fn orders_needing_renewal_at(&self, now: DateTime<Utc>) -> Vec<String> {
563        let factor = self.config.pre_renewal_factor;
564        let mut needs_renewal = Vec::new();
565
566        for entry in self.orders.iter() {
567            let order = entry.value();
568
569            if order.status != StarOrderStatus::Active || order.lifetime_end <= now {
570                continue;
571            }
572
573            let should_renew = match &order.current_certificate {
574                None => {
575                    // No certificate issued yet — renew immediately.
576                    true
577                }
578                Some(cert) => {
579                    // Renew when the remaining validity drops below the
580                    // pre-renewal threshold.
581                    let interval_secs = order.renewal_interval.as_secs() as f64;
582                    let pre_renewal_secs = (interval_secs * factor) as i64;
583                    let renewal_deadline =
584                        cert.not_after - chrono::Duration::seconds(pre_renewal_secs);
585                    now >= renewal_deadline
586                }
587            };
588
589            if should_renew {
590                needs_renewal.push(order.id.clone());
591            }
592        }
593
594        debug!(
595            count = needs_renewal.len(),
596            total_active = self.active_order_count(),
597            "scanned orders needing renewal"
598        );
599
600        needs_renewal
601    }
602
603    /// Retrieve a clone of a STAR order by ID.
604    ///
605    /// Returns `None` if the order does not exist.
606    pub fn get_order(&self, star_id: &str) -> Option<StarOrder> {
607        self.orders.get(star_id).map(|entry| entry.clone())
608    }
609}
610
611/// Serde helper for `std::time::Duration` via `humantime`.
612///
613/// Serializes durations as human-readable strings (e.g., "24h", "7d")
614/// and deserializes them back.  Used for the `renewal_interval` field
615/// in `StarOrder`.
616mod humantime_serde {
617    use serde::{self, Deserialize, Deserializer, Serializer};
618    use std::time::Duration;
619
620    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
621    where
622        S: Serializer,
623    {
624        serializer.serialize_u64(duration.as_secs())
625    }
626
627    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
628    where
629        D: Deserializer<'de>,
630    {
631        let secs = u64::deserialize(deserializer)?;
632        Ok(Duration::from_secs(secs))
633    }
634}
635
636#[cfg(test)]
637mod review13_tests {
638    use super::*;
639
640    #[test]
641    fn overlapping_renewals_cover_the_entire_order_lifetime() {
642        for factor in [0.1, 0.5, 0.9] {
643            let manager = StarManager::new(crate::config::StarConfig {
644                pre_renewal_factor: factor,
645                ..Default::default()
646            });
647            let order = manager
648                .create_order(
649                    "synthetic".into(),
650                    "rsa".into(),
651                    "{}".into(),
652                    3600,
653                    1,
654                    "default".into(),
655                    vec![],
656                    None,
657                )
658                .unwrap();
659            let mut start = order.created_at;
660            let mut issued = 0;
661            loop {
662                let end = (start + chrono::Duration::hours(1)).min(order.lifetime_end);
663                issued += 1;
664                manager
665                    .store_renewed_certificate(
666                        &order.id,
667                        StarCertificate {
668                            certificate_der: vec![],
669                            serial_number: issued.to_string(),
670                            not_before: start,
671                            not_after: end,
672                            renewal_number: issued,
673                            star_order_id: order.id.clone(),
674                        },
675                    )
676                    .unwrap();
677                if end >= order.lifetime_end {
678                    assert_eq!(
679                        manager.get_order(&order.id).unwrap().status,
680                        StarOrderStatus::Completed
681                    );
682                    break;
683                }
684                start = end - chrono::Duration::seconds((3600.0 * factor) as i64);
685                assert_eq!(
686                    manager.orders_needing_renewal_at(start),
687                    vec![order.id.clone()]
688                );
689                assert!(issued < 300, "renewal horizon must terminate");
690            }
691            assert!(
692                issued > 24,
693                "overlap requires more than 24 hourly certificates"
694            );
695            assert_eq!(
696                manager
697                    .get_current_certificate(&order.id)
698                    .unwrap()
699                    .not_after,
700                order.lifetime_end
701            );
702        }
703    }
704}