|
@@ -5,8 +5,8 @@ import (
|
|
|
"crypto/x509"
|
|
|
"embed"
|
|
|
"encoding/pem"
|
|
|
+ "fmt"
|
|
|
"io"
|
|
|
- "io/ioutil"
|
|
|
"log"
|
|
|
"os"
|
|
|
"path/filepath"
|
|
@@ -15,12 +15,19 @@ import (
|
|
|
"imuslab.com/zoraxy/mod/utils"
|
|
|
)
|
|
|
|
|
|
+type CertCache struct {
|
|
|
+ Cert *x509.Certificate
|
|
|
+ PubKey string
|
|
|
+ PriKey string
|
|
|
+}
|
|
|
+
|
|
|
type Manager struct {
|
|
|
- CertStore string
|
|
|
- verbal bool
|
|
|
+ CertStore string //Path where all the certs are stored
|
|
|
+ LoadedCerts []*CertCache //A list of loaded certs
|
|
|
+ verbal bool
|
|
|
}
|
|
|
|
|
|
-//go:embed localhost.crt localhost.key
|
|
|
+//go:embed localhost.pem localhost.key
|
|
|
var buildinCertStore embed.FS
|
|
|
|
|
|
func NewManager(certStore string, verbal bool) (*Manager, error) {
|
|
@@ -28,14 +35,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 +140,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,36 +157,45 @@ 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)
|
|
|
+ fmt.Println(pubKey, priKey)
|
|
|
} 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")
|
|
@@ -117,17 +219,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 +245,9 @@ func (m *Manager) RemoveCert(domain string) error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ //Update the cert list
|
|
|
+ m.UpdateLoadedCertList()
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
@@ -171,15 +276,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
|
|
|
}
|