فهرست منبع

更新证书申请和处理逻辑

更新了证书申请函数,移除了不必要的私钥返回,并在多个文件中调整了相关的错误处理和变量使用。同时,新增了对颁发者证书的支持,并修正了一些拼写错误。版本号从 v0.3.1 更新到 v0.4.0。
SongZihuan 3 ماه پیش
والد
کامیت
eefe07061e
8فایلهای تغییر یافته به همراه145 افزوده شده و 71 حذف شده
  1. 1 1
      VERSION
  2. 28 7
      src/certssl/account/data.go
  3. 15 22
      src/certssl/applycert/main.go
  4. 24 4
      src/certssl/applycert/read.go
  5. 50 27
      src/certssl/main.go
  6. 25 8
      src/httpsslserver/server.go
  7. 1 1
      src/mainfunc/v1.go
  8. 1 1
      src/utils/x509.go

+ 1 - 1
VERSION

@@ -1 +1 @@
-v0.3.1
+v0.4.0

+ 28 - 7
src/certssl/account/data.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"github.com/SongZihuan/Http-Demo/src/utils"
+	"github.com/go-acme/lego/v4/certcrypto"
 	"github.com/go-acme/lego/v4/lego"
 	"github.com/go-acme/lego/v4/registration"
 	"os"
@@ -13,9 +14,11 @@ import (
 )
 
 const DefaultAccountExp = 24 * time.Hour
+const DefaultUserKeyType = certcrypto.RSA4096
 
 var ErrExpiredAccount = fmt.Errorf("account expired")
 var ErrNotValidAccount = fmt.Errorf("account not valid")
