mirror of
https://github.com/InazumaV/Ratte.git
synced 2026-02-04 04:30:09 +00:00
Initial commit
This commit is contained in:
40
acme/acme.go
Normal file
40
acme/acme.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"Ratte/conf"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
)
|
||||
|
||||
type Acme struct {
|
||||
client *lego.Client
|
||||
c *conf.ACME
|
||||
}
|
||||
|
||||
func NewAcme(c *conf.ACME) (*Acme, error) {
|
||||
user, err := NewLegoUser(
|
||||
path.Join(c.Storage, "user", fmt.Sprintf("user-%s.json", c.Email)),
|
||||
c.Email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create user error: %s", err)
|
||||
}
|
||||
lc := lego.NewConfig(user)
|
||||
//c.CADirURL = "http://192.168.99.100:4000/directory"
|
||||
lc.Certificate.KeyType = certcrypto.RSA2048
|
||||
client, err := lego.NewClient(lc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := Acme{
|
||||
client: client,
|
||||
c: c,
|
||||
}
|
||||
err = l.SetProvider()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("set provider error: %s", err)
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
119
acme/cert.go
Normal file
119
acme/cert.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"Ratte/common/file"
|
||||
"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"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 *Acme) SetProvider() error {
|
||||
switch l.c.Provider {
|
||||
case "http":
|
||||
err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "dns":
|
||||
for k, v := range l.c.DNSEnv {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
p, err := dns.NewDNSChallengeProviderByName(l.c.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)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported provider %s", l.c.Provider)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Acme) CreateCert(certPath, keyPath, domain string) (err error) {
|
||||
if certPath == "" || keyPath == "" {
|
||||
return fmt.Errorf("cert file path or key file path not exist")
|
||||
}
|
||||
if file.IsExist(certPath) && file.IsExist(keyPath) {
|
||||
return l.RenewCert(certPath, keyPath, domain)
|
||||
}
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{domain},
|
||||
Bundle: true,
|
||||
}
|
||||
certificates, err := l.client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("obtain certificate error: %s", err)
|
||||
}
|
||||
err = l.writeCert(certPath, keyPath, certificates)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Acme) RenewCert(certPath, keyPath, domain string) error {
|
||||
file, err := os.ReadFile(certPath)
|
||||
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: domain,
|
||||
Certificate: file,
|
||||
}, true, false, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = l.writeCert(certPath, keyPath, res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Acme) 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 *Acme) writeCert(cert, key string, certificates *certificate.Resource) error {
|
||||
err := checkPath(cert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check path error: %s", err)
|
||||
}
|
||||
err = os.WriteFile(cert, certificates.Certificate, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkPath(key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check path error: %s", err)
|
||||
}
|
||||
err = os.WriteFile(key, certificates.PrivateKey, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
129
acme/user.go
Normal file
129
acme/user.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"Ratte/common/file"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"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 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
|
||||
}
|
||||
Reference in New Issue
Block a user