Skip to content

return logical and compliant ARI windows for expiring certs#484

Merged
aarongable merged 8 commits into
letsencrypt:mainfrom
jvanasco:main
Feb 21, 2025
Merged

return logical and compliant ARI windows for expiring certs#484
aarongable merged 8 commits into
letsencrypt:mainfrom
jvanasco:main

Conversation

@jvanasco

@jvanasco jvanasco commented Feb 15, 2025

Copy link
Copy Markdown
Contributor

While testing ARI functionality with extremely short lived certs, Pebble returned illogical data - such as the windowEnd appearing in the future for already expired certs, or after the cert's notAfter.

This PR adjusts RenewalInfoSimple to do the following:

  • if the cert has expired, just return RenewalInfoImmediate
  • if the cert will expire before the windowEnd, set the windowEnd to the cert's notAfter
  • cleanup: RFC requires the windowStart to be before windowEnd, so remove a second from the windowStart if needed.

I also made the RenewalInfoImmediate window change from -60:-30 minutes to -60:-59.59 minutes; the sole reason for this change is that it is visually easier to see the change (and not need to do mental math) and recognize this is a RenewalInfoImmediate from logs.

There are likely better ways to accomplish this, and I may be overlooking some elements from the RFC.

With these changes, Pebble can be configured for certificate validity periods as low as 5-10seconds, enabling quick testing of real (unmocked) flows such as:

  • issue cert
  • query ari
  • wait for expiry or query ari
  • renew with replaces

Also: The linting errors existed on creating my fork; there may be some linting errors in this PR - but there is a library issue.

Comment thread core/types.go Outdated
Comment thread core/types.go Outdated
Comment on lines +271 to +285
if expires.Before(now) {
// If the cert has expired, return `RenewalInfoImmediate` instead.
return RenewalInfoImmediate(now)
}

if expires.Before(windowEnd) {
// If the windowEnd is after the cert's expiry, adjust it to the window end.
windowStart = expires.Add(-1 * time.Second)
windowEnd = expires
}

if !windowEnd.After(windowStart) {
// Ensure windowStart is before windowEnd
windowEnd = expires.Add(-1 * time.Second)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lint error here: these lines all have incorrect indentation.

More importantly, I think there's a much simpler version of this logic that accomplishes the same goal:

  • If windowEnd is after expires, make it equal to expires
  • If windowStart is before issued, make it equal to issued

True, this doesn't explicitly protect against a certificate which has a zero-second validity period, but I'm not worried about that case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did the first element and changed initial spacing. I don't know why my editor was auto-expanding tabs here. I added the windowStart change to issued as well.

I left the last element, however, ensuring windowStart is BEFORE windowEnd - and included a reference to the RFC. The RFC draft explicitly requires windowStart to precede windowEnd, and the payload is invalid otherwise.

Striving to simplify logic has caveats. I prioritized ensuring an ARI compliant payload over edge cases of future notBefore values or zero second validity.

@jvanasco jvanasco left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some requested changes were made; some alternatives were presented.

Comment thread core/types.go Outdated
Comment on lines +271 to +285
if expires.Before(now) {
// If the cert has expired, return `RenewalInfoImmediate` instead.
return RenewalInfoImmediate(now)
}

if expires.Before(windowEnd) {
// If the windowEnd is after the cert's expiry, adjust it to the window end.
windowStart = expires.Add(-1 * time.Second)
windowEnd = expires
}

if !windowEnd.After(windowStart) {
// Ensure windowStart is before windowEnd
windowEnd = expires.Add(-1 * time.Second)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did the first element and changed initial spacing. I don't know why my editor was auto-expanding tabs here. I added the windowStart change to issued as well.

I left the last element, however, ensuring windowStart is BEFORE windowEnd - and included a reference to the RFC. The RFC draft explicitly requires windowStart to precede windowEnd, and the payload is invalid otherwise.

Striving to simplify logic has caveats. I prioritized ensuring an ARI compliant payload over edge cases of future notBefore values or zero second validity.

Comment thread core/types.go Outdated
}

// Ensure RenewalWindow is not after the expiry
if windowEnd.After(expires){

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still have formatting issues here, and below. You should be running gofmt automatically.

Suggested change
if windowEnd.After(expires){
if windowEnd.After(expires) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! It's not performing on directories, just specific files. Fixed.

Comment thread core/types.go Outdated
Comment on lines +270 to +273
if expires.Before(now) {
// If the cert has expired, return `RenewalInfoImmediate` instead.
return RenewalInfoImmediate(now)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant: if we're clamping windowEnd to expires, then an expired cert will always be given a window that is wholly in the past. No need to special case it here. This is why I suggested the removal of this stanza in my previous review.

If you want to keep it anyway, move it above the computation of the ideal window, since none of that computation is necessary for this stanza.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that makes sense. I just removed it.

Comment thread core/types.go Outdated
Comment on lines +279 to +280
// Ensure correct start for future issueds
if windowStart.After(issued){

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't suggest this to handle issued timestamps that are in the future. I suggested this to handle the case where the certificate has a validity period of less than 48 hours, so that subtracting 24 hours from a point in the middle of the validity period results in a windowStart that precedes the issued timestamp.

Also, you've reversed the logic from what I suggested. This should be if windowStart.Before(issued) {.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was trying to do this in one window while debugging another upstream project in the cloud.

@jvanasco

Copy link
Copy Markdown
Contributor Author

This should have all the requested changes and formatting. Apologies. I've also tested it against with a client as I couldn't find any ARI tests here.

@aarongable aarongable merged commit bc21177 into letsencrypt:main Feb 21, 2025
kwatson added a commit to kwatson/letsencrypt-pebble that referenced this pull request Jun 9, 2025
* 'main' of https://github.com/letsencrypt/pebble: (35 commits)
  add overriding of ARI response (letsencrypt#501)
  wfe: fix a race in `orderForDisplay` (letsencrypt#500)
  Bump golang.org/x/ dependencies (letsencrypt#499)
  currectly triggers BadSignatureAlgorithmProblem at JWS parse time (letsencrypt#492)
  use newer validation subdomain for dns-account-01 (fix CI eggsampler/acme error) (letsencrypt#498)
  Orders don't have a "deactivated" status. (letsencrypt#301)
  Update golangci-lint (letsencrypt#488)
  build(deps): bump github.com/go-jose/go-jose/v4 from 4.0.4 to 4.0.5 (letsencrypt#487)
  Truncate ARI timestamps to millisecond resolution (letsencrypt#485)
  return logical and compliant ARI windows for expiring certs (letsencrypt#484)
  Update dependencies (letsencrypt#481)
  docs: rm mention of subproblems being unimpl'd (letsencrypt#479)
  Fix(NOISSUE): Fix docker compose file example in README.md (letsencrypt#475)
  Add support for ACME Profiles (letsencrypt#473)
  Simplify KU, EKU, and SKID fields of issued certs (letsencrypt#472)
  Update golangci-lint to 1.60.2 (letsencrypt#474)
  Update /x/net for compatibility with go1.23 (letsencrypt#470)
  Reject extra command line args and fix README invocation (letsencrypt#467)
  Document exposing API and management ports when not using docker-compose.yaml (letsencrypt#465)
  Implement latest draft-ietf-acme-ari spec (letsencrypt#461)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants