refactor lego

This commit is contained in:
yuzuki999
2023-01-12 14:27:06 +08:00
parent 05ac7c19f6
commit 405cd246e1
30 changed files with 461 additions and 4070 deletions

View File

@@ -0,0 +1,110 @@
package lego
import (
"fmt"
"github.com/Yuzuki616/V2bX/common/file"
"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"
"path"
"strings"
"time"
)
func (l *Lego) SetProvider() error {
switch l.config.CertMode {
case "http":
err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002"))
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) parseParmas(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 {
if !file.IsExist(path.Dir(l.config.CertFile)) {
err := os.MkdirAll(path.Dir(l.config.CertFile), 0755)
if err != nil {
return fmt.Errorf("create dir error: %s", err)
}
}
err := os.WriteFile(l.parseParmas(l.config.CertFile), certificates.Certificate, 0644)
if err != nil {
return err
}
if !file.IsExist(path.Dir(l.config.KeyFile)) {
err := os.MkdirAll(path.Dir(l.config.CertFile), 0755)
if err != nil {
return fmt.Errorf("create dir error: %s", err)
}
}
err = os.WriteFile(l.parseParmas(l.config.KeyFile), certificates.PrivateKey, 0644)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,51 @@
package lego
import (
"fmt"
"github.com/Yuzuki616/V2bX/common/file"
"github.com/Yuzuki616/V2bX/conf"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/lego"
"os"
"path"
)
type Lego struct {
client *lego.Client
config *conf.CertConfig
}
func New(config *conf.CertConfig) (*Lego, error) {
user, err := NewUser(path.Join("/etc/V2bX/cert/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
}

View File

@@ -0,0 +1,40 @@
package lego
import (
"github.com/Yuzuki616/V2bX/conf"
"log"
"os"
"testing"
)
var l *Lego
func init() {
var err error
l, err = New(&conf.CertConfig{
CertMode: "dns",
Email: "test@test.com",
CertDomain: "test.test.com",
Provider: "cloudflare",
DNSEnv: map[string]string{
"CF_DNS_API_TOKEN": "123",
},
CertFile: "./cert/1.pem",
KeyFile: "./cert/1.key",
})
if err != nil {
log.Println(err)
os.Exit(1)
}
}
func TestLego_CreateCertByDns(t *testing.T) {
err := l.CreateCert()
if err != nil {
t.Error(err)
}
}
func TestLego_RenewCert(t *testing.T) {
log.Println(l.RenewCert())
}

View File

@@ -0,0 +1,129 @@
package lego
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/Yuzuki616/V2bX/common/file"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/goccy/go-json"
"os"
)
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
}