Add Expires header to CRL response

This commit is contained in:
Herman Slatman 2024-02-08 14:10:48 +01:00
parent 67246925d2
commit d1deb7f930
No known key found for this signature in database
GPG Key ID: F4D8A44EA0A75A4F
4 changed files with 25 additions and 9 deletions

View File

@ -54,7 +54,7 @@ type Authority interface {
GetRoots() ([]*x509.Certificate, error) GetRoots() ([]*x509.Certificate, error)
GetFederation() ([]*x509.Certificate, error) GetFederation() ([]*x509.Certificate, error)
Version() authority.Version Version() authority.Version
GetCertificateRevocationList() ([]byte, error) GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error)
} }
// mustAuthority will be replaced on unit tests. // mustAuthority will be replaced on unit tests.

View File

@ -200,7 +200,7 @@ type mockAuthority struct {
getEncryptedKey func(kid string) (string, error) getEncryptedKey func(kid string) (string, error)
getRoots func() ([]*x509.Certificate, error) getRoots func() ([]*x509.Certificate, error)
getFederation 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) 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) 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) renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error)
@ -214,12 +214,12 @@ type mockAuthority struct {
version func() authority.Version version func() authority.Version
} }
func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) { func (m *mockAuthority) GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) {
if m.getCRL != nil { if m.getCRL != nil {
return m.getCRL() return m.getCRL()
} }
return m.ret1.([]byte), m.err return m.ret1.(*authority.CertificateRevocationListInfo), m.err
} }
// TODO: remove once Authorize is deprecated. // TODO: remove once Authorize is deprecated.

View File

@ -3,18 +3,21 @@ package api
import ( import (
"encoding/pem" "encoding/pem"
"net/http" "net/http"
"time"
"github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/api/render"
) )
// CRL is an HTTP handler that returns the current CRL in DER or PEM format // CRL is an HTTP handler that returns the current CRL in DER or PEM format
func CRL(w http.ResponseWriter, r *http.Request) { func CRL(w http.ResponseWriter, r *http.Request) {
crlBytes, err := mustAuthority(r.Context()).GetCertificateRevocationList() crlInfo, err := mustAuthority(r.Context()).GetCertificateRevocationList()
if err != nil { if err != nil {
render.Error(w, err) render.Error(w, err)
return return
} }
w.Header().Add("Expires", crlInfo.ExpiresAt.Format(time.RFC1123))
_, formatAsPEM := r.URL.Query()["pem"] _, formatAsPEM := r.URL.Query()["pem"]
if formatAsPEM { if formatAsPEM {
w.Header().Add("Content-Type", "application/x-pem-file") 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{ _ = pem.Encode(w, &pem.Block{
Type: "X509 CRL", Type: "X509 CRL",
Bytes: crlBytes, Bytes: crlInfo.Data,
}) })
} else { } else {
w.Header().Add("Content-Type", "application/pkix-crl") w.Header().Add("Content-Type", "application/pkix-crl")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"") w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"")
w.Write(crlBytes) w.Write(crlInfo.Data)
} }
} }

View File

@ -696,9 +696,17 @@ func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateIn
return a.db.RevokeSSH(rci) 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 // GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented
// error if the underlying AuthDB does not support CRLs // 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() { if !a.config.CRL.IsEnabled() {
return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList") 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 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 // GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the