diff --git a/core/sing/hook.go b/core/sing/hook.go index 428f633..e965c6c 100644 --- a/core/sing/hook.go +++ b/core/sing/hook.go @@ -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) { + t := &Tracker{l: func() {}} l, err := limiter.GetLimiter(m.Inbound) if err != nil { 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() h.logger.Error("[", m.Inbound, "] ", "Limited ", m.User, " access to ", m.Domain, " by domain rule") - return conn, &Tracker{l: func() {}} + return conn, t } if l.CheckProtocolRule(m.Protocol) { conn.Close() h.logger.Error("[", m.Inbound, "] ", "Limited ", m.User, " use ", m.Domain, " by protocol rule") - return conn, &Tracker{l: func() {}} + return conn, t } ip := m.Source.Addr.String() if b, r := l.CheckLimit(m.User, ip, true); r { conn.Close() h.logger.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn") - return conn, &Tracker{l: func() {}} + return conn, t } else if b != nil { conn = rate.NewConnRateLimiter(conn, b) } - t := &Tracker{ - l: func() { - l.ConnLimiter.DelConnCount(m.User, ip) - }, + t.l = func() { + l.ConnLimiter.DelConnCount(m.User, ip) } if c, ok := h.counter.Load(m.Inbound); ok { return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t diff --git a/node/cert.go b/node/cert.go index 9f9f3df..425efee 100644 --- a/node/cert.go +++ b/node/cert.go @@ -1,15 +1,22 @@ package node import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" + "math/big" + "os" + "time" "github.com/InazumaV/V2bX/common/file" - "github.com/InazumaV/V2bX/node/lego" log "github.com/sirupsen/logrus" ) func (c *Controller) renewCertTask() error { - l, err := lego.New(c.CertConfig) + l, err := NewLego(c.CertConfig) if err != nil { log.WithField("tag", c.tag).Info("new lego error: ", err) return nil @@ -25,12 +32,10 @@ func (c *Controller) renewCertTask() error { func (c *Controller) requestCert() error { switch c.CertConfig.CertMode { case "reality", "none", "": - return nil case "file": if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" { return fmt.Errorf("cert file path or key file path not exist") } - return nil case "dns", "http": if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" { 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) { return nil } - l, err := lego.New(c.CertConfig) + l, err := NewLego(c.CertConfig) if err != nil { return fmt.Errorf("create lego object error: %s", err) } err = l.CreateCert() 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 } diff --git a/node/cert_test.go b/node/cert_test.go new file mode 100644 index 0000000..d26b478 --- /dev/null +++ b/node/cert_test.go @@ -0,0 +1,7 @@ +package node + +import "testing" + +func Test_generateSelfSslCertificate(t *testing.T) { + t.Log(generateSelfSslCertificate("domain.com", "1.pem", "1.key")) +} diff --git a/node/lego.go b/node/lego.go new file mode 100644 index 0000000..e3af377 --- /dev/null +++ b/node/lego.go @@ -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 +} diff --git a/node/lego/cert.go b/node/lego/cert.go deleted file mode 100644 index 1f5a36e..0000000 --- a/node/lego/cert.go +++ /dev/null @@ -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 -} diff --git a/node/lego/lego.go b/node/lego/lego.go deleted file mode 100644 index 9a5c0d1..0000000 --- a/node/lego/lego.go +++ /dev/null @@ -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 -} diff --git a/node/lego/user.go b/node/lego/user.go deleted file mode 100644 index d2d3b18..0000000 --- a/node/lego/user.go +++ /dev/null @@ -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 -} diff --git a/node/lego/lego_test.go b/node/lego_test.go similarity index 92% rename from node/lego/lego_test.go rename to node/lego_test.go index 69b2eab..128014d 100644 --- a/node/lego/lego_test.go +++ b/node/lego_test.go @@ -1,4 +1,4 @@ -package lego +package node import ( "log" @@ -12,7 +12,7 @@ var l *Lego func init() { var err error - l, err = New(&conf.CertConfig{ + l, err = NewLego(&conf.CertConfig{ CertMode: "dns", Email: "test@test.com", CertDomain: "test.test.com",