safing-portmaster/firewall/inspection/tls/verify/ocsp.go
2018-11-27 16:39:06 +01:00

137 lines
3 KiB
Go

package verify
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
"golang.org/x/crypto/ocsp"
"github.com/Safing/portbase/crypto/hash"
"github.com/Safing/portbase/log"
)
var (
ocspOpts = ocsp.RequestOptions{
Hash: crypto.SHA1,
}
dupOcspReqMap = make(map[string]*sync.Mutex)
dupOcspReqLock sync.Mutex
)
func UpdateOCSP(rCert *Cert, cert, ca *x509.Certificate, caID string) (*Cert, error) {
if rCert.NextOCSPUpdate > time.Now().Unix() {
return rCert, nil
}
var err error
// get CA if necessary
if ca == nil {
ca, err = GetOrFetchIssuer(cert)
if err != nil {
return rCert, err
}
}
if caID == "" {
caID = hash.Sum(ca.RawSubjectPublicKeyInfo, hash.SHA2_256).Safe64()
}
// dedup requests
dupOcspReqLock.Lock()
mutex, requestActive := dupOcspReqMap[rCert.FmtKey()]
if !requestActive {
mutex = new(sync.Mutex)
mutex.Lock()
dupOcspReqMap[rCert.FmtKey()] = mutex
dupOcspReqLock.Unlock()
} else {
dupOcspReqLock.Unlock()
log.Tracef("waiting for duplicate OCSP request for %s to complete", rCert.FmtKey())
mutex.Lock()
// only wait until duplicate request is finished, then return
mutex.Unlock()
// refetch rCert
rCert, err = GetRevokedCert(caID, cert.SerialNumber)
if err != nil {
return nil, fmt.Errorf("failed to refetch rCert %s: %s", rCert.FmtKey(), err)
}
return rCert, nil
}
defer func() {
dupOcspReqLock.Lock()
delete(dupOcspReqMap, rCert.FmtKey())
dupOcspReqLock.Unlock()
mutex.Unlock()
}()
// create request
ocspRequest, err := ocsp.CreateRequest(cert, ca, &ocspOpts)
if err != nil {
return rCert, err
}
// fetch
var resp *ocsp.Response
for _, url := range cert.OCSPServer {
resp, err = fetchOCSP(url, ocspRequest, cert, ca)
if err != nil {
continue
}
rCert.NextOCSPUpdate = resp.NextUpdate.Unix()
if rCert.NextOCSPUpdate > rCert.Expires {
rCert.Expires = rCert.NextOCSPUpdate
}
rCert.RevokedWithOCSP = resp.Status != ocsp.Good
rCert.OCSPFailed = false
rCert.Save()
return rCert, nil
}
rCert.NextOCSPUpdate = time.Now().Add(120 * time.Second).Unix()
rCert.OCSPFailed = true
rCert.Save()
return rCert, fmt.Errorf("all OCSP servers failed, last error: %s", err)
}
// fetchOCSP attempts to request an OCSP response from the
// server. The error only indicates a failure to *fetch* the
// certificate, and *does not* mean the certificate is valid.
func fetchOCSP(server string, req []byte, cert, ca *x509.Certificate) (*ocsp.Response, error) {
var resp *http.Response
var err error
if len(req) > 256 {
buf := bytes.NewBuffer(req)
resp, err = http.Post(server, "application/ocsp-request", buf)
} else {
reqURL := server + "/" + base64.StdEncoding.EncodeToString(req)
resp, err = http.Get(reqURL)
}
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("failed to retrieve OSCP")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
resp.Body.Close()
return ocsp.ParseResponseForCert(body, cert, ca)
}