rio/internal/certificates/store.go
Alexander "PapaTutuWawa 3692168346
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat: First steps towards using wildcard certificates
2024-01-02 18:23:46 +01:00

125 lines
3.3 KiB
Go

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
}