XrayR/common/mylego/certs_storage.go
2022-11-08 11:57:06 +08:00

163 lines
4.4 KiB
Go

package mylego
import (
"bytes"
"crypto/x509"
"encoding/json"
"log"
"os"
"path/filepath"
"strings"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"golang.org/x/net/idna"
)
const (
baseCertificatesFolderName = "certificates"
)
// CertificatesStorage a certificates' storage.
//
// rootPath:
//
// ./.lego/certificates/
// │ └── root certificates directory
// └── "path" option
//
// archivePath:
//
// ./.lego/archives/
// │ └── archived certificates directory
// └── "path" option
type CertificatesStorage struct {
rootPath string
pem bool
}
// NewCertificatesStorage create a new certificates storage.
func NewCertificatesStorage(path string) *CertificatesStorage {
return &CertificatesStorage{
rootPath: filepath.Join(path, baseCertificatesFolderName),
}
}
func (s *CertificatesStorage) CreateRootFolder() {
err := createNonExistingFolder(s.rootPath)
if err != nil {
log.Panicf("Could not check/create path: %v", err)
}
}
func (s *CertificatesStorage) GetRootPath() string {
return s.rootPath
}
func (s *CertificatesStorage) SaveResource(certRes *certificate.Resource) {
domain := certRes.Domain
// We store the certificate, private key and metadata in different files
// as web servers would not be able to work with a combined file.
err := s.WriteFile(domain, ".crt", certRes.Certificate)
if err != nil {
log.Panicf("Unable to save Certificate for domain %s\n\t%v", domain, err)
}
if certRes.IssuerCertificate != nil {
err = s.WriteFile(domain, ".issuer.crt", certRes.IssuerCertificate)
if err != nil {
log.Panicf("Unable to save IssuerCertificate for domain %s\n\t%v", domain, err)
}
}
if certRes.PrivateKey != nil {
// if we were given a CSR, we don't know the private key
err = s.WriteFile(domain, ".key", certRes.PrivateKey)
if err != nil {
log.Panicf("Unable to save PrivateKey for domain %s\n\t%v", domain, err)
}
if s.pem {
err = s.WriteFile(domain, ".pem", bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil))
if err != nil {
log.Panicf("Unable to save Certificate and PrivateKey in .pem for domain %s\n\t%v", domain, err)
}
}
} else if s.pem {
// we don't have the private key; can't write the .pem file
log.Panicf("Unable to save pem without private key for domain %s\n\t%v; are you using a CSR?", domain, err)
}
jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
if err != nil {
log.Panicf("Unable to marshal CertResource for domain %s\n\t%v", domain, err)
}
err = s.WriteFile(domain, ".json", jsonBytes)
if err != nil {
log.Panicf("Unable to save CertResource for domain %s\n\t%v", domain, err)
}
}
func (s *CertificatesStorage) ReadResource(domain string) certificate.Resource {
raw, err := s.ReadFile(domain, ".json")
if err != nil {
log.Panicf("Error while loading the meta data for domain %s\n\t%v", domain, err)
}
var resource certificate.Resource
if err = json.Unmarshal(raw, &resource); err != nil {
log.Panicf("Error while marshaling the meta data for domain %s\n\t%v", domain, err)
}
return resource
}
func (s *CertificatesStorage) ExistsFile(domain, extension string) bool {
filePath := s.GetFileName(domain, extension)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return false
} else if err != nil {
log.Panic(err)
}
return true
}
func (s *CertificatesStorage) ReadFile(domain, extension string) ([]byte, error) {
return os.ReadFile(s.GetFileName(domain, extension))
}
func (s *CertificatesStorage) GetFileName(domain, extension string) string {
filename := sanitizedDomain(domain) + extension
return filepath.Join(s.rootPath, filename)
}
func (s *CertificatesStorage) ReadCertificate(domain, extension string) ([]*x509.Certificate, error) {
content, err := s.ReadFile(domain, extension)
if err != nil {
return nil, err
}
// The input may be a bundle or a single certificate.
return certcrypto.ParsePEMBundle(content)
}
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
var baseFileName = sanitizedDomain(domain)
filePath := filepath.Join(s.rootPath, baseFileName+extension)
return os.WriteFile(filePath, data, filePerm)
}
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
func sanitizedDomain(domain string) string {
safe, err := idna.ToASCII(strings.ReplaceAll(domain, "*", "_"))
if err != nil {
log.Panic(err)
}
return safe
}