diff --git a/core/objects.go b/core/objects.go index ad09b01611b..443e1a693da 100644 --- a/core/objects.go +++ b/core/objects.go @@ -456,6 +456,7 @@ func (window SuggestedWindow) IsWithin(now time.Time) bool { // endpoint specified in draft-aaron-ari. type RenewalInfo struct { SuggestedWindow SuggestedWindow `json:"suggestedWindow"` + ExplanationURL string `json:"explanationURL,omitempty"` } // RenewalInfoSimple constructs a `RenewalInfo` object and suggested window @@ -478,13 +479,15 @@ func RenewalInfoSimple(issued time.Time, expires time.Time) RenewalInfo { // window in the past. Per the draft-ietf-acme-ari-01 spec, clients should // attempt to renew immediately if the suggested window is in the past. The // passed `now` is assumed to be a timestamp representing the current moment in -// time. -func RenewalInfoImmediate(now time.Time) RenewalInfo { +// time. The `explanationURL` is an optional URL that the subscriber can use to +// learn more about why the renewal is suggested. +func RenewalInfoImmediate(now time.Time, explanationURL string) RenewalInfo { oneHourAgo := now.Add(-1 * time.Hour) return RenewalInfo{ SuggestedWindow: SuggestedWindow{ Start: oneHourAgo, End: oneHourAgo.Add(time.Minute * 30), }, + ExplanationURL: explanationURL, } } diff --git a/wfe2/wfe.go b/wfe2/wfe.go index 11e3fcf58cb..22526275ed9 100644 --- a/wfe2/wfe.go +++ b/wfe2/wfe.go @@ -2118,8 +2118,15 @@ func (wfe *WebFrontEndImpl) determineARIWindow(ctx context.Context, serial strin } if len(result.Incidents) > 0 { + // Find the earliest incident. + var earliest *sapb.Incident + for _, incident := range result.Incidents { + if earliest == nil || incident.RenewBy.AsTime().Before(earliest.RenewBy.AsTime()) { + earliest = incident + } + } // The existing cert is impacted by an incident, renew immediately. - return core.RenewalInfoImmediate(wfe.clk.Now()), nil + return core.RenewalInfoImmediate(wfe.clk.Now(), earliest.Url), nil } // Check if the serial is revoked. @@ -2130,7 +2137,7 @@ func (wfe *WebFrontEndImpl) determineARIWindow(ctx context.Context, serial strin if status.Status == string(core.OCSPStatusRevoked) { // The existing certificate is revoked, renew immediately. - return core.RenewalInfoImmediate(wfe.clk.Now()), nil + return core.RenewalInfoImmediate(wfe.clk.Now(), ""), nil } // It's okay to use GetCertificate (vs trying to get a precertificate), diff --git a/wfe2/wfe_test.go b/wfe2/wfe_test.go index 7f2afaa5aa6..19c7cc8ac11 100644 --- a/wfe2/wfe_test.go +++ b/wfe2/wfe_test.go @@ -1920,7 +1920,7 @@ func newMockSAWithIncident(sa sapb.StorageAuthorityReadOnlyClient, serial []stri { Id: 0, SerialTable: "incident_foo", - Url: agreementURL, + Url: "http://big.bad/incident", RenewBy: nil, Enabled: true, }, @@ -3785,6 +3785,8 @@ func TestIncidentARI(t *testing.T) { test.AssertEquals(t, ri.SuggestedWindow.End.After(ri.SuggestedWindow.Start), true) // The end of the window should also be in the past. test.AssertEquals(t, ri.SuggestedWindow.End.Before(wfe.clk.Now()), true) + // The explanationURL should be set. + test.AssertEquals(t, ri.ExplanationURL, "http://big.bad/incident") } func TestOldTLSInbound(t *testing.T) {