Initial commit after restructure

This commit is contained in:
Daniel
2018-08-13 14:14:27 +02:00
commit bdeddc41f9
177 changed files with 26108 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
from: https://github.com/cloudflare/cfssl
commit: https://github.com/cloudflare/cfssl/commit/851e4b77bb0e13a97e7d836a2f069d17ed5478c1
license: BSD 2-clause "Simplified" License
original files imported:
- revoke/revoke.go sha256:95e3b14c1dc3d99a37f3da914c0137df80145e82295a817acf7382f9ce4bcc7e
- helpers/helpers.go sha256:f2642d23b3f48a9dd7d0a72d3538f95aeadbc652df30fe2f52e4e9207e256e07
from: https://github.com/golang/go
commit: https://github.com/golang/go/commit/220e0e0f7383a79fda0ba61bd1bf2076f5f74d72
license: BSD 3-clause "New" or "Revised" License
original files imported:
- src/crypto/x509/verify.go sha256:8d31581662e28a35af65166a98e8dadd60de750a83bfa7e25efad2500832b8ed

View File

@@ -0,0 +1,266 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package verify
import (
"bytes"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"strings"
"github.com/cloudflare/cfssl/crypto/pkcs7"
datastore "github.com/ipfs/go-datastore"
"github.com/Safing/safing-core/crypto/hash"
"github.com/Safing/safing-core/database"
)
// Cert saves a certificate.
type Cert struct {
database.Base
cert *x509.Certificate
Raw []byte
RevokedWithCRL bool `*:",omitempty"`
RevokedWithOneCRL bool `*:",omitempty"`
RevokedWithCRLSet bool `*:",omitempty"`
RevokedWithOCSP bool `*:",omitempty"`
OCSPFailed bool
NextOCSPUpdate int64
LastSeen int64
Expires int64
}
var certModel *Cert // only use this as parameter for database.EnsureModel-like functions
func init() {
database.RegisterModel(certModel, func() database.Model { return new(Cert) })
}
// ensureParsed ensures that the certificate is parsed and available in the cert attribute
func (m *Cert) ensureParsed() error {
if m.cert != nil {
if len(m.Raw) == 0 {
return errors.New("certificate data not saved")
}
var err error
m.cert, err = x509.ParseCertificate(m.Raw)
if err != nil {
return fmt.Errorf("could not parse certificate: %s", err)
}
}
return nil
}
// GetCertificate returns the underlying x509.Certificate
func (m *Cert) GetCertificate() (*x509.Certificate, error) {
if err := m.ensureParsed(); err != nil {
return nil, err
}
return m.cert, nil
}
// IsRevoked returns if the certificate has been revoked.
func (m *Cert) IsRevoked(hardFail bool) bool {
if m.RevokedWithCRL || m.RevokedWithOneCRL || m.RevokedWithCRLSet || m.RevokedWithOCSP {
return true
}
if hardFail && m.OCSPFailed {
return true
}
return false
}
// RevocationStatus returns the status of the certificate in form of a string to be appended to something like "The certificate is ".
func (m *Cert) RevocationStatus(hardFail bool) string {
if !m.IsRevoked(hardFail) {
return "not revoked"
}
var revokedBy []string
if m.RevokedWithCRL {
revokedBy = append(revokedBy, "CRL")
}
if m.RevokedWithOneCRL {
revokedBy = append(revokedBy, "OneCRL")
}
if m.RevokedWithCRLSet {
revokedBy = append(revokedBy, "CRLSet")
}
if m.RevokedWithOCSP {
revokedBy = append(revokedBy, "OCSP")
}
if len(revokedBy) > 0 {
return fmt.Sprintf("revoked by %s", strings.Join(revokedBy, ", "))
}
return fmt.Sprintf("possibly revoked (OCSP failed) - hardfailing as requested.")
}
// CreateWithUrl saves Cert in the default namespace using the certificate URL as the key.
func (m *Cert) CreateWithUrl(url string) error {
return m.CreateObject(&database.CertCache, fmt.Sprintf("U%x", url), m)
}
// CreateWithSPKI saves Cert in the default namespace using the certificate SPKI as the key.
func (m *Cert) CreateWithSPKI(spki []byte) error {
return m.CreateObject(&database.CertCache, fmt.Sprintf("K%x", hash.Sum(spki, hash.SHA2_256).Safe64()), m)
}
// CreateRevokedCert creates a new Cert in its CA's namespace with its Serial Number
func (m *Cert) CreateRevokedCert(caID string, serialNumber *big.Int) error {
namespace := database.CARevocationInfoCache.ChildString(fmt.Sprintf("CARevocationInfo:%s", caID))
return m.CreateInNamespace(&namespace, fmt.Sprintf("S%x", serialNumber))
}
// CreateInNamespace saves Cert with the provided name in the provided namespace.
func (m *Cert) CreateInNamespace(namespace *datastore.Key, name string) error {
return m.CreateObject(namespace, name, m)
}
// Save saves Cert.
func (m *Cert) Save() error {
return m.SaveObject(m)
}
// GetCertWithURL fetches Cert from the default namespace using the certificate URL as the key.
func GetCertWithURL(url string) (*Cert, error) {
return GetCertFromNamespace(&database.CertCache, fmt.Sprintf("U%x", url))
}
// GetCertWithSPKI fetches Cert from the default namespace using the certificate SPKI as the key.
func GetCertWithSPKI(spki []byte) (*Cert, error) {
return GetCertFromNamespace(&database.CertCache, fmt.Sprintf("K%x", hash.Sum(spki, hash.SHA2_256).Safe64()))
}
// GetCertFromNamespace gets Cert with the provided name from the provided namespace.
func GetCertFromNamespace(namespace *datastore.Key, name string) (*Cert, error) {
object, err := database.GetAndEnsureModel(namespace, name, certModel)
if err != nil {
return nil, err
}
model, ok := object.(*Cert)
if !ok {
return nil, database.NewMismatchError(object, certModel)
}
return model, nil
}
// GetRevokedCert gets Cert from its CA's namespace with its Serial Number
func GetRevokedCert(caID string, serialNumber *big.Int) (*Cert, error) {
namespace := database.CARevocationInfoCache.ChildString(fmt.Sprintf("CARevocationInfo:%s", caID))
return GetCertFromNamespace(&namespace, fmt.Sprintf("S%x", serialNumber))
}
func GetOrFetchCert(urls []string) (*x509.Certificate, error) {
// TODO: handle root CAs
for _, url := range urls {
cert, err := GetCertWithURL(url)
if err != nil {
continue
}
certificate, err := x509.ParseCertificate(cert.Raw)
if err == nil {
return certificate, nil
}
}
return ImportCert(urls)
}
func GetOrFetchIssuer(cert *x509.Certificate) (*x509.Certificate, error) {
if len(cert.IssuingCertificateURL) == 0 {
return nil, errors.New("no issuing certificate URLs")
}
return GetOrFetchCert(cert.IssuingCertificateURL)
}
func ImportCert(urls []string) (*x509.Certificate, error) {
var err error
for _, url := range urls {
var resp *http.Response
resp, err = http.Get(url)
if err != nil {
err = fmt.Errorf("could not fetch certificate from %s: %s", url, err)
continue
}
var data []byte
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("could not read certificate from %s: %s", url, err)
continue
}
resp.Body.Close()
var cert *x509.Certificate
cert, err = x509.ParseCertificate(data)
if err != nil {
cert, err = ParsePEMCertificate(data)
}
if err != nil {
err = fmt.Errorf("could not parse certificate from %s: %s", url, err)
continue
}
// verify
_, err = cert.Verify(x509.VerifyOptions{})
// chains, err = cert.Verify(x509.VerifyOptions{})
if err != nil {
err = fmt.Errorf("failed to verify certificate from %s: %s", url, err)
}
// check revocation
// save
return cert, nil
}
return nil, fmt.Errorf("no or only failing Certs available, last error: %s", err)
}
// ParsePEMCertificate parses and returns a PEM-encoded certificate,
// can handle PEM encoded PKCS #7 structures.
func ParsePEMCertificate(certPEM []byte) (*x509.Certificate, error) {
block, _ := pem.Decode(bytes.TrimSpace(certPEM))
if block == nil {
return nil, errors.New("decoding failed")
}
// if len(rest) > 0 {
// return nil, errors.New("decoding failed: the PEM data should contain only one object")
// }
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
return cert, nil
}
pkcs7data, err := pkcs7.ParsePKCS7(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parsing failed: %s", err)
}
if pkcs7data.ContentInfo != "SignedData" {
return nil, errors.New("parsing failed: only PKCS #7 Signed Data Content Info supported for certificate parsing")
}
certs := pkcs7data.Content.SignedData.Certificates
if certs == nil {
return nil, errors.New("PKCS #7 structure contains no certificates")
}
// if len(certs) > 1 {
// return nil, errors.New("decoding failed: the PKCS7 object in the PEM data should contain only one certificate"))
// }
return certs[0], nil
}

