chore: Restructure
This commit is contained in:
109
internal/certificates/certificate.go
Normal file
109
internal/certificates/certificate.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
)
|
||||
|
||||
func ObtainNewCertificate(domains []string, acmeClient *lego.Client) (CertificateWrapper, error) {
|
||||
req := certificate.ObtainRequest{
|
||||
Domains: domains,
|
||||
Bundle: true,
|
||||
}
|
||||
cert, err := acmeClient.Certificate.Obtain(req)
|
||||
if err != nil {
|
||||
return CertificateWrapper{}, err
|
||||
}
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||
if err != nil {
|
||||
return CertificateWrapper{}, err
|
||||
}
|
||||
|
||||
wrapper := CertificateWrapper{
|
||||
TlsCertificate: &tlsCert,
|
||||
Domain: cert.Domain,
|
||||
//NotAfter: tlsCert.Leaf.NotAfter,
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 60),
|
||||
PrivateKeyEncoded: base64.StdEncoding.EncodeToString(cert.PrivateKey),
|
||||
Certificate: cert.Certificate,
|
||||
IssuerCertificate: cert.IssuerCertificate,
|
||||
CertificateUrl: cert.CertURL,
|
||||
}
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
// Generate a fallback certificate for the domain.
|
||||
func MakeFallbackCertificate(pagesDomain string) (*CertificateWrapper, error) {
|
||||
key, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notAfter := time.Now().Add(time.Hour * 24 * 7)
|
||||
cert := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: pagesDomain,
|
||||
Organization: []string{"Pages Server"},
|
||||
},
|
||||
NotAfter: notAfter,
|
||||
NotBefore: time.Now(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
certBytes, err := x509.CreateCertificate(
|
||||
rand.Reader,
|
||||
&cert,
|
||||
&cert,
|
||||
&key.(*rsa.PrivateKey).PublicKey,
|
||||
key,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
err = pem.Encode(out, &pem.Block{
|
||||
Bytes: certBytes,
|
||||
Type: "CERTIFICATE",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outBytes := out.Bytes()
|
||||
res := &certificate.Resource{
|
||||
PrivateKey: certcrypto.PEMEncode(key),
|
||||
Certificate: outBytes,
|
||||
IssuerCertificate: outBytes,
|
||||
Domain: pagesDomain,
|
||||
}
|
||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CertificateWrapper{
|
||||
TlsCertificate: &tlsCertificate,
|
||||
Domain: pagesDomain,
|
||||
NotAfter: notAfter,
|
||||
PrivateKeyEncoded: base64.StdEncoding.EncodeToString(certcrypto.PEMEncode(key)),
|
||||
Certificate: outBytes,
|
||||
IssuerCertificate: outBytes,
|
||||
CertificateUrl: "localhost",
|
||||
}, nil
|
||||
}
|
||||
114
internal/certificates/store.go
Normal file
114
internal/certificates/store.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// A convenience wrapper around a TLS certificate
|
||||
type CertificateWrapper struct {
|
||||
TlsCertificate *tls.Certificate `json:"-"`
|
||||
Domain string `json:"domain"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
PrivateKeyEncoded string `json:"private_key"`
|
||||
Certificate []byte `json:"certificate"`
|
||||
IssuerCertificate []byte `json:"issuer_certificate"`
|
||||
CertificateUrl string `json:"certificate_url"`
|
||||
}
|
||||
|
||||
// A structure to store all the certificates we know of in.
|
||||
type CertificatesCache struct {
|
||||
// The certificate to use as a fallback if all else fails.
|
||||
FallbackCertificate *CertificateWrapper
|
||||
|
||||
// Mapping of domain name to certificate.
|
||||
Certificates map[string]CertificateWrapper
|
||||
}
|
||||
|
||||
// Internal type to let encoding JSON handle the bulk of the work.
|
||||
type certificatesStore struct {
|
||||
FallbackCertificate CertificateWrapper `json:"fallback"`
|
||||
Certificates []CertificateWrapper `json:"certificates"`
|
||||
}
|
||||
|
||||
// Decodes the private key of the certificate wrapper.
|
||||
func (c *CertificateWrapper) GetPrivateKey() *rsa.PrivateKey {
|
||||
data, _ := base64.StdEncoding.DecodeString(c.PrivateKeyEncoded)
|
||||
pk, _ := certcrypto.ParsePEMPrivateKey(data)
|
||||
|
||||
return pk.(*rsa.PrivateKey)
|
||||
}
|
||||
|
||||
// Populate the certificate's TlsCertificate field.
|
||||
func (c *CertificateWrapper) initTlsCertificate() {
|
||||
pk, _ := base64.StdEncoding.DecodeString(c.PrivateKeyEncoded)
|
||||
tlsCert, _ := tls.X509KeyPair(
|
||||
c.Certificate,
|
||||
pk,
|
||||
)
|
||||
c.TlsCertificate = &tlsCert
|
||||
}
|
||||
|
||||
// Checks if the certificate is still valid now.
|
||||
func (c *CertificateWrapper) IsValid() bool {
|
||||
return time.Now().Compare(c.NotAfter) <= -1
|
||||
}
|
||||
|
||||
// Serializes the certificate cache to a JSON string for writing to a file.
|
||||
func (c *CertificatesCache) toStoreData() []byte {
|
||||
certs := make([]CertificateWrapper, 0)
|
||||
for _, cert := range c.Certificates {
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
result, err := json.Marshal(certificatesStore{
|
||||
FallbackCertificate: *c.FallbackCertificate,
|
||||
Certificates: certs,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to Marshal cache: %v", err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Saves the cache to disk.
|
||||
func (c *CertificatesCache) FlushToDisk(path string) {
|
||||
ioutil.WriteFile(path, c.toStoreData(), 0600)
|
||||
}
|
||||
|
||||
func (c *CertificatesCache) AddCert(cert CertificateWrapper, path string) {
|
||||
c.Certificates[cert.Domain] = cert
|
||||
c.FlushToDisk(path)
|
||||
}
|
||||
|
||||
// Load the certificate cache from the file.
|
||||
func CertificateCacheFromFile(path string) (CertificatesCache, error) {
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return CertificatesCache{}, err
|
||||
}
|
||||
|
||||
var store certificatesStore
|
||||
_ = json.Unmarshal(content, &store)
|
||||
|
||||
store.FallbackCertificate.initTlsCertificate()
|
||||
cache := CertificatesCache{
|
||||
FallbackCertificate: &store.FallbackCertificate,
|
||||
}
|
||||
|
||||
certs := make(map[string]CertificateWrapper)
|
||||
for _, cert := range store.Certificates {
|
||||
cert.initTlsCertificate()
|
||||
certs[cert.Domain] = cert
|
||||
}
|
||||
cache.Certificates = certs
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
Reference in New Issue
Block a user