Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ type Certificate struct {
DER []byte
IssuerChains [][]*Certificate
AccountID string
// When non-empty, this is the ARI response sent for this certificate.
ARIResponse string
}

func (c Certificate) PEM() []byte {
Expand Down
16 changes: 16 additions & 0 deletions db/memorystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,3 +549,19 @@ func (m *MemoryStore) IsDomainBlocked(name string) bool {

return false
}

// SetARIResponse looks up a certificate by serial number and sets its ARI response field
func (m *MemoryStore) SetARIResponse(serial *big.Int, ariResponse string) error {
m.Lock()
defer m.Unlock()

for _, cert := range m.certificatesByID {
if cert.Cert.SerialNumber.Cmp(serial) == 0 {
cert.ARIResponse = ariResponse
return nil
}
}

// Certificate not found
return fmt.Errorf("certificate with serial number %s not found", serial.String())
}
69 changes: 61 additions & 8 deletions wfe/wfe.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,18 @@ const (
// Draft or likely-to-change paths
renewalInfoPath = "/draft-ietf-acme-ari-03/renewalInfo/"

// Theses entrypoints are not a part of the standard ACME endpoints,
// These entrypoints are not a part of the standard ACME endpoints,
// and are exposed by Pebble as an integration test tool. We export
// RootCertPath so that the pebble binary can reference it.
RootCertPath = "/roots/"
rootKeyPath = "/root-keys/"
intermediateCertPath = "/intermediates/"
intermediateKeyPath = "/intermediate-keys/"
certStatusBySerial = "/cert-status-by-serial/"
// Post certificate PEM and desired literal response for renewal info
// (the renewal info response is not validated so may be intentionally
// malformed).
setRenewalInfoPath = "/set-renewal-info/"

// How long do pending authorizations last before expiring?
pendingAuthzExpire = time.Hour
Expand Down Expand Up @@ -542,6 +546,9 @@ func (wfe *WebFrontEndImpl) ManagementHandler() http.Handler {
wfe.HandleManagementFunc(m, intermediateCertPath, wfe.handleCert(wfe.ca.GetIntermediateCert, intermediateCertPath))
wfe.HandleManagementFunc(m, intermediateKeyPath, wfe.handleKey(wfe.ca.GetIntermediateKey, intermediateKeyPath))
wfe.HandleManagementFunc(m, certStatusBySerial, wfe.handleCertStatusBySerial)

// POST only handlers
wfe.HandleFunc(m, setRenewalInfoPath, wfe.SetRenewalInfo, http.MethodPost)
return m
}

Expand Down Expand Up @@ -1903,7 +1910,18 @@ func (wfe *WebFrontEndImpl) RenewalInfo(_ context.Context, response http.Respons
return
}

renewalInfo, err := wfe.determineARIWindow(certID)
cert := wfe.db.GetCertificateBySerial(certID.SerialNumber)
if cert == nil {
wfe.sendError(acme.NotFoundProblem("failed to retrieve existing certificate serial"), response)
return
}

if cert.ARIResponse != "" {
_, _ = response.Write([]byte(cert.ARIResponse))
return
}

renewalInfo, err := wfe.determineARIWindow(certID, cert)
if err != nil {
wfe.sendError(acme.InternalErrorProblem(fmt.Sprintf("Error determining renewal window: %s", err)), response)
return
Expand All @@ -1917,7 +1935,47 @@ func (wfe *WebFrontEndImpl) RenewalInfo(_ context.Context, response http.Respons
}
}

func (wfe *WebFrontEndImpl) determineARIWindow(id *core.CertID) (*core.RenewalInfo, error) {
// SetRenewalInfo overrides the default ARI response for a certificate.
func (wfe *WebFrontEndImpl) SetRenewalInfo(_ context.Context, response http.ResponseWriter, request *http.Request) {
body, err := io.ReadAll(request.Body)
if err != nil {
wfe.sendError(acme.InternalErrorProblem("Error reading body"), response)
}

var reqJSON struct {
Certificate string // in PEM form
ARIResponse string // can be anything, even malformed JSON, so that users can test client response to malformed data
}
err = json.Unmarshal(body, &reqJSON)
if err != nil {
wfe.sendError(acme.MalformedProblem("Error unmarshaling request body"), response)
return
}

// Decode and parse the PEM certificate
block, _ := pem.Decode([]byte(reqJSON.Certificate))
if block == nil || block.Type != "CERTIFICATE" {
wfe.sendError(acme.MalformedProblem("Error decoding certificate PEM"), response)
return
}

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
wfe.sendError(acme.MalformedProblem("Error parsing certificate"), response)
return
}

// Look up certificate by serial number and update response
err = wfe.db.SetARIResponse(cert.SerialNumber, reqJSON.ARIResponse)
if err != nil {
wfe.sendError(acme.NotFoundProblem(err.Error()), response)
return
}

response.WriteHeader(http.StatusOK)
}

func (wfe *WebFrontEndImpl) determineARIWindow(id *core.CertID, cert *core.Certificate) (*core.RenewalInfo, error) {
if id == nil {
return nil, errors.New("CertID was nil")
}
Expand All @@ -1928,11 +1986,6 @@ func (wfe *WebFrontEndImpl) determineARIWindow(id *core.CertID) (*core.RenewalIn
return core.RenewalInfoImmediate(time.Now().In(time.UTC)), nil
}

cert := wfe.db.GetCertificateBySerial(id.SerialNumber)
if cert == nil {
return nil, errors.New("failed to retrieve existing certificate serial")
}

return core.RenewalInfoSimple(cert.Cert.NotBefore, cert.Cert.NotAfter), nil
}

Expand Down
Loading