Skip to content

Commit d52948c

Browse files
authored
add overriding of ARI response (#501)
Fixes #486 This moves the GetCertificateBySerial call earlier, which means that call needs to succeed even for revoked certificates. So this also follows up on #252 by keeping revoked certs in the primary certificatesByID map (while still adding them to the revokedCertificatesByID map).
1 parent 39dbb64 commit d52948c

File tree

3 files changed

+79
-9
lines changed

3 files changed

+79
-9
lines changed

core/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ type Certificate struct {
149149
DER []byte
150150
IssuerChains [][]*Certificate
151151
AccountID string
152+
// When non-empty, this is the ARI response sent for this certificate.
153+
ARIResponse string
152154
}
153155

154156
func (c Certificate) PEM() []byte {

db/memorystore.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ func (m *MemoryStore) RevokeCertificate(cert *core.RevokedCertificate) {
413413
m.Lock()
414414
defer m.Unlock()
415415
m.revokedCertificatesByID[cert.Certificate.ID] = cert
416-
delete(m.certificatesByID, cert.Certificate.ID)
417416
}
418417

419418
/*
@@ -549,3 +548,19 @@ func (m *MemoryStore) IsDomainBlocked(name string) bool {
549548

550549
return false
551550
}
551+
552+
// SetARIResponse looks up a certificate by serial number and sets its ARI response field
553+
func (m *MemoryStore) SetARIResponse(serial *big.Int, ariResponse string) error {
554+
m.Lock()
555+
defer m.Unlock()
556+
557+
for _, cert := range m.certificatesByID {
558+
if cert.Cert.SerialNumber.Cmp(serial) == 0 {
559+
cert.ARIResponse = ariResponse
560+
return nil
561+
}
562+
}
563+
564+
// Certificate not found
565+
return fmt.Errorf("certificate with serial number %s not found", serial.String())
566+
}

wfe/wfe.go

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,18 @@ const (
5757
// Draft or likely-to-change paths
5858
renewalInfoPath = "/draft-ietf-acme-ari-03/renewalInfo/"
5959

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

6973
// How long do pending authorizations last before expiring?
7074
pendingAuthzExpire = time.Hour
@@ -542,6 +546,9 @@ func (wfe *WebFrontEndImpl) ManagementHandler() http.Handler {
542546
wfe.HandleManagementFunc(m, intermediateCertPath, wfe.handleCert(wfe.ca.GetIntermediateCert, intermediateCertPath))
543547
wfe.HandleManagementFunc(m, intermediateKeyPath, wfe.handleKey(wfe.ca.GetIntermediateKey, intermediateKeyPath))
544548
wfe.HandleManagementFunc(m, certStatusBySerial, wfe.handleCertStatusBySerial)
549+
550+
// POST only handlers
551+
wfe.HandleFunc(m, setRenewalInfoPath, wfe.SetRenewalInfo, http.MethodPost)
545552
return m
546553
}
547554

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

1906-
renewalInfo, err := wfe.determineARIWindow(certID)
1913+
cert := wfe.db.GetCertificateBySerial(certID.SerialNumber)
1914+
if cert == nil {
1915+
wfe.sendError(acme.NotFoundProblem("failed to retrieve existing certificate serial"), response)
1916+
return
1917+
}
1918+
1919+
if cert.ARIResponse != "" {
1920+
_, _ = response.Write([]byte(cert.ARIResponse))
1921+
return
1922+
}
1923+
1924+
renewalInfo, err := wfe.determineARIWindow(certID, cert)
19071925
if err != nil {
19081926
wfe.sendError(acme.InternalErrorProblem(fmt.Sprintf("Error determining renewal window: %s", err)), response)
19091927
return
@@ -1917,7 +1935,47 @@ func (wfe *WebFrontEndImpl) RenewalInfo(_ context.Context, response http.Respons
19171935
}
19181936
}
19191937

1920-
func (wfe *WebFrontEndImpl) determineARIWindow(id *core.CertID) (*core.RenewalInfo, error) {
1938+
// SetRenewalInfo overrides the default ARI response for a certificate.
1939+
func (wfe *WebFrontEndImpl) SetRenewalInfo(_ context.Context, response http.ResponseWriter, request *http.Request) {
1940+
body, err := io.ReadAll(request.Body)
1941+
if err != nil {
1942+
wfe.sendError(acme.InternalErrorProblem("Error reading body"), response)
1943+
}
1944+
1945+
var reqJSON struct {
1946+
Certificate string // in PEM form
1947+
ARIResponse string // can be anything, even malformed JSON, so that users can test client response to malformed data
1948+
}
1949+
err = json.Unmarshal(body, &reqJSON)
1950+
if err != nil {
1951+
wfe.sendError(acme.MalformedProblem("Error unmarshaling request body"), response)
1952+
return
1953+
}
1954+
1955+
// Decode and parse the PEM certificate
1956+
block, _ := pem.Decode([]byte(reqJSON.Certificate))
1957+
if block == nil || block.Type != "CERTIFICATE" {
1958+
wfe.sendError(acme.MalformedProblem("Error decoding certificate PEM"), response)
1959+
return
1960+
}
1961+
1962+
cert, err := x509.ParseCertificate(block.Bytes)
1963+
if err != nil {
1964+
wfe.sendError(acme.MalformedProblem("Error parsing certificate"), response)
1965+
return
1966+
}
1967+
1968+
// Look up certificate by serial number and update response
1969+
err = wfe.db.SetARIResponse(cert.SerialNumber, reqJSON.ARIResponse)
1970+
if err != nil {
1971+
wfe.sendError(acme.NotFoundProblem(err.Error()), response)
1972+
return
1973+
}
1974+
1975+
response.WriteHeader(http.StatusOK)
1976+
}
1977+
1978+
func (wfe *WebFrontEndImpl) determineARIWindow(id *core.CertID, cert *core.Certificate) (*core.RenewalInfo, error) {
19211979
if id == nil {
19221980
return nil, errors.New("CertID was nil")
19231981
}
@@ -1928,11 +1986,6 @@ func (wfe *WebFrontEndImpl) determineARIWindow(id *core.CertID) (*core.RenewalIn
19281986
return core.RenewalInfoImmediate(time.Now().In(time.UTC)), nil
19291987
}
19301988

1931-
cert := wfe.db.GetCertificateBySerial(id.SerialNumber)
1932-
if cert == nil {
1933-
return nil, errors.New("failed to retrieve existing certificate serial")
1934-
}
1935-
19361989
return core.RenewalInfoSimple(cert.Cert.NotBefore, cert.Cert.NotAfter), nil
19371990
}
19381991

0 commit comments

Comments
 (0)