Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
17 changes: 16 additions & 1 deletion db/memorystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,6 @@ func (m *MemoryStore) RevokeCertificate(cert *core.RevokedCertificate) {
m.Lock()
defer m.Unlock()
m.revokedCertificatesByID[cert.Certificate.ID] = cert
delete(m.certificatesByID, cert.Certificate.ID)
}

/*
Expand Down Expand Up @@ -549,3 +548,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