mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-12 08:05:19 +02:00
Updated a lot of stuffs
+ Added comments for whitelist + Added automatic cert pick for multi-host certs (SNI) + Renamed .crt to .pem for cert store + Added best-fit selection for wildcard matching rules + Added x-proxy-by header + Added X-real-Ip header + Added Development Mode (Cache-Control: no-store) + Updated utm timeout to 10 seconds instead of 90
This commit is contained in:
@@ -5,22 +5,22 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
//This remove the certificates in the list where either the
|
||||
//public key or the private key is missing
|
||||
// This remove the certificates in the list where either the
|
||||
// public key or the private key is missing
|
||||
func getCertPairs(certFiles []string) []string {
|
||||
crtMap := make(map[string]bool)
|
||||
pemMap := make(map[string]bool)
|
||||
keyMap := make(map[string]bool)
|
||||
|
||||
for _, filename := range certFiles {
|
||||
if filepath.Ext(filename) == ".crt" {
|
||||
crtMap[strings.TrimSuffix(filename, ".crt")] = true
|
||||
if filepath.Ext(filename) == ".pem" {
|
||||
pemMap[strings.TrimSuffix(filename, ".pem")] = true
|
||||
} else if filepath.Ext(filename) == ".key" {
|
||||
keyMap[strings.TrimSuffix(filename, ".key")] = true
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
for domain := range crtMap {
|
||||
for domain := range pemMap {
|
||||
if keyMap[domain] {
|
||||
result = append(result, domain)
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func getCertPairs(certFiles []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
//Get the cloest subdomain certificate from a list of domains
|
||||
// Get the cloest subdomain certificate from a list of domains
|
||||
func matchClosestDomainCertificate(subdomain string, domains []string) string {
|
||||
var matchingDomain string = ""
|
||||
maxLength := 0
|
||||
@@ -43,18 +43,3 @@ func matchClosestDomainCertificate(subdomain string, domains []string) string {
|
||||
|
||||
return matchingDomain
|
||||
}
|
||||
|
||||
//Check if a requesting domain is a subdomain of a given domain
|
||||
func isSubdomain(subdomain, domain string) bool {
|
||||
subdomainParts := strings.Split(subdomain, ".")
|
||||
domainParts := strings.Split(domain, ".")
|
||||
if len(subdomainParts) < len(domainParts) {
|
||||
return false
|
||||
}
|
||||
for i := range domainParts {
|
||||
if subdomainParts[len(subdomainParts)-1-i] != domainParts[len(domainParts)-1-i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
22
src/mod/tlscert/localhost.pem
Normal file
22
src/mod/tlscert/localhost.pem
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDuTCCAqCgAwIBAgIBADANBgkqhkiG9w0BAQ0FADB2MQswCQYDVQQGEwJoazES
|
||||
MBAGA1UECAwJSG9uZyBLb25nMRQwEgYDVQQKDAtpbXVzbGFiLmNvbTEZMBcGA1UE
|
||||
AwwQWm9yYXh5IFNlbGYtaG9zdDEQMA4GA1UEBwwHSU1VU0xBQjEQMA4GA1UECwwH
|
||||
SU1VU0xBQjAeFw0yMzA1MjcxMDQyNDJaFw0zODA1MjgxMDQyNDJaMHYxCzAJBgNV
|
||||
BAYTAmhrMRIwEAYDVQQIDAlIb25nIEtvbmcxFDASBgNVBAoMC2ltdXNsYWIuY29t
|
||||
MRkwFwYDVQQDDBBab3JheHkgU2VsZi1ob3N0MRAwDgYDVQQHDAdJTVVTTEFCMRAw
|
||||
DgYDVQQLDAdJTVVTTEFCMIIBIzANBgkqhkiG9w0BAQEFAAOCARAAMIIBCwKCAQIA
|
||||
xav3Qq4DBooHsGW9m+r0dgjI832grX2c0Z6MJQQoE7B6wfpUI0OyfRugTXyXoiRZ
|
||||
gLxuROgiCUmp8FaLbl7RsvbImMbCPo3D/RbCT1aJCNXLZ0a7yvcDYc6woQW4nUyk
|
||||
ohHfT2otcu+OYS6aYRZuXGsKTAqPSwEXRMtr89wkPgZPsrCD27LFHBOmIcVABDvF
|
||||
KRuiwHWSHhFfU5n1AZLyYeYoLNQ9fZPvzPpkMD+HMKi4MMwr/vLE0DwU5jSfVFq+
|
||||
cd68zVihp9N/T77yah5EIH9CYm4m8Acs4bfL8DALxnaSN3KmGw6J35rOXrJvJLdh
|
||||
t42PDROmQrXN8uG8wGkBiBkCAwEAAaNQME4wHQYDVR0OBBYEFLhXihE+1K6MoL0P
|
||||
Nx5htfuSatpiMB8GA1UdIwQYMBaAFLhXihE+1K6MoL0PNx5htfuSatpiMAwGA1Ud
|
||||
EwQFMAMBAf8wDQYJKoZIhvcNAQENBQADggECAMCn0ed1bfLefGvoQJV/q+X9p61U
|
||||
HunSFJAAhp0N2Q3tq/zjIu0kJX7N0JBciEw2c0ZmqJIqR8V8Im/h/4XuuOR+53hg
|
||||
opOSPo39ww7mpxyBlQm63v1nXcNQcvw4U0JqXQ4Kyv8cgX7DIuyjRWHQpc5+6joy
|
||||
L5Nz5hzQbgpnPdHQEMorfnm8q6bWg/291IAV3ZA9Z6T5gn4YuyjeUdDczQtpT6nu
|
||||
1iTNPqtO6R3aeTVT+OSJT9sH2MHfDAsf371HBM6MzM/5QBc/62Bgau7NUjNKeSEA
|
||||
EtUBil8wBHwT7vOtqbyNk5FHEfoCpYsQtP7AtEo10izKCQpDXPftfiJefkOY
|
||||
-----END CERTIFICATE-----
|
@@ -6,7 +6,6 @@ import (
|
||||
"embed"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -15,12 +14,19 @@ import (
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
CertStore string
|
||||
verbal bool
|
||||
type CertCache struct {
|
||||
Cert *x509.Certificate
|
||||
PubKey string
|
||||
PriKey string
|
||||
}
|
||||
|
||||
//go:embed localhost.crt localhost.key
|
||||
type Manager struct {
|
||||
CertStore string //Path where all the certs are stored
|
||||
LoadedCerts []*CertCache //A list of loaded certs
|
||||
verbal bool
|
||||
}
|
||||
|
||||
//go:embed localhost.pem localhost.key
|
||||
var buildinCertStore embed.FS
|
||||
|
||||
func NewManager(certStore string, verbal bool) (*Manager, error) {
|
||||
@@ -28,14 +34,99 @@ func NewManager(certStore string, verbal bool) (*Manager, error) {
|
||||
os.MkdirAll(certStore, 0775)
|
||||
}
|
||||
|
||||
pubKey := "./tmp/localhost.pem"
|
||||
priKey := "./tmp/localhost.key"
|
||||
|
||||
//Check if this is initial setup
|
||||
if !utils.FileExists(pubKey) {
|
||||
buildInPubKey, _ := buildinCertStore.ReadFile(filepath.Base(pubKey))
|
||||
os.WriteFile(pubKey, buildInPubKey, 0775)
|
||||
}
|
||||
|
||||
if !utils.FileExists(priKey) {
|
||||
buildInPriKey, _ := buildinCertStore.ReadFile(filepath.Base(priKey))
|
||||
os.WriteFile(priKey, buildInPriKey, 0775)
|
||||
}
|
||||
|
||||
thisManager := Manager{
|
||||
CertStore: certStore,
|
||||
verbal: verbal,
|
||||
CertStore: certStore,
|
||||
LoadedCerts: []*CertCache{},
|
||||
verbal: verbal,
|
||||
}
|
||||
|
||||
err := thisManager.UpdateLoadedCertList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &thisManager, nil
|
||||
}
|
||||
|
||||
// Update domain mapping from file
|
||||
func (m *Manager) UpdateLoadedCertList() error {
|
||||
//Get a list of certificates from file
|
||||
domainList, err := m.ListCertDomains()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Load each of the certificates into memory
|
||||
certList := []*CertCache{}
|
||||
for _, certname := range domainList {
|
||||
//Read their certificate into memory
|
||||
pubKey := filepath.Join(m.CertStore, certname+".pem")
|
||||
priKey := filepath.Join(m.CertStore, certname+".key")
|
||||
certificate, err := tls.LoadX509KeyPair(pubKey, priKey)
|
||||
if err != nil {
|
||||
log.Println("Certificate loaded failed: " + certname)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, thisCert := range certificate.Certificate {
|
||||
loadedCert, err := x509.ParseCertificate(thisCert)
|
||||
if err != nil {
|
||||
//Error pasring cert, skip this byte segment
|
||||
continue
|
||||
}
|
||||
|
||||
thisCacheEntry := CertCache{
|
||||
Cert: loadedCert,
|
||||
PubKey: pubKey,
|
||||
PriKey: priKey,
|
||||
}
|
||||
certList = append(certList, &thisCacheEntry)
|
||||
}
|
||||
}
|
||||
|
||||
//Replace runtime cert array
|
||||
m.LoadedCerts = certList
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match cert by CN
|
||||
func (m *Manager) CertMatchExists(serverName string) bool {
|
||||
for _, certCacheEntry := range m.LoadedCerts {
|
||||
if certCacheEntry.Cert.VerifyHostname(serverName) == nil || certCacheEntry.Cert.Issuer.CommonName == serverName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get cert entry by matching server name, return pubKey and priKey if found
|
||||
// check with CertMatchExists before calling to the load function
|
||||
func (m *Manager) GetCertByX509CNHostname(serverName string) (string, string) {
|
||||
for _, certCacheEntry := range m.LoadedCerts {
|
||||
if certCacheEntry.Cert.VerifyHostname(serverName) == nil || certCacheEntry.Cert.Issuer.CommonName == serverName {
|
||||
return certCacheEntry.PubKey, certCacheEntry.PriKey
|
||||
}
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// Return a list of domains by filename
|
||||
func (m *Manager) ListCertDomains() ([]string, error) {
|
||||
filenames, err := m.ListCerts()
|
||||
if err != nil {
|
||||
@@ -48,8 +139,9 @@ func (m *Manager) ListCertDomains() ([]string, error) {
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
// Return a list of cert files (public and private keys)
|
||||
func (m *Manager) ListCerts() ([]string, error) {
|
||||
certs, err := ioutil.ReadDir(m.CertStore)
|
||||
certs, err := os.ReadDir(m.CertStore)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
@@ -64,44 +156,52 @@ func (m *Manager) ListCerts() ([]string, error) {
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
// 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.crt"
|
||||
pubKey := "./tmp/localhost.pem"
|
||||
priKey := "./tmp/localhost.key"
|
||||
|
||||
//Check if this is initial setup
|
||||
if !utils.FileExists(pubKey) {
|
||||
buildInPubKey, _ := buildinCertStore.ReadFile(filepath.Base(pubKey))
|
||||
os.WriteFile(pubKey, buildInPubKey, 0775)
|
||||
}
|
||||
|
||||
if !utils.FileExists(priKey) {
|
||||
buildInPriKey, _ := buildinCertStore.ReadFile(filepath.Base(priKey))
|
||||
os.WriteFile(priKey, buildInPriKey, 0775)
|
||||
}
|
||||
|
||||
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".crt")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
|
||||
pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".crt")
|
||||
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 {
|
||||
domainCerts, _ := m.ListCertDomains()
|
||||
cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
|
||||
if cloestDomainCert != "" {
|
||||
//There is a matching parent domain for this subdomain. Use this instead.
|
||||
pubKey = filepath.Join(m.CertStore, cloestDomainCert+".crt")
|
||||
priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
|
||||
} else if m.DefaultCertExists() {
|
||||
//Use default.crt and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.crt")
|
||||
//Fallback to legacy method of matching certificates
|
||||
/*
|
||||
domainCerts, _ := m.ListCertDomains()
|
||||
cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
|
||||
if cloestDomainCert != "" {
|
||||
//There is a matching parent domain for this subdomain. Use this instead.
|
||||
pubKey = filepath.Join(m.CertStore, cloestDomainCert+".pem")
|
||||
priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
|
||||
} else if m.DefaultCertExists() {
|
||||
//Use default.pem and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||
priKey = filepath.Join(m.CertStore, "default.key")
|
||||
if m.verbal {
|
||||
log.Println("No matching certificate found. Serving with default")
|
||||
}
|
||||
} else {
|
||||
if m.verbal {
|
||||
log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
}
|
||||
}*/
|
||||
|
||||
if m.DefaultCertExists() {
|
||||
//Use default.pem and default.key
|
||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||
priKey = filepath.Join(m.CertStore, "default.key")
|
||||
if m.verbal {
|
||||
log.Println("No matching certificate found. Serving with default")
|
||||
}
|
||||
//if m.verbal {
|
||||
// log.Println("No matching certificate found. Serving with default")
|
||||
//}
|
||||
} else {
|
||||
if m.verbal {
|
||||
log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
}
|
||||
//if m.verbal {
|
||||
// log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,17 +217,17 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
|
||||
|
||||
// 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.crt")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
}
|
||||
|
||||
// Check if the default cert exists returning seperate results for pubkey and prikey
|
||||
func (m *Manager) DefaultCertExistsSep() (bool, bool) {
|
||||
return utils.FileExists(filepath.Join(m.CertStore, "default.crt")), utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")), utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||
}
|
||||
|
||||
// Delete the cert if exists
|
||||
func (m *Manager) RemoveCert(domain string) error {
|
||||
pubKey := filepath.Join(m.CertStore, domain+".crt")
|
||||
pubKey := filepath.Join(m.CertStore, domain+".pem")
|
||||
priKey := filepath.Join(m.CertStore, domain+".key")
|
||||
if utils.FileExists(pubKey) {
|
||||
err := os.Remove(pubKey)
|
||||
@@ -143,6 +243,9 @@ func (m *Manager) RemoveCert(domain string) error {
|
||||
}
|
||||
}
|
||||
|
||||
//Update the cert list
|
||||
m.UpdateLoadedCertList()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -171,15 +274,11 @@ func IsValidTLSFile(file io.Reader) bool {
|
||||
return false
|
||||
}
|
||||
// Check if the certificate is a valid TLS/SSL certificate
|
||||
return cert.IsCA == false && cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 && cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0
|
||||
return !cert.IsCA && cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 && cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0
|
||||
} else if strings.Contains(block.Type, "PRIVATE KEY") {
|
||||
// The file contains a private key
|
||||
_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
// Handle the error
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return err == nil
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
Reference in New Issue
Block a user