+var user *Account
 
 type Data struct {
 	Resource       *registration.Resource `json:"resource,omitempty"`
@@ -28,32 +31,43 @@ type Data struct {
 type Account struct {
 	data        Data
 	key         crypto.PrivateKey
+	dir         string
 	accountpath string
 	keypath     string
 }
 
-func NewAccount(basedir string, email string, key crypto.PrivateKey) (*Account, error) {
+func NewAccount(basedir string, email string) (*Account, error) {
 	dir := path.Join(basedir, "account", email)
 	err := os.MkdirAll(dir, 0775)
 	if err != nil {
 		return nil, fmt.Errorf("create account dir failed: %s", err.Error())
 	}
 
+	privateKey, err := certcrypto.GeneratePrivateKey(DefaultUserKeyType)
+	if err != nil {
+		return nil, fmt.Errorf("generate new user private key failed: %s", err.Error())
+	}
+
 	now := time.Now()
-	return &Account{
+	user = &Account{
 		data: Data{
 			Email:          email,
 			Resource:       nil,
 			RegisterTime:   now.Unix(),
 			ExpirationTime: now.Add(DefaultAccountExp).Unix(),
 		},
-		key:         key,
+		key:         privateKey,
+		dir:         dir,
 		accountpath: path.Join(dir, "account.json"),
 		keypath:     path.Join(dir, "account.key"),
-	}, nil
+	}
+	return user, nil
 }
 
 func LoadAccount(basedir string, email string) (*Account, error) {
+	if user != nil {
+		return user, nil
+	}
 
 	dir := path.Join(basedir, "account", email)
 	accountpath := path.Join(dir, "account.json")
@@ -88,12 +102,14 @@ func LoadAccount(basedir string, email string) (*Account, error) {
 		return nil, ErrNotValidAccount
 	}
 
-	return &Account{
+	user = &Account{
 		data:        data,
 		key:         privateKey,
+		dir:         dir,
 		accountpath: accountpath,
 		keypath:     keypath,
-	}, nil
+	}
+	return user, nil
 }
 
 func (u *Account) GetEmail() string {
@@ -109,7 +125,12 @@ func (u *Account) GetPrivateKey() crypto.PrivateKey {
 }
 
 func (u *Account) SaveAccount() error {
-	data, err := json.Marshal(u)
+	err := os.MkdirAll(u.dir, 0775)
+	if err != nil {
+		return fmt.Errorf("create account dir failed: %s", err.Error())
+	}
+
+	data, err := json.Marshal(u.data)
 	if err != nil {
 		return err
 	}

+ 15 - 22
src/certssl/applycert/main.go

@@ -1,7 +1,6 @@
 package applycert
 
 import (
-	"crypto"
 	"fmt"
 	"github.com/SongZihuan/Http-Demo/src/certssl/account"
 	"github.com/SongZihuan/Http-Demo/src/utils"
@@ -16,23 +15,17 @@ import (
 const DefaultCertTimeout = 30 * 24 * time.Hour
 const DefaultCertType = certcrypto.RSA4096
 
-func ApplyCert(basedir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string) (crypto.PrivateKey, *certificate.Resource, error) {
+func ApplyCert(basedir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string) (*certificate.Resource, error) {
 	if domain == "" || !utils.IsValidDomain(domain) {
-		return nil, nil, fmt.Errorf("domain is invalid")
+		return nil, fmt.Errorf("domain is invalid")
 	}
 
 	user, err := account.LoadAccount(basedir, email)
 	if err != nil {
-		fmt.Printf("load local account failed, register a ew on for %s: %s\n", email, err.Error())
-
-		privateKey, err := certcrypto.GeneratePrivateKey(DefaultCertType)
-		if err != nil {
-			return nil, nil, fmt.Errorf("generate new user private key failed: %s", err.Error())
-		}
-
-		user, err = account.NewAccount(basedir, email, privateKey)
+		fmt.Printf("load local account failed, register a new on for %s: %s\n", email, err.Error())
+		user, err = account.NewAccount(basedir, email)
 		if err != nil {
-			return nil, nil, fmt.Errorf("generate new user failed: %s", err.Error())
+			return nil, fmt.Errorf("generate new user failed: %s", err.Error())
 		}
 	}
 
@@ -42,7 +35,7 @@ func ApplyCert(basedir string, email string, aliyunAccessKey string, aliyunAcces
 	config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
 	client, err := lego.NewClient(config)
 	if err != nil {
-		return nil, nil, fmt.Errorf("new client failed: %s", err.Error())
+		return nil, fmt.Errorf("new client failed: %s", err.Error())
 	}
 
 	aliyunDnsConfig := alidns.NewDefaultConfig()
@@ -51,19 +44,19 @@ func ApplyCert(basedir string, email string, aliyunAccessKey string, aliyunAcces
 
 	provider, err := alidns.NewDNSProviderConfig(aliyunDnsConfig)
 	if err != nil {
-		return nil, nil, fmt.Errorf("failed to initialize AliDNS provider: %s", err.Error())
+		return nil, fmt.Errorf("failed to initialize AliDNS provider: %s", err.Error())
 	}
 
 	err = client.Challenge.SetDNS01Provider(provider)
 	if err != nil {
-		return nil, nil, fmt.Errorf("set challenge dns1 provider failed: %s", err.Error())
+		return nil, fmt.Errorf("set challenge dns1 provider failed: %s", err.Error())
 	}
 
 	reg, err := user.Register(client)
 	if err != nil {
-		return nil, nil, fmt.Errorf("get account failed: %s", err.Error())
+		return nil, fmt.Errorf("get account failed: %s", err.Error())
 	} else if reg == nil {
-		return nil, nil, fmt.Errorf("get account failed: return nil account.resurce, unknown reason")
+		return nil, fmt.Errorf("get account failed: return nil account.resurce, unknown reason")
 	}
 
 	request := certificate.ObtainRequest{
@@ -73,23 +66,23 @@ func ApplyCert(basedir string, email string, aliyunAccessKey string, aliyunAcces
 
 	resource, err := client.Certificate.Obtain(request)
 	if err != nil {
-		return nil, nil, fmt.Errorf("obtain certificate failed: %s", err.Error())
+		return nil, fmt.Errorf("obtain certificate failed: %s", err.Error())
 	}
 
 	err = user.SaveAccount()
 	if err != nil {
-		return nil, nil, fmt.Errorf("save account error after obtain: %s", err.Error())
+		return nil, fmt.Errorf("save account error after obtain: %s", err.Error())
 	}
 
 	err = writerWithDate(path.Join(basedir, "cert-backup"), resource)
 	if err != nil {
-		return nil, nil, fmt.Errorf("writer certificate backup failed: %s", err.Error())
+		return nil, fmt.Errorf("writer certificate backup failed: %s", err.Error())
 	}
 
 	err = writer(basedir, resource)
 	if err != nil {
-		return nil, nil, fmt.Errorf("writer certificate failed: %s", err.Error())
+		return nil, fmt.Errorf("writer certificate failed: %s", err.Error())
 	}
 
-	return user.GetPrivateKey(), resource, nil
+	return resource, nil
 }

+ 24 - 4
src/certssl/applycert/read.go

@@ -10,18 +10,23 @@ import (
 	"path"
 )
 
-func ReadLocalCertificateAndPrivateKey(basedir string) (crypto.PrivateKey, *x509.Certificate, error) {
+func ReadLocalCertificateAndPrivateKey(basedir string) (crypto.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
 	cert, err := readCertificate(basedir)
 	if err != nil {
-		return nil, nil, fmt.Errorf("read certificate failed: %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("read certificate failed: %s", err.Error())
+	}
+
+	cacert, err := readCACertificate(basedir)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read certificate failed: %s", err.Error())
 	}
 
 	privateKey, err := readPrivateKey(basedir)
 	if err != nil {
-		return nil, nil, fmt.Errorf("read private key failed: %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("read private key failed: %s", err.Error())
 	}
 
-	return privateKey, cert, nil
+	return privateKey, cert, cacert, nil
 }
 
 func readCertificate(basedir string) (*x509.Certificate, error) {
@@ -39,6 +44,21 @@ func readCertificate(basedir string) (*x509.Certificate, error) {
 	return cert, nil
 }
 
+func readCACertificate(basedir string) (*x509.Certificate, error) {
+	filepath := path.Join(basedir, filename.FileIssuerCertificate)
+	data, err := os.ReadFile(filepath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read certificate file: %v", err)
+	}
+
+	cert, err := utils.ReadCertificate(data)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parser certificate file: %v", err)
+	}
+
+	return cert, nil
+}
+
 func readPrivateKey(basedir string) (crypto.PrivateKey, error) {
 	filepath := path.Join(basedir, filename.FilePrivateKey)
 	data, err := os.ReadFile(filepath)

+ 50 - 27
src/certssl/main.go

@@ -11,46 +11,57 @@ import (
 
 const CertDefaultNewApplyTime = 5 * 24 * time.Hour
 
-func GetCertificateAndPrivateKey(basedir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string) (crypto.PrivateKey, *x509.Certificate, error) {
+func GetCertificateAndPrivateKey(basedir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string) (crypto.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
 	if email == "" {
 		email = "no-reply@example.com"
 	}
 
 	if !utils.IsValidEmail(email) {
-		return nil, nil, fmt.Errorf("not a valid email")
+		return nil, nil, nil, fmt.Errorf("not a valid email")
 	}
 
 	if !utils.IsValidDomain(domain) {
-		return nil, nil, fmt.Errorf("not a valid domain")
+		return nil, nil, nil, fmt.Errorf("not a valid domain")
 	}
 
-	privateKey, cert, err := applycert.ReadLocalCertificateAndPrivateKey(basedir)
+	privateKey, cert, cacert, err := applycert.ReadLocalCertificateAndPrivateKey(basedir)
 	if err == nil && utils.CheckCertWithDomain(cert, domain) && utils.CheckCertWithTime(cert, 5*24*time.Hour) {
-		return privateKey, cert, nil
+		return privateKey, cert, cacert, nil
 	}
 
-	privateKey, resource, err := applycert.ApplyCert(basedir, email, aliyunAccessKey, aliyunAccessSecret, domain)
+	resource, err := applycert.ApplyCert(basedir, email, aliyunAccessKey, aliyunAccessSecret, domain)
 	if err != nil {
-		return nil, nil, fmt.Errorf("apply cert failed: %s", err.Error())
-	} else if privateKey == nil || resource == nil {
-		return nil, nil, fmt.Errorf("read cert failed: private key or certificate (resource) is nil, unknown reason")
+		return nil, nil, nil, fmt.Errorf("apply cert failed: %s", err.Error())
+	} else if resource == nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: private key or certificate (resource) is nil, unknown reason")
+	}
+
+	privateKey, err = utils.ReadPrivateKey(resource.PrivateKey)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read private key failed: %s", err.Error())
 	}
 
 	cert, err = utils.ReadCertificate(resource.Certificate)
 	if err != nil {
-		return nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+	}
+
+	cacert, err = utils.ReadCertificate(resource.IssuerCertificate)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
 	}
 
-	return privateKey, cert, nil
+	return privateKey, cert, cacert, nil
 }
 
 type NewCert struct {
-	PrivateKey  crypto.PrivateKey
-	Certificate *x509.Certificate
-	Error       error
+	PrivateKey        crypto.PrivateKey
+	Certificate       *x509.Certificate
+	IssuerCertificate *x509.Certificate
+	Error             error
 }
 
-func WatchCertificateAndPrivateKey(dir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string, oldCert *x509.Certificate, stopchan chan bool, newchan chan NewCert) error {
+func WatchCertificate(dir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string, oldCert *x509.Certificate, stopchan chan bool, newchan chan NewCert) error {
 	for {
 		select {
 		case <-stopchan:
@@ -62,47 +73,59 @@ func WatchCertificateAndPrivateKey(dir string, email string, aliyunAccessKey str
 			close(stopchan)
 			return nil
 		default:
-			privateKey, cert, err := watchCertificateAndPrivateKey(dir, email, aliyunAccessKey, aliyunAccessSecret, domain, oldCert)
+			privateKey, cert, cacert, err := watchCertificate(dir, email, aliyunAccessKey, aliyunAccessSecret, domain, oldCert)
 			if err != nil {
 				newchan <- NewCert{
 					Error: fmt.Errorf("watch cert failed: %s", err.Error()),
 				}
-			} else if privateKey != nil || cert != nil {
+			} else if privateKey != nil && cert != nil && cacert != nil {
+				oldCert = cert
 				newchan <- NewCert{
-					PrivateKey:  privateKey,
-					Certificate: cert,
+					PrivateKey:        privateKey,
+					Certificate:       cert,
+					IssuerCertificate: cacert,
 				}
 			}
 		}
 	}
 }
 
-func watchCertificateAndPrivateKey(dir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string, oldCert *x509.Certificate) (crypto.PrivateKey, *x509.Certificate, error) {
+func watchCertificate(dir string, email string, aliyunAccessKey string, aliyunAccessSecret string, domain string, oldCert *x509.Certificate) (crypto.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
 	if email == "" {
 		email = "no-reply@example.com"
 	}
 
 	if !utils.IsValidEmail(email) {
-		return nil, nil, fmt.Errorf("not a valid email")
+		return nil, nil, nil, fmt.Errorf("not a valid email")
 	}
 
 	if !utils.IsValidDomain(domain) {
-		return nil, nil, fmt.Errorf("not a valid domain")
+		return nil, nil, nil, fmt.Errorf("not a valid domain")
 	}
 
 	if utils.CheckCertWithDomain(oldCert, domain) && utils.CheckCertWithTime(oldCert, CertDefaultNewApplyTime) {
-		return nil, nil, nil
+		return nil, nil, nil, nil
 	}
 
-	privateKey, resource, err := applycert.ApplyCert(dir, email, aliyunAccessKey, aliyunAccessSecret, domain)
+	resource, err := applycert.ApplyCert(dir, email, aliyunAccessKey, aliyunAccessSecret, domain)
 	if err != nil {
-		return nil, nil, fmt.Errorf("apply cert fail: %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("apply cert fail: %s", err.Error())
+	}
+
+	privateKey, err := utils.ReadPrivateKey(resource.PrivateKey)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read private key failed: %s", err.Error())
 	}
 
 	cert, err := utils.ReadCertificate(resource.Certificate)
 	if err != nil {
-		return nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
+	}
+
+	cacert, err := utils.ReadCertificate(resource.IssuerCertificate)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("read cert failed: %s", err.Error())
 	}
 
-	return privateKey, cert, nil
+	return privateKey, cert, cacert, nil
 }

+ 25 - 8
src/httpsslserver/server.go

@@ -25,8 +25,9 @@ var HttpSSLAliyunAccessSecret string
 
 var PrivateKey crypto.PrivateKey
 var Certificate *x509.Certificate
+var IssuserCertificate *x509.Certificate
 
-var ErrStop = fmt.Errorf("http server error")
+var ErrStop = fmt.Errorf("https server error")
 var ReloadMutex sync.Mutex
 
 func InitHttpSSLServer() (err error) {
@@ -37,9 +38,11 @@ func InitHttpSSLServer() (err error) {
 	HttpSSLAliyunAccessKey = flagparser.HttpsAliyunKey
 	HttpSSLAliyunAccessSecret = flagparser.HttpsAliyunSecret
 
-	PrivateKey, Certificate, err = certssl.GetCertificateAndPrivateKey(HttpSSLCertDir, HttpSSLEmail, HttpSSLAliyunAccessKey, HttpSSLAliyunAccessSecret, HttpSSLDomain)
+	PrivateKey, Certificate, IssuserCertificate, err = certssl.GetCertificateAndPrivateKey(HttpSSLCertDir, HttpSSLEmail, HttpSSLAliyunAccessKey, HttpSSLAliyunAccessSecret, HttpSSLDomain)
 	if err != nil {
 		return fmt.Errorf("init htttps cert ssl server error: %s", err.Error())
+	} else if PrivateKey == nil || Certificate == nil || IssuserCertificate == nil {
+		return fmt.Errorf("init https server error: get key and cert error, return nil, unknown reason")
 	}
 
 	err = initHttpSSLServer()
@@ -51,12 +54,21 @@ func InitHttpSSLServer() (err error) {
 }
 
 func initHttpSSLServer() (err error) {
+	if PrivateKey == nil || Certificate == nil || IssuserCertificate == nil {
+		return fmt.Errorf("init https server error: get key and cert error, return nil, unknown reason")
+	}
+
+	if Certificate.Raw == nil || len(Certificate.Raw) == 0 || IssuserCertificate.Raw == nil || len(IssuserCertificate.Raw) == 0 {
+		return fmt.Errorf("init https server error: get cert.raw error, return nil, unknown reason")
+	}
+
 	tlsConfig := &tls.Config{
 		Certificates: []tls.Certificate{{
-			Certificate: [][]byte{Certificate.Raw}, // Raw包含 DER 编码的证书
+			Certificate: [][]byte{Certificate.Raw, IssuserCertificate.Raw}, // Raw包含 DER 编码的证书
 			PrivateKey:  PrivateKey,
 			Leaf:        Certificate,
 		}},
+		MinVersion: tls.VersionTLS12,
 	}
 
 	HttpSSLServer = &http.Server{
@@ -70,7 +82,7 @@ func initHttpSSLServer() (err error) {
 
 func RunServer() error {
 	stopchan := make(chan bool)
-	WatchCert(stopchan)
+	WatchCertificate(stopchan)
 	err := runServer()
 	stopchan <- true
 	return err
@@ -95,11 +107,11 @@ ListenCycle:
 	}
 }
 
-func WatchCert(stopchan chan bool) {
+func WatchCertificate(stopchan chan bool) {
 	newchan := make(chan certssl.NewCert)
 
 	go func() {
-		err := certssl.WatchCertificateAndPrivateKey(HttpSSLCertDir, HttpSSLEmail, HttpSSLAliyunAccessKey, HttpSSLAliyunAccessSecret, HttpSSLDomain, Certificate, stopchan, newchan)
+		err := certssl.WatchCertificate(HttpSSLCertDir, HttpSSLEmail, HttpSSLAliyunAccessKey, HttpSSLAliyunAccessSecret, HttpSSLDomain, Certificate, stopchan, newchan)
 		if err != nil {
 			fmt.Printf("watch https cert server error: %s", err.Error())
 		}
@@ -113,7 +125,7 @@ func WatchCert(stopchan chan bool) {
 				return
 			} else if res.Error != nil {
 				fmt.Printf("https cert reload server error: %s", res.Error.Error())
-			} else if res.PrivateKey == nil && res.Certificate == nil {
+			} else if res.PrivateKey != nil && res.Certificate != nil && res.IssuerCertificate != nil {
 				func() {
 					ReloadMutex.Lock()
 					defer ReloadMutex.Unlock()
@@ -123,11 +135,16 @@ func WatchCert(stopchan chan bool) {
 
 					err := HttpSSLServer.Shutdown(ctx)
 					if err != nil {
-						fmt.Printf("https server reload error: %s", err.Error())
+						fmt.Printf("https server reload shutdown error: %s", err.Error())
 					}
 
 					PrivateKey = res.PrivateKey
 					Certificate = res.Certificate
+					IssuserCertificate = res.IssuerCertificate
+					err = initHttpSSLServer()
+					if err != nil {
+						fmt.Printf("https server reload init error: %s", err.Error())
+					}
 				}()
 			}
 		}

+ 1 - 1
src/mainfunc/v1.go

@@ -83,7 +83,7 @@ func MainV1() (exitcode int) {
 		fmt.Printf("Http Server error closed: %s\n", err.Error())
 		return 1
 	case err := <-httpsslchan:
-		if errors.Is(err, httpserver.ErrStop) {
+		if errors.Is(err, httpsslserver.ErrStop) {
 			fmt.Printf("Https Server closed: safe\n")
 			return 0
 		}

+ 1 - 1
src/utils/x509.go

@@ -50,5 +50,5 @@ func CheckCertWithTime(cert *x509.Certificate, gracePeriod time.Duration) bool {
 		return false
 	}
 
-	return false
+	return true
 }