mirror of
https://github.com/wyx2685/V2bX.git
synced 2025-01-22 09:58:14 -05:00
add self CertMode
This commit is contained in:
parent
27702d353a
commit
42e86bf94c
@ -42,6 +42,7 @@ func (h *HookServer) PreStart() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule) (net.Conn, adapter.Tracker) {
|
func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||||
|
t := &Tracker{l: func() {}}
|
||||||
l, err := limiter.GetLimiter(m.Inbound)
|
l, err := limiter.GetLimiter(m.Inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("get limiter for ", m.Inbound, " error: ", err)
|
log.Error("get limiter for ", m.Inbound, " error: ", err)
|
||||||
@ -50,26 +51,24 @@ func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapte
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
h.logger.Error("[", m.Inbound, "] ",
|
h.logger.Error("[", m.Inbound, "] ",
|
||||||
"Limited ", m.User, " access to ", m.Domain, " by domain rule")
|
"Limited ", m.User, " access to ", m.Domain, " by domain rule")
|
||||||
return conn, &Tracker{l: func() {}}
|
return conn, t
|
||||||
}
|
}
|
||||||
if l.CheckProtocolRule(m.Protocol) {
|
if l.CheckProtocolRule(m.Protocol) {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
h.logger.Error("[", m.Inbound, "] ",
|
h.logger.Error("[", m.Inbound, "] ",
|
||||||
"Limited ", m.User, " use ", m.Domain, " by protocol rule")
|
"Limited ", m.User, " use ", m.Domain, " by protocol rule")
|
||||||
return conn, &Tracker{l: func() {}}
|
return conn, t
|
||||||
}
|
}
|
||||||
ip := m.Source.Addr.String()
|
ip := m.Source.Addr.String()
|
||||||
if b, r := l.CheckLimit(m.User, ip, true); r {
|
if b, r := l.CheckLimit(m.User, ip, true); r {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
h.logger.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
|
h.logger.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
|
||||||
return conn, &Tracker{l: func() {}}
|
return conn, t
|
||||||
} else if b != nil {
|
} else if b != nil {
|
||||||
conn = rate.NewConnRateLimiter(conn, b)
|
conn = rate.NewConnRateLimiter(conn, b)
|
||||||
}
|
}
|
||||||
t := &Tracker{
|
t.l = func() {
|
||||||
l: func() {
|
l.ConnLimiter.DelConnCount(m.User, ip)
|
||||||
l.ConnLimiter.DelConnCount(m.User, ip)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if c, ok := h.counter.Load(m.Inbound); ok {
|
if c, ok := h.counter.Load(m.Inbound); ok {
|
||||||
return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
|
return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
|
||||||
|
80
node/cert.go
80
node/cert.go
@ -1,15 +1,22 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/InazumaV/V2bX/common/file"
|
"github.com/InazumaV/V2bX/common/file"
|
||||||
"github.com/InazumaV/V2bX/node/lego"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Controller) renewCertTask() error {
|
func (c *Controller) renewCertTask() error {
|
||||||
l, err := lego.New(c.CertConfig)
|
l, err := NewLego(c.CertConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithField("tag", c.tag).Info("new lego error: ", err)
|
log.WithField("tag", c.tag).Info("new lego error: ", err)
|
||||||
return nil
|
return nil
|
||||||
@ -25,12 +32,10 @@ func (c *Controller) renewCertTask() error {
|
|||||||
func (c *Controller) requestCert() error {
|
func (c *Controller) requestCert() error {
|
||||||
switch c.CertConfig.CertMode {
|
switch c.CertConfig.CertMode {
|
||||||
case "reality", "none", "":
|
case "reality", "none", "":
|
||||||
return nil
|
|
||||||
case "file":
|
case "file":
|
||||||
if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
|
if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
|
||||||
return fmt.Errorf("cert file path or key file path not exist")
|
return fmt.Errorf("cert file path or key file path not exist")
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
case "dns", "http":
|
case "dns", "http":
|
||||||
if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
|
if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
|
||||||
return fmt.Errorf("cert file path or key file path not exist")
|
return fmt.Errorf("cert file path or key file path not exist")
|
||||||
@ -38,15 +43,74 @@ func (c *Controller) requestCert() error {
|
|||||||
if file.IsExist(c.CertConfig.CertFile) && file.IsExist(c.CertConfig.KeyFile) {
|
if file.IsExist(c.CertConfig.CertFile) && file.IsExist(c.CertConfig.KeyFile) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
l, err := lego.New(c.CertConfig)
|
l, err := NewLego(c.CertConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create lego object error: %s", err)
|
return fmt.Errorf("create lego object error: %s", err)
|
||||||
}
|
}
|
||||||
err = l.CreateCert()
|
err = l.CreateCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create cert error: %s", err)
|
return fmt.Errorf("create lego cert error: %s", err)
|
||||||
}
|
}
|
||||||
return nil
|
case "self":
|
||||||
|
if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
|
||||||
|
return fmt.Errorf("cert file path or key file path not exist")
|
||||||
|
}
|
||||||
|
if file.IsExist(c.CertConfig.CertFile) && file.IsExist(c.CertConfig.KeyFile) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := generateSelfSslCertificate(
|
||||||
|
c.CertConfig.CertDomain,
|
||||||
|
c.CertConfig.CertFile,
|
||||||
|
c.CertConfig.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate self cert error: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported certmode: %s", c.CertConfig.CertMode)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unsupported certmode: %s", c.CertConfig.CertMode)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSelfSslCertificate(domain, certPath, keyPath string) error {
|
||||||
|
key, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
tmpl := &x509.Certificate{
|
||||||
|
Version: 3,
|
||||||
|
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: domain,
|
||||||
|
},
|
||||||
|
DNSNames: []string{domain},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(30, 0, 0),
|
||||||
|
}
|
||||||
|
cert, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(certPath, os.O_CREATE|os.O_RDWR, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = pem.Encode(f, &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: cert,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err = os.OpenFile(keyPath, os.O_CREATE|os.O_RDWR, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = pem.Encode(f, &pem.Block{
|
||||||
|
Type: "EC PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
7
node/cert_test.go
Normal file
7
node/cert_test.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Test_generateSelfSslCertificate(t *testing.T) {
|
||||||
|
t.Log(generateSelfSslCertificate("domain.com", "1.pem", "1.key"))
|
||||||
|
}
|
272
node/lego.go
Normal file
272
node/lego.go
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
|
||||||
|
"github.com/InazumaV/V2bX/common/file"
|
||||||
|
"github.com/InazumaV/V2bX/conf"
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lego struct {
|
||||||
|
client *lego.Client
|
||||||
|
config *conf.CertConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLego(config *conf.CertConfig) (*Lego, error) {
|
||||||
|
user, err := NewLegoUser(path.Join(path.Dir(config.CertFile),
|
||||||
|
"user",
|
||||||
|
fmt.Sprintf("user-%s.json", config.Email)),
|
||||||
|
config.Email)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create user error: %s", err)
|
||||||
|
}
|
||||||
|
c := lego.NewConfig(user)
|
||||||
|
//c.CADirURL = "http://192.168.99.100:4000/directory"
|
||||||
|
c.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
client, err := lego.NewClient(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := Lego{
|
||||||
|
client: client,
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
err = l.SetProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("set provider error: %s", err)
|
||||||
|
}
|
||||||
|
return &l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPath(p string) error {
|
||||||
|
if !file.IsExist(path.Dir(p)) {
|
||||||
|
err := os.MkdirAll(path.Dir(p), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create dir error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lego) SetProvider() error {
|
||||||
|
switch l.config.CertMode {
|
||||||
|
case "http":
|
||||||
|
err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "dns":
|
||||||
|
for k, v := range l.config.DNSEnv {
|
||||||
|
os.Setenv(k, v)
|
||||||
|
}
|
||||||
|
p, err := dns.NewDNSChallengeProviderByName(l.config.Provider)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create dns challenge provider error: %s", err)
|
||||||
|
}
|
||||||
|
err = l.client.Challenge.SetDNS01Provider(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("set dns provider error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lego) CreateCert() (err error) {
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: []string{l.config.CertDomain},
|
||||||
|
}
|
||||||
|
certificates, err := l.client.Certificate.Obtain(request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("obtain certificate error: %s", err)
|
||||||
|
}
|
||||||
|
err = l.writeCert(certificates)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lego) RenewCert() error {
|
||||||
|
file, err := os.ReadFile(l.config.CertFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read cert file error: %s", err)
|
||||||
|
}
|
||||||
|
if e, err := l.CheckCert(file); !e {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("check cert error: %s", err)
|
||||||
|
}
|
||||||
|
res, err := l.client.Certificate.Renew(certificate.Resource{
|
||||||
|
Domain: l.config.CertDomain,
|
||||||
|
Certificate: file,
|
||||||
|
}, false, false, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = l.writeCert(res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lego) CheckCert(file []byte) (bool, error) {
|
||||||
|
cert, err := certcrypto.ParsePEMCertificate(file)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
notAfter := int(time.Until(cert.NotAfter).Hours() / 24.0)
|
||||||
|
if notAfter > 30 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
func (l *Lego) parseParams(path string) string {
|
||||||
|
r := strings.NewReplacer("{domain}", l.config.CertDomain,
|
||||||
|
"{email}", l.config.Email)
|
||||||
|
return r.Replace(path)
|
||||||
|
}
|
||||||
|
func (l *Lego) writeCert(certificates *certificate.Resource) error {
|
||||||
|
err := checkPath(l.config.CertFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check path error: %s", err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(l.parseParams(l.config.CertFile), certificates.Certificate, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = checkPath(l.config.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check path error: %s", err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(l.parseParams(l.config.KeyFile), certificates.PrivateKey, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Email string `json:"Email"`
|
||||||
|
Registration *registration.Resource `json:"Registration"`
|
||||||
|
key crypto.PrivateKey
|
||||||
|
KeyEncoded string `json:"Key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
func (u *User) GetRegistration() *registration.Resource {
|
||||||
|
return u.Registration
|
||||||
|
}
|
||||||
|
func (u *User) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
return u.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLegoUser(path string, email string) (*User, error) {
|
||||||
|
var user User
|
||||||
|
if file.IsExist(path) {
|
||||||
|
err := user.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user.Email != email {
|
||||||
|
user.Registration = nil
|
||||||
|
user.Email = email
|
||||||
|
err := registerUser(&user, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.Email = email
|
||||||
|
err := registerUser(&user, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerUser(user *User, path string) error {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate key error: %s", err)
|
||||||
|
}
|
||||||
|
user.key = privateKey
|
||||||
|
c := lego.NewConfig(user)
|
||||||
|
client, err := lego.NewClient(c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create lego client error: %s", err)
|
||||||
|
}
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Registration = reg
|
||||||
|
err = user.Save(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("save user error: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodePrivate(privKey *ecdsa.PrivateKey) (string, error) {
|
||||||
|
encoded, err := x509.MarshalECPrivateKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: encoded})
|
||||||
|
return string(pemEncoded), nil
|
||||||
|
}
|
||||||
|
func (u *User) Save(path string) error {
|
||||||
|
err := checkPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check path error: %s", err)
|
||||||
|
}
|
||||||
|
u.KeyEncoded, _ = EncodePrivate(u.key.(*ecdsa.PrivateKey))
|
||||||
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.NewEncoder(f).Encode(u)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal json error: %s", err)
|
||||||
|
}
|
||||||
|
u.KeyEncoded = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey, error) {
|
||||||
|
blockPriv, _ := pem.Decode([]byte(pemEncodedPriv))
|
||||||
|
x509EncodedPriv := blockPriv.Bytes
|
||||||
|
privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv)
|
||||||
|
return privateKey, err
|
||||||
|
}
|
||||||
|
func (u *User) Load(path string) error {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open file error: %s", err)
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(f).Decode(u)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshal json error: %s", err)
|
||||||
|
}
|
||||||
|
u.key, err = u.DecodePrivate(u.KeyEncoded)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decode private key error: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,104 +0,0 @@
|
|||||||
package lego
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Lego) SetProvider() error {
|
|
||||||
switch l.config.CertMode {
|
|
||||||
case "http":
|
|
||||||
err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "dns":
|
|
||||||
for k, v := range l.config.DNSEnv {
|
|
||||||
os.Setenv(k, v)
|
|
||||||
}
|
|
||||||
p, err := dns.NewDNSChallengeProviderByName(l.config.Provider)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create dns challenge provider error: %s", err)
|
|
||||||
}
|
|
||||||
err = l.client.Challenge.SetDNS01Provider(p)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("set dns provider error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lego) CreateCert() (err error) {
|
|
||||||
request := certificate.ObtainRequest{
|
|
||||||
Domains: []string{l.config.CertDomain},
|
|
||||||
}
|
|
||||||
certificates, err := l.client.Certificate.Obtain(request)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("obtain certificate error: %s", err)
|
|
||||||
}
|
|
||||||
err = l.writeCert(certificates)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lego) RenewCert() error {
|
|
||||||
file, err := os.ReadFile(l.config.CertFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("read cert file error: %s", err)
|
|
||||||
}
|
|
||||||
if e, err := l.CheckCert(file); !e {
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("check cert error: %s", err)
|
|
||||||
}
|
|
||||||
res, err := l.client.Certificate.Renew(certificate.Resource{
|
|
||||||
Domain: l.config.CertDomain,
|
|
||||||
Certificate: file,
|
|
||||||
}, false, false, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = l.writeCert(res)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lego) CheckCert(file []byte) (bool, error) {
|
|
||||||
cert, err := certcrypto.ParsePEMCertificate(file)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
notAfter := int(time.Until(cert.NotAfter).Hours() / 24.0)
|
|
||||||
if notAfter > 30 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
func (l *Lego) parseParams(path string) string {
|
|
||||||
r := strings.NewReplacer("{domain}", l.config.CertDomain,
|
|
||||||
"{email}", l.config.Email)
|
|
||||||
return r.Replace(path)
|
|
||||||
}
|
|
||||||
func (l *Lego) writeCert(certificates *certificate.Resource) error {
|
|
||||||
err := checkPath(l.config.CertFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("check path error: %s", err)
|
|
||||||
}
|
|
||||||
err = os.WriteFile(l.parseParams(l.config.CertFile), certificates.Certificate, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = checkPath(l.config.KeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("check path error: %s", err)
|
|
||||||
}
|
|
||||||
err = os.WriteFile(l.parseParams(l.config.KeyFile), certificates.PrivateKey, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package lego
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/InazumaV/V2bX/common/file"
|
|
||||||
"github.com/InazumaV/V2bX/conf"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Lego struct {
|
|
||||||
client *lego.Client
|
|
||||||
config *conf.CertConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(config *conf.CertConfig) (*Lego, error) {
|
|
||||||
user, err := NewUser(path.Join(path.Dir(config.CertFile),
|
|
||||||
"user",
|
|
||||||
fmt.Sprintf("user-%s.json", config.Email)),
|
|
||||||
config.Email)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create user error: %s", err)
|
|
||||||
}
|
|
||||||
c := lego.NewConfig(user)
|
|
||||||
//c.CADirURL = "http://192.168.99.100:4000/directory"
|
|
||||||
c.Certificate.KeyType = certcrypto.RSA2048
|
|
||||||
client, err := lego.NewClient(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
l := Lego{
|
|
||||||
client: client,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
err = l.SetProvider()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("set provider error: %s", err)
|
|
||||||
}
|
|
||||||
return &l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPath(p string) error {
|
|
||||||
if !file.IsExist(path.Dir(p)) {
|
|
||||||
err := os.MkdirAll(path.Dir(p), 0755)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create dir error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
package lego
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/InazumaV/V2bX/common/file"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
|
||||||
"github.com/goccy/go-json"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Email string `json:"Email"`
|
|
||||||
Registration *registration.Resource `json:"Registration"`
|
|
||||||
key crypto.PrivateKey
|
|
||||||
KeyEncoded string `json:"Key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) GetEmail() string {
|
|
||||||
return u.Email
|
|
||||||
}
|
|
||||||
func (u *User) GetRegistration() *registration.Resource {
|
|
||||||
return u.Registration
|
|
||||||
}
|
|
||||||
func (u *User) GetPrivateKey() crypto.PrivateKey {
|
|
||||||
return u.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUser(path string, email string) (*User, error) {
|
|
||||||
var user User
|
|
||||||
if file.IsExist(path) {
|
|
||||||
err := user.Load(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if user.Email != email {
|
|
||||||
user.Registration = nil
|
|
||||||
user.Email = email
|
|
||||||
err := registerUser(&user, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
user.Email = email
|
|
||||||
err := registerUser(&user, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerUser(user *User, path string) error {
|
|
||||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("generate key error: %s", err)
|
|
||||||
}
|
|
||||||
user.key = privateKey
|
|
||||||
c := lego.NewConfig(user)
|
|
||||||
client, err := lego.NewClient(c)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create lego client error: %s", err)
|
|
||||||
}
|
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.Registration = reg
|
|
||||||
err = user.Save(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("save user error: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func EncodePrivate(privKey *ecdsa.PrivateKey) (string, error) {
|
|
||||||
encoded, err := x509.MarshalECPrivateKey(privKey)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: encoded})
|
|
||||||
return string(pemEncoded), nil
|
|
||||||
}
|
|
||||||
func (u *User) Save(path string) error {
|
|
||||||
err := checkPath(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("check path error: %s", err)
|
|
||||||
}
|
|
||||||
u.KeyEncoded, _ = EncodePrivate(u.key.(*ecdsa.PrivateKey))
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = json.NewEncoder(f).Encode(u)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("marshal json error: %s", err)
|
|
||||||
}
|
|
||||||
u.KeyEncoded = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey, error) {
|
|
||||||
blockPriv, _ := pem.Decode([]byte(pemEncodedPriv))
|
|
||||||
x509EncodedPriv := blockPriv.Bytes
|
|
||||||
privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv)
|
|
||||||
return privateKey, err
|
|
||||||
}
|
|
||||||
func (u *User) Load(path string) error {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("open file error: %s", err)
|
|
||||||
}
|
|
||||||
err = json.NewDecoder(f).Decode(u)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal json error: %s", err)
|
|
||||||
}
|
|
||||||
u.key, err = u.DecodePrivate(u.KeyEncoded)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("decode private key error: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package lego
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
@ -12,7 +12,7 @@ var l *Lego
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
l, err = New(&conf.CertConfig{
|
l, err = NewLego(&conf.CertConfig{
|
||||||
CertMode: "dns",
|
CertMode: "dns",
|
||||||
Email: "test@test.com",
|
Email: "test@test.com",
|
||||||
CertDomain: "test.test.com",
|
CertDomain: "test.test.com",
|
Loading…
Reference in New Issue
Block a user