GoDoxy/agent/pkg/certs/certs.go
2025-02-11 06:20:09 +08:00

201 lines
4.9 KiB
Go

package certs
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math/big"
"os"
"time"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/utils"
)
const (
CertsDNSName = "godoxy.agent"
caCertPath = "certs/ca.crt"
caKeyPath = "certs/ca.key"
srvCertPath = "certs/agent.crt"
srvKeyPath = "certs/agent.key"
)
func loadCerts(certPath, keyPath string) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
return &cert, err
}
func write(b []byte, f *os.File) error {
_, err := f.Write(b)
return err
}
func saveCerts(certDER []byte, key *rsa.PrivateKey, certPath, keyPath string) ([]byte, []byte, error) {
certPEM, keyPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
if certPath == "" || keyPath == "" {
return certPEM, keyPEM, nil
}
certFile, err := os.Create(certPath)
if err != nil {
return nil, nil, err
}
defer certFile.Close()
keyFile, err := os.Create(keyPath)
if err != nil {
return nil, nil, err
}
defer keyFile.Close()
return certPEM, keyPEM, errors.Join(
write(certPEM, certFile),
write(keyPEM, keyFile),
)
}
func checkExists(certPath, keyPath string) bool {
certExists, err := utils.FileExists(certPath)
if err != nil {
E.LogFatal("cert error", err)
}
keyExists, err := utils.FileExists(keyPath)
if err != nil {
E.LogFatal("key error", err)
}
return certExists && keyExists
}
func InitCerts() (ca *tls.Certificate, srv *tls.Certificate, isNew bool, err error) {
if checkExists(caCertPath, caKeyPath) && checkExists(srvCertPath, srvKeyPath) {
logging.Info().Msg("Loading existing certs...")
ca, err = loadCerts(caCertPath, caKeyPath)
if err != nil {
return nil, nil, false, err
}
srv, err = loadCerts(srvCertPath, srvKeyPath)
if err != nil {
return nil, nil, false, err
}
logging.Info().Msg("Verifying agent cert...")
roots := x509.NewCertPool()
roots.AddCert(ca.Leaf)
srvCert, err := x509.ParseCertificate(srv.Certificate[0])
if err != nil {
return nil, nil, false, err
}
// check if srv is signed by ca
if _, err := srvCert.Verify(x509.VerifyOptions{
Roots: roots,
}); err == nil {
logging.Info().Msg("OK")
return ca, srv, false, nil
}
logging.Error().Msg("Agent cert and CA cert mismatch, regenerating")
}
// Create the CA's certificate
caTemplate := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"GoDoxy"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1000, 0, 0), // 1000 years
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
}
caKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, false, err
}
caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey)
if err != nil {
return nil, nil, false, err
}
certPEM, keyPEM, err := saveCerts(caCertDER, caKey, caCertPath, caKeyPath)
if err != nil {
return nil, nil, false, err
}
caCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, nil, false, err
}
ca = &caCert
// Generate a new private key for the server certificate
serverKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, false, err
}
srvTemplate := caTemplate
srvTemplate.Issuer = srvTemplate.Subject
srvTemplate.DNSNames = append(srvTemplate.DNSNames, CertsDNSName)
srvCertDER, err := x509.CreateCertificate(rand.Reader, &srvTemplate, &caTemplate, &serverKey.PublicKey, caKey)
if err != nil {
return nil, nil, false, err
}
certPEM, keyPEM, err = saveCerts(srvCertDER, serverKey, srvCertPath, srvKeyPath)
if err != nil {
return nil, nil, false, err
}
agentCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, nil, false, err
}
srv = &agentCert
return ca, srv, true, nil
}
func NewClientCert(ca *tls.Certificate) ([]byte, []byte, error) {
// Generate the SSL's private key
sslKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
// Create the SSL's certificate
sslTemplate := &x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{
Organization: []string{"GoDoxy"},
CommonName: CertsDNSName,
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1000, 0, 0), // 1000 years
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
// Sign the certificate with the CA
sslCertDER, err := x509.CreateCertificate(rand.Reader, sslTemplate, ca.Leaf, &sslKey.PublicKey, ca.PrivateKey)
if err != nil {
return nil, nil, err
}
return saveCerts(sslCertDER, sslKey, "", "")
}