View File

@@ -0,0 +1,70 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package verify
import (
"crypto/x509"
"fmt"
"testing"
)
// func TestCertFetching(t *testing.T) {
//
// cert, err := GetOrFetchCert([]string{"http://cert.int-x3.letsencrypt.org/"})
// if err != nil {
// t.Errorf("failed to GetOrFetchCert: %s", err)
// }
// fmt.Printf("%v\n", cert)
//
// GetOrFetchCert([]string{"http://cert.int-x3.letsencrypt.org/"})
// if err != nil {
// t.Errorf("failed to GetOrFetchCert: %s", err)
// }
// fmt.Printf("%v\n", cert)
//
// }
func TestMissingChain(t *testing.T) {
certPEM := []byte(`-----BEGIN CERTIFICATE-----
MIIEuzCCA6OgAwIBAgIQRMpyC8ARigQMjp7ywd0HOzANBgkqhkiG9w0BAQsFADBD
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMR0wGwYDVQQDExR0
aGF3dGUgU0hBMjU2IFNTTCBDQTAeFw0xNjAxMTkwMDAwMDBaFw0xODAxMTgyMzU5
NTlaMHQxCzAJBgNVBAYTAkRFMRYwFAYDVQQIDA1OaWVkZXJzYWNoc2VuMREwDwYD
VQQHDAhIYW5ub3ZlcjEjMCEGA1UECgwaSGVpc2UgTWVkaWVuIEdtYkggJiBDby4g
S0cxFTATBgNVBAMMDHd3dy5oZWlzZS5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL+S5DqFzKXKpDuPKxSkhG/2ap4kFxBXv0u7gmAE30Cya16RASHt
oSZCjHPE2yyGhLaLTjnf6kC4AgJ4eQtStPb0Oc7NodEbeFYzn6ei1OyXmYD4V7kL
HYwjGIE3TZch4scb6peuNexYotHLB032KL/csScfdtDSpYg6ZEJ7kYI2MSqP4ogo
BbakfgVjCdTwi4PWfmRO080t6MUEfJbfqojxcVxO70femsvmteU5/7IaXXNCnnoF
KWl/G2WgmD8eBu2+HY9ojRrG5DrbKcz6XcNJCz88khQ+x/1EPsEEWjHiADfvl3HH
uWmFf0BBoB3V3oL+v4i1zu2ffZH6UdlekxMCAwEAAaOCAXgwggF0MCEGA1UdEQQa
MBiCDHd3dy5oZWlzZS5kZYIIaGVpc2UuZGUwCQYDVR0TBAIwADBuBgNVHSAEZzBl
MGMGBmeBDAECAjBZMCYGCCsGAQUFBwIBFhpodHRwczovL3d3dy50aGF3dGUuY29t
L2NwczAvBggrBgEFBQcCAjAjDCFodHRwczovL3d3dy50aGF3dGUuY29tL3JlcG9z
aXRvcnkwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFCuaNa4BGDgw4XB6BeAR
dqPOvZAUMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6Ly90Zy5zeW1jYi5jb20vdGcu
Y3JsMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBXBggrBgEFBQcBAQRL
MEkwHwYIKwYBBQUHMAGGE2h0dHA6Ly90Zy5zeW1jZC5jb20wJgYIKwYBBQUHMAKG
Gmh0dHA6Ly90Zy5zeW1jYi5jb20vdGcuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQAy
dryRQkVsQIxrhyGlAdGVR9ygOwtJBUq0najtb0+/HNEysN0QZjguNKDlzmHm1pAI
gYR5hTlQH93XqL4d1+UVRL61hiKJja0EEkHnDEtde9eRsyvVfBHRrUF/qV6ar3yG
0NHQdlZSIGpztKap4Za6RKxwgZid+LC1k67a4envcSPxHnREtR23mDIpe6u0NoQA
VhUDXwbAUHO2A/6dKvhQVlPsZES56hg0uPrA6ODCxHQeRId2mn+/HY2VnDkwfJRZ
NwVkZSvy4/Mi4cZYkkcW3Z9gePYDiNGe1wBr4/H3ffK63ek6W7Uy3ju2TpMiOyIB
gQDY9b+bJOhhWfRQhONh
-----END CERTIFICATE-----`)
cert, err := ParsePEMCertificate(certPEM)
if err != nil {
t.Errorf("failed to parse cert: %s", err)
return
}
_, err = cert.Verify(x509.VerifyOptions{})
// chains, err = cert.Verify(x509.VerifyOptions{})
if err != nil {
err = fmt.Errorf("failed to verify certificate: %s", err)
}
}

