From d1deb7f93066e15e39ff6b9ca0d341e8213981cb Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 8 Feb 2024 14:10:48 +0100 Subject: [PATCH] Add `Expires` header to CRL response --- api/api.go | 2 +- api/api_test.go | 6 +++--- api/crl.go | 9 ++++++--- authority/tls.go | 17 +++++++++++++++-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/api/api.go b/api/api.go index 7cf44a11..5d96cc45 100644 --- a/api/api.go +++ b/api/api.go @@ -54,7 +54,7 @@ type Authority interface { GetRoots() ([]*x509.Certificate, error) GetFederation() ([]*x509.Certificate, error) Version() authority.Version - GetCertificateRevocationList() ([]byte, error) + GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) } // mustAuthority will be replaced on unit tests. diff --git a/api/api_test.go b/api/api_test.go index b3c01816..a62b34e8 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -200,7 +200,7 @@ type mockAuthority struct { getEncryptedKey func(kid string) (string, error) getRoots func() ([]*x509.Certificate, error) getFederation func() ([]*x509.Certificate, error) - getCRL func() ([]byte, error) + getCRL func() (*authority.CertificateRevocationListInfo, error) signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) @@ -214,12 +214,12 @@ type mockAuthority struct { version func() authority.Version } -func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) { +func (m *mockAuthority) GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) { if m.getCRL != nil { return m.getCRL() } - return m.ret1.([]byte), m.err + return m.ret1.(*authority.CertificateRevocationListInfo), m.err } // TODO: remove once Authorize is deprecated. diff --git a/api/crl.go b/api/crl.go index 6386f34a..7f12c6f8 100644 --- a/api/crl.go +++ b/api/crl.go @@ -3,18 +3,21 @@ package api import ( "encoding/pem" "net/http" + "time" "github.com/smallstep/certificates/api/render" ) // CRL is an HTTP handler that returns the current CRL in DER or PEM format func CRL(w http.ResponseWriter, r *http.Request) { - crlBytes, err := mustAuthority(r.Context()).GetCertificateRevocationList() + crlInfo, err := mustAuthority(r.Context()).GetCertificateRevocationList() if err != nil { render.Error(w, err) return } + w.Header().Add("Expires", crlInfo.ExpiresAt.Format(time.RFC1123)) + _, formatAsPEM := r.URL.Query()["pem"] if formatAsPEM { w.Header().Add("Content-Type", "application/x-pem-file") @@ -22,11 +25,11 @@ func CRL(w http.ResponseWriter, r *http.Request) { _ = pem.Encode(w, &pem.Block{ Type: "X509 CRL", - Bytes: crlBytes, + Bytes: crlInfo.Data, }) } else { w.Header().Add("Content-Type", "application/pkix-crl") w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"") - w.Write(crlBytes) + w.Write(crlInfo.Data) } } diff --git a/authority/tls.go b/authority/tls.go index 0dd6eb54..fa170d44 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -696,9 +696,17 @@ func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateIn return a.db.RevokeSSH(rci) } +// CertificateRevocationListInfo contains a CRL in DER format and associated metadata. +type CertificateRevocationListInfo struct { + Number int64 + ExpiresAt time.Time + Duration time.Duration + Data []byte +} + // GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented // error if the underlying AuthDB does not support CRLs -func (a *Authority) GetCertificateRevocationList() ([]byte, error) { +func (a *Authority) GetCertificateRevocationList() (*CertificateRevocationListInfo, error) { if !a.config.CRL.IsEnabled() { return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList") } @@ -713,7 +721,12 @@ func (a *Authority) GetCertificateRevocationList() ([]byte, error) { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetCertificateRevocationList") } - return crlInfo.DER, nil + return &CertificateRevocationListInfo{ + Number: crlInfo.Number, + ExpiresAt: crlInfo.ExpiresAt, + Duration: crlInfo.Duration, + Data: crlInfo.DER, + }, nil } // GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the