Skip to content

Commit d4daea6

Browse files
renewal_info() yields retry-after
1 parent 1e68826 commit d4daea6

File tree

3 files changed

+23
-7
lines changed

3 files changed

+23
-7
lines changed

src/account.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::sync::Arc;
2+
#[cfg(feature = "time")]
3+
use std::time::{Duration, SystemTime};
24

35
use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
46
use http::header::LOCATION;
@@ -12,7 +14,7 @@ use serde::{Deserialize, Serialize};
1214

1315
#[cfg(feature = "hyper-rustls")]
1416
use crate::DefaultClient;
15-
use crate::order::Order;
17+
use crate::order::{Order, retry_after};
1618
use crate::types::{
1719
AccountCredentials, AuthorizationStatus, Empty, Header, JoseJson, Jwk, KeyOrKeyId, NewAccount,
1820
NewAccountPayload, NewOrder, OrderState, Problem, ProfileMeta, RevocationRequest, Signer,
@@ -143,16 +145,18 @@ impl Account {
143145
/// uniformly random point between the window start/end should be selected and used to
144146
/// schedule a renewal in the future.
145147
///
148+
/// The `Duration` is a hint from the ACME server when to again fetch the renewal window.
149+
/// See <https://www.rfc-editor.org/rfc/rfc9773.html#section-4.3.2> for details.
150+
///
146151
/// This is only supported by some ACME servers. If the server does not support this feature,
147152
/// this method will return `Error::Unsupported`.
148153
///
149-
/// See <https://www.rfc-editor.org/rfc/rfc9773.html#section-4.2-4> for more
150-
/// information.
154+
/// See <https://www.rfc-editor.org/rfc/rfc9773.html#section-4.2-4> for more information.
151155
#[cfg(feature = "time")]
152156
pub async fn renewal_info(
153157
&self,
154158
certificate_id: &CertificateIdentifier<'_>,
155-
) -> Result<RenewalInfo, Error> {
159+
) -> Result<(RenewalInfo, Duration), Error> {
156160
let renewal_info_url = match self.inner.client.directory.renewal_info.as_deref() {
157161
Some(url) => url,
158162
None => return Err(Error::Unsupported("ACME renewal information (ARI)")),
@@ -166,7 +170,16 @@ impl Account {
166170
.body(Full::default())?;
167171

168172
let rsp = self.inner.client.http.request(request).await?;
169-
Problem::check::<RenewalInfo>(rsp).await
173+
174+
let Some(retry_after) = retry_after(&rsp) else {
175+
return Err(Error::Str("missing Retry-After header"));
176+
};
177+
178+
let delay = retry_after
179+
.duration_since(SystemTime::now())
180+
.unwrap_or(Duration::ZERO);
181+
182+
Ok((Problem::check::<RenewalInfo>(rsp).await?, delay))
170183
}
171184

172185
/// Update the account's authentication key

src/order.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ impl RetryState {
622622
///
623623
/// Retry-After = HTTP-date / delay-seconds
624624
/// delay-seconds = 1*DIGIT
625-
fn retry_after(rsp: &BytesResponse) -> Option<SystemTime> {
625+
pub(crate) fn retry_after(rsp: &BytesResponse) -> Option<SystemTime> {
626626
let value = rsp.parts.headers.get(RETRY_AFTER)?.to_str().ok()?.trim();
627627
if value.is_empty() {
628628
return None;

tests/pebble.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,10 @@ async fn replacement_order() -> Result<(), Box<dyn StdError>> {
229229
.expect("failed to fetch renewal window");
230230

231231
// Since we revoked the initial certificate, the window start should be in the past.
232-
assert!(renewal_info.suggested_window.start < OffsetDateTime::now_utc());
232+
assert!(renewal_info.0.suggested_window.start < OffsetDateTime::now_utc());
233+
234+
// The next iteration to update the window should be in the future.
235+
assert!(renewal_info.1 > Duration::ZERO);
233236

234237
// So, let's go ahead and issue a replacement certificate.
235238
env.test::<Http01>(&NewOrder::new(&dns_identifiers(names)).replaces(initial_cert_id))

0 commit comments

Comments
 (0)