mihomo/component/ca/config.go
2025-09-13 14:21:33 +08:00

144 lines
3.2 KiB
Go

package ca
import (
"crypto/tls"
"crypto/x509"
_ "embed"
"errors"
"fmt"
"os"
"strconv"
"sync"
"github.com/metacubex/mihomo/common/once"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp"
)
var globalCertPool *x509.CertPool
var mutex sync.RWMutex
var errNotMatch = errors.New("certificate fingerprints do not match")
//go:embed ca-certificates.crt
var _CaCertificates []byte
var DisableEmbedCa, _ = strconv.ParseBool(os.Getenv("DISABLE_EMBED_CA"))
var DisableSystemCa, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_CA"))
func AddCertificate(certificate string) error {
mutex.Lock()
defer mutex.Unlock()
if certificate == "" {
return fmt.Errorf("certificate is empty")
}
if globalCertPool == nil {
initializeCertPool()
}
if globalCertPool.AppendCertsFromPEM([]byte(certificate)) {
return nil
} else if cert, err := x509.ParseCertificate([]byte(certificate)); err == nil {
globalCertPool.AddCert(cert)
return nil
} else {
return fmt.Errorf("add certificate failed")
}
}
func initializeCertPool() {
var err error
if DisableSystemCa {
globalCertPool = x509.NewCertPool()
} else {
globalCertPool, err = x509.SystemCertPool()
if err != nil {
globalCertPool = x509.NewCertPool()
}
}
if !DisableEmbedCa {
globalCertPool.AppendCertsFromPEM(_CaCertificates)
}
}
func ResetCertificate() {
mutex.Lock()
defer mutex.Unlock()
initializeCertPool()
}
func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) {
var certificate []byte
var err error
if len(customCA) > 0 {
path := C.Path.Resolve(customCA)
if !C.Path.IsSafePath(path) {
return nil, C.Path.ErrNotSafePath(path)
}
certificate, err = os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load ca error: %w", err)
}
} else if customCAString != "" {
certificate = []byte(customCAString)
}
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate)
}
return certPool, nil
} else {
mutex.Lock()
defer mutex.Unlock()
if globalCertPool == nil {
initializeCertPool()
}
return globalCertPool, nil
}
}
type Option struct {
TLSConfig *tls.Config
Fingerprint string
CustomCA string
CustomCAString string
ZeroTrust bool
}
func GetTLSConfig(opt Option) (tlsConfig *tls.Config, err error) {
tlsConfig = opt.TLSConfig
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
tlsConfig.Time = ntp.Now
if opt.ZeroTrust {
tlsConfig.RootCAs = zeroTrustCertPool()
} else {
tlsConfig.RootCAs, err = GetCertPool(opt.CustomCA, opt.CustomCAString)
if err != nil {
return nil, err
}
}
if len(opt.Fingerprint) > 0 {
tlsConfig.VerifyPeerCertificate, err = NewFingerprintVerifier(opt.Fingerprint)
if err != nil {
return nil, err
}
tlsConfig.InsecureSkipVerify = true
}
return tlsConfig, nil
}
var zeroTrustCertPool = once.OnceValue(func() *x509.CertPool {
if len(_CaCertificates) != 0 { // always using embed cert first
zeroTrustCertPool := x509.NewCertPool()
if zeroTrustCertPool.AppendCertsFromPEM(_CaCertificates) {
return zeroTrustCertPool
}
}
return nil // fallback to system pool
})