mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
215 lines
5.9 KiB
Go
215 lines
5.9 KiB
Go
// 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
|
|
|
|
}
|