Added cert resolve viewer

- Added certificate resolve viewer on HTTP proxy rule editor
- Exposed SNI options (wip)
- Code optimize
This commit is contained in:
Toby Chui
2025-07-07 14:18:10 +08:00
parent c091b9d1ca
commit 45506c8772
9 changed files with 393 additions and 1488 deletions

View File

@@ -8,6 +8,7 @@ import (
"time"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/utils"
)
/*
@@ -105,3 +106,49 @@ func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain str
return targetEpt.Remove()
}
// GetProxyEndpointById retrieves a proxy endpoint by its ID from the Router's ProxyEndpoints map.
// It returns the ProxyEndpoint if found, or an error if not found.
func (h *Router) GetProxyEndpointById(searchingDomain string, includeAlias bool) (*ProxyEndpoint, error) {
var found *ProxyEndpoint
h.ProxyEndpoints.Range(func(key, value interface{}) bool {
proxy, ok := value.(*ProxyEndpoint)
if ok && (proxy.RootOrMatchingDomain == searchingDomain || (includeAlias && utils.StringInArray(proxy.MatchingDomainAlias, searchingDomain))) {
found = proxy
return false // stop iteration
}
return true // continue iteration
})
if found != nil {
return found, nil
}
return nil, errors.New("proxy rule with given id not found")
}
func (h *Router) GetProxyEndpointByAlias(alias string) (*ProxyEndpoint, error) {
var found *ProxyEndpoint
h.ProxyEndpoints.Range(func(key, value interface{}) bool {
proxy, ok := value.(*ProxyEndpoint)
if !ok {
return true
}
//Also check for wildcard aliases that matches the alias
for _, thisAlias := range proxy.MatchingDomainAlias {
if ok && thisAlias == alias {
found = proxy
return false // stop iteration
} else if ok && strings.HasPrefix(thisAlias, "*") {
//Check if the alias matches a wildcard alias
if strings.HasSuffix(alias, thisAlias[1:]) {
found = proxy
return false // stop iteration
}
}
}
return true // continue iteration
})
if found != nil {
return found, nil
}
return nil, errors.New("proxy rule with given alias not found")
}

View File

@@ -175,7 +175,8 @@ type ProxyEndpoint struct {
Disabled bool //If the rule is disabled
//Inbound TLS/SSL Related
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
TlsOptions *tlscert.HostSpecificTlsBehavior //TLS options for this endpoint, if nil, use global TLS options
//Virtual Directories
VirtualDirectories []*VirtualDirectoryEndpoint

View File

@@ -20,11 +20,21 @@ type CertCache struct {
PriKey string
}
type HostSpecificTlsBehavior struct {
DisableSNI bool //If SNI is enabled for this server name
DisableLegacyCertificateMatching bool //If legacy certificate matching is disabled for this server name
EnableAutoHTTPS bool //If auto HTTPS is enabled for this server name
PreferredCertificate string //Preferred certificate for this server name, if empty, use the first matching certificate
}
type Manager struct {
CertStore string //Path where all the certs are stored
LoadedCerts []*CertCache //A list of loaded certs
Logger *logger.Logger //System wide logger for debug mesage
verbal bool
/* External handlers */
hostSpecificTlsBehavior func(serverName string) (*HostSpecificTlsBehavior, error) // Function to get host specific TLS behavior, if nil, use global TLS options
verbal bool
}
//go:embed localhost.pem localhost.key
@@ -50,10 +60,11 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
}
thisManager := Manager{
CertStore: certStore,
LoadedCerts: []*CertCache{},
verbal: verbal,
Logger: logger,
CertStore: certStore,
LoadedCerts: []*CertCache{},
hostSpecificTlsBehavior: defaultHostSpecificTlsBehavior, //Default to no SNI and no auto HTTPS
verbal: verbal,
Logger: logger,
}
err := thisManager.UpdateLoadedCertList()
@@ -64,6 +75,21 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
return &thisManager, nil
}
// Default host specific TLS behavior
// This is used when no specific TLS behavior is defined for a server name
func GetDefaultHostSpecificTlsBehavior() *HostSpecificTlsBehavior {
return &HostSpecificTlsBehavior{
DisableSNI: false,
DisableLegacyCertificateMatching: false,
EnableAutoHTTPS: false,
PreferredCertificate: "",
}
}
func defaultHostSpecificTlsBehavior(serverName string) (*HostSpecificTlsBehavior, error) {
return GetDefaultHostSpecificTlsBehavior(), nil
}
// Update domain mapping from file
func (m *Manager) UpdateLoadedCertList() error {
//Get a list of certificates from file
@@ -161,24 +187,11 @@ func (m *Manager) ListCerts() ([]string, error) {
// Get a certificate from disk where its certificate matches with the helloinfo
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
//Check if the domain corrisponding cert exists
pubKey := "./tmp/localhost.pem"
priKey := "./tmp/localhost.key"
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".pem")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
//Direct hit
pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".pem")
priKey = filepath.Join(m.CertStore, helloInfo.ServerName+".key")
} else if m.CertMatchExists(helloInfo.ServerName) {
//Use x509
pubKey, priKey = m.GetCertByX509CNHostname(helloInfo.ServerName)
} else {
//Fallback to legacy method of matching certificates
if m.DefaultCertExists() {
//Use default.pem and default.key
pubKey = filepath.Join(m.CertStore, "default.pem")
priKey = filepath.Join(m.CertStore, "default.key")
}
//Look for the certificate by hostname
pubKey, priKey, err := m.GetCertificateByHostname(helloInfo.ServerName)
if err != nil {
m.Logger.PrintAndLog("tls-router", "Failed to get certificate for "+helloInfo.ServerName, err)
return nil, err
}
//Load the cert and serve it
@@ -190,6 +203,51 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
return &cer, nil
}
// GetCertificateByHostname returns the certificate and private key for a given hostname
func (m *Manager) GetCertificateByHostname(hostname string) (string, string, error) {
//Check if the domain corrisponding cert exists
pubKey := "./tmp/localhost.pem"
priKey := "./tmp/localhost.key"
tlsBehavior, err := m.hostSpecificTlsBehavior(hostname)
if err != nil {
tlsBehavior, _ = defaultHostSpecificTlsBehavior(hostname)
}
if tlsBehavior.DisableSNI && tlsBehavior.PreferredCertificate != "" &&
utils.FileExists(filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".pem")) &&
utils.FileExists(filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".key")) {
//User setup a Preferred certificate, use the preferred certificate directly
pubKey = filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".pem")
priKey = filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".key")
} else {
if !tlsBehavior.DisableLegacyCertificateMatching &&
utils.FileExists(filepath.Join(m.CertStore, hostname+".pem")) &&
utils.FileExists(filepath.Join(m.CertStore, hostname+".key")) {
//Legacy filename matching, use the file names directly
//This is the legacy method of matching certificates, it will match the file names directly
//This is used for compatibility with Zoraxy v2 setups
pubKey = filepath.Join(m.CertStore, hostname+".pem")
priKey = filepath.Join(m.CertStore, hostname+".key")
} else if !tlsBehavior.DisableSNI &&
m.CertMatchExists(hostname) {
//SNI scan match, find the first matching certificate
pubKey, priKey = m.GetCertByX509CNHostname(hostname)
} else if tlsBehavior.EnableAutoHTTPS {
//Get certificate from CA, WIP
//TODO: Implement AutoHTTPS
} else {
//Fallback to legacy method of matching certificates
if m.DefaultCertExists() {
//Use default.pem and default.key
pubKey = filepath.Join(m.CertStore, "default.pem")
priKey = filepath.Join(m.CertStore, "default.key")
}
}
}
return pubKey, priKey, nil
}
// Check if both the default cert public key and private key exists
func (m *Manager) DefaultCertExists() bool {
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
@@ -220,7 +278,6 @@ func (m *Manager) RemoveCert(domain string) error {
//Update the cert list
m.UpdateLoadedCertList()
return nil
}