View File

@@ -0,0 +1,254 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package verify
import (
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"sort"
"sync"
"time"
datastore "github.com/ipfs/go-datastore"
"github.com/Safing/safing-core/crypto/hash"
"github.com/Safing/safing-core/database"
"github.com/Safing/safing-core/log"
)
// CARevocationInfo saves Information on revokation of Certificates of a Certificate Authority.
type CARevocationInfo struct {
database.Base
CRLDistributionPoints []string
OCSPServers []string
CertificateURLs []string
LastCRLUpdate int64
NextCRLUpdate int64
cert *x509.Certificate
Raw []byte
Expires int64
}
var (
caRevocationInfoModel *CARevocationInfo // only use this as parameter for database.EnsureModel-like functions
dupCrlReqMap = make(map[string]*sync.Mutex)
dupCrlReqLock sync.Mutex
)
func init() {
database.RegisterModel(caRevocationInfoModel, func() database.Model { return new(CARevocationInfo) })
}
// Create saves CARevocationInfo with the provided name in the default namespace.
func (m *CARevocationInfo) Create(name string) error {
return m.CreateObject(&database.CARevocationInfoCache, name, m)
}
// CreateInNamespace saves CARevocationInfo with the provided name in the provided namespace.
func (m *CARevocationInfo) CreateInNamespace(namespace *datastore.Key, name string) error {
return m.CreateObject(namespace, name, m)
}
// Save saves CARevocationInfo.
func (m *CARevocationInfo) Save() error {
return m.SaveObject(m)
}
func (m *CARevocationInfo) GetRevokedCert(serialNumber *big.Int) (*Cert, error) {
return GetCertFromNamespace(m.GetKey(), fmt.Sprintf("S%x", serialNumber))
}
func (m *CARevocationInfo) CreateRevokedCert(cert *Cert, serialNumber *big.Int) error {
return cert.CreateInNamespace(m.GetKey(), fmt.Sprintf("S%x", serialNumber))
}
// GetCARevocationInfo fetches CARevocationInfo with the provided name from the default namespace.
func GetCARevocationInfo(name string) (*CARevocationInfo, error) {
return GetCARevocationInfoFromNamespace(&database.CARevocationInfoCache, name)
}
// GetCARevocationInfoFromNamespace fetches CARevocationInfo with the provided name from the provided namespace.
func GetCARevocationInfoFromNamespace(namespace *datastore.Key, name string) (*CARevocationInfo, error) {
object, err := database.GetAndEnsureModel(namespace, name, caRevocationInfoModel)
if err != nil {
return nil, err
}
model, ok := object.(*CARevocationInfo)
if !ok {
return nil, database.NewMismatchError(object, caRevocationInfoModel)
}
return model, nil
}
// ensureCertParsed ensures that the certificate is parsed and available in the cert attribute
func (m *CARevocationInfo) ensureCertParsed() error {
if m.cert != nil {
if len(m.Raw) == 0 {
return errors.New("certificate data not saved")
}
var err error
m.cert, err = x509.ParseCertificate(m.Raw)
if err != nil {
return fmt.Errorf("could not parse certificate: %s", err)
}
}
return nil
}
// UpdateCRLDistributionPoints updates the CRL Distribution Points with new urls
func (m *CARevocationInfo) UpdateCRLDistributionPoints(newCRLDistributionPoints []string) {
var found bool
sort.Reverse(sort.StringSlice(newCRLDistributionPoints))
for _, newEntry := range newCRLDistributionPoints {
found = false
for _, entry := range m.CRLDistributionPoints {
if newEntry == entry {
found = true
break
}
}
if !found {
m.CRLDistributionPoints = append([]string{newEntry}, m.CRLDistributionPoints...)
}
}
}
// UpdateCRL fetches and imports the CRL belonging to a CA, if expired.
func UpdateCRL(caInfo *CARevocationInfo, ca *x509.Certificate, caID string) error {
var err error
// ensure we have caInfo
if caInfo == nil {
if ca == nil && caID == "" {
return errors.New("verify: UpdateCRL must be called with at least one of: caInfo *CARevocationInfo, ca *x509.Certificate, caID string")
}
if caID == "" {
caID = hash.Sum(ca.RawSubjectPublicKeyInfo, hash.SHA2_256).Safe64()
}
caInfo, err = GetCARevocationInfo(caID)
if err != nil {
return fmt.Errorf("verify: could not get CARevocationInfo for caID %s: %s", caID, err)
}
}
// don't update if we still have a valid record
if caInfo.NextCRLUpdate > time.Now().Unix() {
return nil
}
// dedup requests
dupCrlReqLock.Lock()
mutex, requestActive := dupCrlReqMap[caID]
if !requestActive {
mutex = new(sync.Mutex)
mutex.Lock()
dupCrlReqMap[caID] = mutex
dupCrlReqLock.Unlock()
} else {
dupCrlReqLock.Unlock()
log.Tracef("verify: waiting for duplicate CRL import for CA %s to complete", caID)
mutex.Lock()
// only wait until duplicate request is finished, then return
mutex.Unlock()
return nil
}
defer func() {
dupCrlReqLock.Lock()
delete(dupCrlReqMap, caID)
dupCrlReqLock.Unlock()
mutex.Unlock()
}()
// fetch and import CRL
for _, url := range caInfo.CRLDistributionPoints {
// fetch CRL
crl, err := fetchCRL(url)
if err != nil {
log.Warningf("verify: failed to import CRL from %s: %s", url, err)
continue
}
// check CRL signature
// TODO: how is revokation checked when verifying CRL signature?
err = ca.CheckCRLSignature(crl)
if err != nil {
log.Warningf("verify: failed to import CRL from %s: %s", url, err)
continue
}
log.Infof("verify: importing verified CRL for CA %s from %s", caID, url)
// save to DB
newExpiry := crl.TBSCertList.NextUpdate.Add(720 * time.Hour).Unix()
caInfo.LastCRLUpdate = time.Now().Unix()
caInfo.NextCRLUpdate = newExpiry
for _, entry := range crl.TBSCertList.RevokedCertificates {
// log.Tracef("verify: importing %d", entry.SerialNumber)
// fetch or create rCert
rCert, err := caInfo.GetRevokedCert(entry.SerialNumber)
if err != nil {
rCert = new(Cert)
}
// update expiry
rCert.RevokedWithCRL = true
if newExpiry > rCert.Expires {
rCert.Expires = newExpiry
}
// save
if rCert.GetKey() == nil {
caInfo.CreateRevokedCert(rCert, entry.SerialNumber)
} else {
rCert.Save()
}
}
log.Tracef("verify: import from %s finished.", url)
caInfo.Save()
return nil
}
return fmt.Errorf("verify: no or only failing CRLs available for CA %s", caID)
}
func fetchCRL(url string) (*pkix.CertificateList, error) {
client := &http.Client{
Timeout: 1 * time.Minute,
}
resp, err := client.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to retrieve CRL: %s", err)
} else if resp.StatusCode >= 300 {
return nil, fmt.Errorf("failed to retrieve CRL: non-200 status code: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read CRL: %s", err)
}
resp.Body.Close()
crl, err := x509.ParseCRL(body)
if err != nil {
return nil, fmt.Errorf("failed to parse CRL: %s", err)
}
return crl, nil
}

