Skip to content

Commit

Permalink
Fix certcache to allow self-signed certs.
Browse files Browse the repository at this point in the history
If the cert doesn't have an AIA extension with an OCSP responder URL,
then the cache will just include a fake (invalid) OCSP response in its
cert-chain+cbor. This is sufficient for displaying the SXG in Chrome
with --ignore-certificate-errors-spki-list.
  • Loading branch information
twifkak committed Nov 1, 2019
1 parent 2e5f864 commit 5f7962d
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 15 deletions.
47 changes: 34 additions & 13 deletions packager/certcache/certcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const maxOCSPResponseBytes = 1024 * 1024
// How often to check if OCSP stapling needs updating.
const ocspCheckInterval = 1 * time.Hour

// Sentinel value used to communicate that a returned OCSP was fake, and the caller should not attempt to parse it.
// No, I'm not proud.
var fakeOCSP = []byte("fake ocsp response")

type CertHandler interface {
GetLatestCert() *x509.Certificate
IsHealthy() error
Expand All @@ -65,6 +69,7 @@ type CertCache struct {
// TODO(twifkak): Implement a registry of Updateable instances which can be configured in the toml.
ocspFile Updateable
client http.Client
developmentMode bool

// "Virtual methods", exposed for testing.
// Given a certificate, returns the OCSP responder URL for that cert.
Expand All @@ -74,7 +79,7 @@ type CertCache struct {
}

// Must call Init() on the returned CertCache before you can use it.
func New(certs []*x509.Certificate, ocspCache string) *CertCache {
func New(certs []*x509.Certificate, ocspCache string, developmentMode bool) *CertCache {
return &CertCache{
certName: util.CertName(certs[0]),
certs: certs,
Expand All @@ -90,6 +95,7 @@ func New(certs []*x509.Certificate, ocspCache string) *CertCache {
// you'd have one request, in the backend, and updating them all.
ocspFile: &Chained{first: &InMemory{}, second: &LocalFile{path: ocspCache}},
client: http.Client{Timeout: 60 * time.Second},
developmentMode: developmentMode,
extractOCSPServer: func(cert *x509.Certificate) (string, error) {
if len(cert.OCSPServer) < 1 {
return "", errors.New("Cert missing OCSPServer.")
Expand Down Expand Up @@ -171,15 +177,20 @@ func (this *CertCache) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
util.NewHTTPError(http.StatusInternalServerError, "Error reading OCSP: ", err).LogAndRespond(resp)
return
}
midpoint, err := this.ocspMidpoint(ocsp, this.findIssuer())
if err != nil {
util.NewHTTPError(http.StatusInternalServerError, "Error computing OCSP midpoint: ", err).LogAndRespond(resp)
return
}
// int is large enough to represent 24855 days in seconds.
expiry := int(midpoint.Sub(time.Now()).Seconds())
if expiry < 0 {
expiry = 0
var expiry int
if bytes.Equal(ocsp, fakeOCSP) {
expiry = int(time.Now().Add(3*24*time.Hour).Unix())
} else {
midpoint, err := this.ocspMidpoint(ocsp, this.findIssuer())
if err != nil {
util.NewHTTPError(http.StatusInternalServerError, "Error computing OCSP midpoint: ", err).LogAndRespond(resp)
return
}
// int is large enough to represent 24855 days in seconds.
expiry = int(midpoint.Sub(time.Now()).Seconds())
if expiry < 0 {
expiry = 0
}
}
resp.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(expiry))
resp.Header().Set("X-Content-Type-Options", "nosniff")
Expand Down Expand Up @@ -222,6 +233,9 @@ func (this *CertCache) isHealthy(ocspResp []byte) error {
if issuer == nil {
return errors.New("Cannot find issuer certificate in CertFile.")
}
if bytes.Equal(ocspResp, fakeOCSP) {
return nil
}
resp, err := ocsp.ParseResponseForCert(ocspResp, this.certs[0], issuer)
if err != nil {
return errors.Wrap(err, "Error parsing OCSP response.")
Expand Down Expand Up @@ -285,8 +299,8 @@ func (this *CertCache) maintainOCSP(stop chan struct{}) {
}

// Returns true if OCSP is expired (or near enough).
func (this *CertCache) shouldUpdateOCSP(bytes []byte) bool {
if len(bytes) == 0 {
func (this *CertCache) shouldUpdateOCSP(ocsp []byte) bool {
if len(ocsp) == 0 {
// TODO(twifkak): Use a logging framework with support for debug-only statements.
log.Println("Updating OCSP; none cached yet.")
return true
Expand All @@ -297,8 +311,11 @@ func (this *CertCache) shouldUpdateOCSP(bytes []byte) bool {
// This is a permanent error; do not attempt OCSP update.
return false
}
if bytes.Equal(ocsp, fakeOCSP) {
return false
}
// Compute the midpoint per sleevi #3 (see above).
midpoint, err := this.ocspMidpoint(bytes, issuer)
midpoint, err := this.ocspMidpoint(ocsp, issuer)
if err != nil {
log.Println("Error computing OCSP midpoint:", err)
return true
Expand Down Expand Up @@ -368,6 +385,10 @@ func (this *CertCache) fetchOCSP(orig []byte, ocspUpdateAfter *time.Time) []byte

ocspServer, err := this.extractOCSPServer(this.certs[0])
if err != nil {
if this.developmentMode {
log.Println("Cert lacks OCSP URL; using fake OCSP in development mode.")
return fakeOCSP
}
log.Println("Error extracting OCSP server:", err)
return orig
}
Expand Down
2 changes: 1 addition & 1 deletion packager/certcache/certcache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type CertCacheSuite struct {

func (this *CertCacheSuite) New() (*CertCache, error) {
// TODO(twifkak): Stop the old CertCache's goroutine.
certCache := New(pkgt.B3Certs, filepath.Join(this.tempDir, "ocsp"))
certCache := New(pkgt.B3Certs, filepath.Join(this.tempDir, "ocsp"), false)
certCache.extractOCSPServer = func(*x509.Certificate) (string, error) {
return this.ocspServer.URL, nil
}
Expand Down
2 changes: 1 addition & 1 deletion packager/certloader/certloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func PopulateCertCache(config *util.Config, key crypto.PrivateKey, developmentMo
return nil, errors.Wrapf(err, "checking %s", config.CertFile)
}
}
certCache := certcache.New(certs, config.OCSPCache)
certCache := certcache.New(certs, config.OCSPCache, developmentMode)

return certCache, nil
}
Expand Down

0 comments on commit 5f7962d

Please sign in to comment.