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 { // 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"` } // 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 a domain's domain key to the 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.DomainKey] = 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.DomainKey] = cert } cache.Certificates = certs return cache, nil }