View File

@@ -0,0 +1,139 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
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/safing-core/crypto/hash"
"github.com/Safing/safing-core/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)
}

View File

@@ -0,0 +1,215 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package verify
import (
"crypto/x509"
"errors"
"fmt"
"time"
"github.com/Safing/safing-core/configuration"
"github.com/Safing/safing-core/crypto/hash"
"github.com/Safing/safing-core/database"
)
// useful references:
// https://github.com/cloudflare/cfssl/blob/master/revoke/revoke.go
// Mozilla OneCRL
// https://blog.mozilla.org/security/2015/03/03/revoking-intermediate-certificates-introducing-onecrl/
// https://wiki.mozilla.org/CA:ImprovingRevocation
// Google CRLSet
// https://security.stackexchange.com/questions/55811/how-are-crlsets-more-secure
// https://www.imperialviolet.org/2012/02/05/crlsets.html
// RE: https://www.grc.com/revocation/crlsets.htm
// RE: RE: https://www.imperialviolet.org/2014/04/29/revocationagain.html
var (
config = configuration.Get()
)
// FullCheckBytes does a full certificate check, certificates are provided as raw bytes.
// It parses the raw certificates and calls FullCheck.
func FullCheckBytes(name string, certBytes [][]byte) (bool, error) {
certs := make([]*x509.Certificate, len(certBytes))
for key, bytes := range certBytes {
cert, err := x509.ParseCertificate(bytes)
if err != nil {
return false, errors.New("verify: failed to parse certificate: " + err.Error())
}
certs[key] = cert
}
return FullCheck(name, certs)
}
// FullCheck does a full certificate check.
// Calls CheckSignatures, CheckRecovation and CheckCertificateTransparency(TODO).
func FullCheck(name string, chain []*x509.Certificate) (bool, error) {
verifiedChain, err := CheckSignatures(name, chain)
if err != nil {
return false, fmt.Errorf("verify: certificate invalid: %s", err)
}
return CheckRecovation(verifiedChain)
}
func CheckSignatures(name string, chain []*x509.Certificate) ([]*x509.Certificate, error) {
if len(chain) == 0 {
return nil, errors.New("no certificates supplied")
}
opts := x509.VerifyOptions{
// Roots: c.config.RootCAs,
// CurrentTime: time.Now(),
DNSName: name,
Intermediates: x509.NewCertPool(),
}
for i, cert := range chain {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
verifiedChains, err := chain[0].Verify(opts)
if err != nil {
return nil, err
}
// TODO: further process all verified chains (revocation / CT)
return verifiedChains[0], nil
}
// TODO
// func CheckCertificateTransparency(chain []*x509.Certificate, securityLevel int8) {
// }
func CheckKnownRevocation(verifiedChain []*x509.Certificate) (bool, error) {
for i := 0; i < len(verifiedChain)-1; i++ {
caID := hash.Sum(verifiedChain[i+1].RawSubjectPublicKeyInfo, hash.SHA2_256).Safe64()
rCert, err := GetRevokedCert(caID, verifiedChain[i].SerialNumber)
if err != nil {
if err != database.ErrNotFound {
return true, nil
}
return true, fmt.Errorf("verify: failed to get rCert from database: %s", err)
}
if rCert.IsRevoked(false) {
return false, nil
}
if rCert.OCSPFailed {
return true, errors.New("verify: OCSP failed in the past")
}
}
return true, nil
}
func CheckRecovation(verifiedChain []*x509.Certificate) (bool, error) {
for i := 0; i < len(verifiedChain)-1; i++ {
ok, err := checkCertRevocation(verifiedChain[i], verifiedChain[i+1])
if !ok {
return ok, err
}
}
return true, nil
}
func checkCertRevocation(cert, ca *x509.Certificate) (bool, error) {
// proper use?
if cert == nil {
return false, errors.New("verify: no certificate supplied")
}
// check if recocation is supported
if len(cert.CRLDistributionPoints) == 0 && len(cert.OCSPServer) == 0 {
return true, fmt.Errorf("verify: certificate does not support OCSP or CRL.")
}
var err error
if ca == nil {
ca, err = GetOrFetchIssuer(cert)
if err != nil {
return true, err
}
}
caID := hash.Sum(ca.RawSubjectPublicKeyInfo, hash.SHA2_256).Safe64()
// check cert
rCert, err := GetRevokedCert(caID, cert.SerialNumber)
if err != nil {
if err != database.ErrNotFound {
return true, fmt.Errorf("verify: failed to get Cert: %s", err)
}
rCert = &Cert{
cert: cert,
// Raw: cert.Raw,
LastSeen: time.Now().Unix(),
Expires: time.Now().Add(7 * 24 * time.Hour).Unix(),
}
rCert.CreateRevokedCert(caID, cert.SerialNumber)
} else if rCert.IsRevoked(false) {
return false, fmt.Errorf("verify: certificate is %s", rCert.RevocationStatus(false))
}
// update OCSP
// TODO: check OCSP stapling data
// TODO: is MustStaple already handled by golang? If not, handle it!
if len(cert.OCSPServer) > 0 {
if rCert == nil {
rCert = new(Cert)
}
rCert, err = UpdateOCSP(rCert, cert, ca, caID)
if err == nil && !rCert.OCSPFailed {
// update CRL later
go checkCRL(rCert, cert, ca, caID, false, true)
if rCert.IsRevoked(false) {
return false, fmt.Errorf("verify: certificate is %s", rCert.RevocationStatus(false))
}
return true, nil
}
}
// update CRL
return checkCRL(rCert, cert, ca, caID, false, false)
}
func checkCRL(rCert *Cert, cert, ca *x509.Certificate, caID string, hardFail bool, postpone bool) (bool, error) {
if postpone {
// postpone a bit to finish packet processing
// TODO: use microtask management
time.Sleep(1 * time.Second)
}
caInfo, err := GetCARevocationInfo(caID)
if err != nil {
if err != database.ErrNotFound {
return true, fmt.Errorf("verify: failed to get CARevocationInfo: %s", err)
}
caInfo = &CARevocationInfo{
CRLDistributionPoints: cert.CRLDistributionPoints,
OCSPServers: cert.OCSPServer,
CertificateURLs: cert.IssuingCertificateURL,
Raw: ca.Raw,
Expires: time.Now().Add(30 * 24 * time.Hour).Unix(),
}
caInfo.Create(caID)
}
UpdateCRL(caInfo, ca, caID)
rCert, err = GetRevokedCert(caID, cert.SerialNumber)
if err != nil {
return true, fmt.Errorf("verify: failed to get Cert: %s", err)
}
if rCert.IsRevoked(false) {
return false, fmt.Errorf("verify: certificate is %s", rCert.RevocationStatus(false))
}
return true, nil
}