2024-01-01 13:19:19 +00:00
|
|
|
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 {
|
2024-01-02 17:23:46 +00:00
|
|
|
// The parsed TLS certificate we can pass to the tls listener
|
|
|
|
TlsCertificate *tls.Certificate `json:"-"`
|
|
|
|
|
|
|
|
// Key identifying for which domain(s) this certificate is valid.
|
|
|
|
DomainKey string `json:"domain"`
|
|
|
|
|
|
|
|
// Indicates at which point in time this certificate is no longer valid.
|
|
|
|
NotAfter time.Time `json:"not_after"`
|
|
|
|
|
|
|
|
// The encoded private key.
|
|
|
|
PrivateKeyEncoded string `json:"private_key"`
|
|
|
|
|
|
|
|
// The PEM-encoded certificate.
|
|
|
|
Certificate []byte `json:"certificate"`
|
|
|
|
|
|
|
|
// The CSR provided when we requested the certificate.
|
|
|
|
CSR []byte `json:"csr"`
|
2024-01-01 13:19:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2024-01-02 17:23:46 +00:00
|
|
|
// Mapping of a domain's domain key to the certificate.
|
2024-01-01 13:19:19 +00:00
|
|
|
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) {
|
2024-01-02 17:23:46 +00:00
|
|
|
c.Certificates[cert.DomainKey] = cert
|
2024-01-01 13:19:19 +00:00
|
|
|
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()
|
2024-01-02 17:23:46 +00:00
|
|
|
certs[cert.DomainKey] = cert
|
2024-01-01 13:19:19 +00:00
|
|
|
}
|
|
|
|
cache.Certificates = certs
|
|
|
|
|
|
|
|
return cache, nil
|
|
|
|
}
|