From a2485ec6fabbdd34c64da858da672dc0821092fe Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Thu, 2 Oct 2025 00:32:48 +0200 Subject: [PATCH] Remove decrypt key by default Signed-off-by: Bryan Frimin --- pkg/certmanager/cache_store.go | 13 ++- pkg/certmanager/provisioner.go | 4 +- pkg/certmanager/renewer.go | 4 +- pkg/certmanager/selector.go | 15 ++- pkg/coredata/cached_certificate.go | 12 ++- pkg/coredata/custom_domain.go | 153 +++++++++++------------------ 6 files changed, 94 insertions(+), 107 deletions(-) diff --git a/pkg/certmanager/cache_store.go b/pkg/certmanager/cache_store.go index e9c55678f..50acfba06 100644 --- a/pkg/certmanager/cache_store.go +++ b/pkg/certmanager/cache_store.go @@ -99,15 +99,20 @@ func (w *CacheStore) warmDomain(ctx context.Context, conn pg.Conn, domain *cored return fmt.Errorf("cannot load domain with decrypted values: %w", err) } - if loadedDomain.SSLCertificate == nil { - return fmt.Errorf("domain has no parsed certificate") + if err := loadedDomain.ParseCertificate(w.encryptionKey); err != nil { + return fmt.Errorf("cannot parse certificate: %w", err) } if len(loadedDomain.SSLCertificatePEM) == 0 { return fmt.Errorf("domain has no certificate PEM") } - if len(loadedDomain.SSLPrivateKeyPEM) == 0 { + privateKeyPEM, err := loadedDomain.DecryptPrivateKey(w.encryptionKey) + if err != nil { + return fmt.Errorf("cannot decrypt private key: %w", err) + } + + if len(privateKeyPEM) == 0 { return fmt.Errorf("domain has no private key PEM") } @@ -122,7 +127,7 @@ func (w *CacheStore) warmDomain(ctx context.Context, conn pg.Conn, domain *cored cache := &coredata.CachedCertificate{ Domain: loadedDomain.Domain, CertificatePEM: string(loadedDomain.SSLCertificatePEM), - PrivateKeyPEM: string(loadedDomain.SSLPrivateKeyPEM), + PrivateKeyPEM: string(privateKeyPEM), CertificateChain: loadedDomain.SSLCertificateChain, ExpiresAt: *loadedDomain.SSLExpiresAt, CachedAt: time.Now(), diff --git a/pkg/certmanager/provisioner.go b/pkg/certmanager/provisioner.go index f2f06542b..caaaee759 100644 --- a/pkg/certmanager/provisioner.go +++ b/pkg/certmanager/provisioner.go @@ -188,7 +188,9 @@ func (p *Provisioner) provisionDomainCertificate( } fullDomain.SSLCertificatePEM = cert.CertPEM - fullDomain.SSLPrivateKeyPEM = cert.KeyPEM + if err := fullDomain.EncryptPrivateKey(cert.KeyPEM, p.encryptionKey); err != nil { + return fmt.Errorf("cannot encrypt private key: %w", err) + } chainStr := string(cert.ChainPEM) fullDomain.SSLCertificateChain = &chainStr fullDomain.SSLExpiresAt = &cert.ExpiresAt diff --git a/pkg/certmanager/renewer.go b/pkg/certmanager/renewer.go index 8296608b5..8916ef97c 100644 --- a/pkg/certmanager/renewer.go +++ b/pkg/certmanager/renewer.go @@ -182,7 +182,9 @@ func (r *Renewer) renewDomain(ctx context.Context, conn pg.Conn, domain *coredat ) lockedDomain.SSLCertificatePEM = cert.CertPEM - lockedDomain.SSLPrivateKeyPEM = cert.KeyPEM + if err := lockedDomain.EncryptPrivateKey(cert.KeyPEM, r.encryptionKey); err != nil { + return fmt.Errorf("cannot encrypt private key: %w", err) + } chainStr := string(cert.ChainPEM) lockedDomain.SSLCertificateChain = &chainStr lockedDomain.SSLExpiresAt = &cert.ExpiresAt diff --git a/pkg/certmanager/selector.go b/pkg/certmanager/selector.go index be53a66d7..222b06595 100644 --- a/pkg/certmanager/selector.go +++ b/pkg/certmanager/selector.go @@ -117,16 +117,21 @@ func (s *Selector) rebuildCacheEntry(ctx context.Context, conn pg.Conn, domain s return fmt.Errorf("domain does not have active SSL certificate") } - if customDomain.SSLCertificate == nil { - return fmt.Errorf("domain has no parsed certificate") + if err := customDomain.ParseCertificate(s.encryptionKey); err != nil { + return fmt.Errorf("cannot parse certificate: %w", err) } if len(customDomain.SSLCertificatePEM) == 0 { return fmt.Errorf("domain has no certificate PEM data") } - if len(customDomain.SSLPrivateKeyPEM) == 0 { - return fmt.Errorf("domain has no private key PEM data") + if len(customDomain.EncryptedSSLPrivateKey) == 0 { + return fmt.Errorf("domain has no encrypted private key data") + } + + privateKeyPEM, err := customDomain.DecryptPrivateKey(s.encryptionKey) + if err != nil { + return fmt.Errorf("cannot decrypt private key: %w", err) } s.cache.Store(domain, customDomain.SSLCertificate) @@ -134,7 +139,7 @@ func (s *Selector) rebuildCacheEntry(ctx context.Context, conn pg.Conn, domain s cache := &coredata.CachedCertificate{ Domain: customDomain.Domain, CertificatePEM: string(customDomain.SSLCertificatePEM), - PrivateKeyPEM: string(customDomain.SSLPrivateKeyPEM), + PrivateKeyPEM: string(privateKeyPEM), CertificateChain: customDomain.SSLCertificateChain, ExpiresAt: *customDomain.SSLExpiresAt, CachedAt: time.Now(), diff --git a/pkg/coredata/cached_certificate.go b/pkg/coredata/cached_certificate.go index 2d13d55d7..4c8a251cc 100644 --- a/pkg/coredata/cached_certificate.go +++ b/pkg/coredata/cached_certificate.go @@ -19,6 +19,7 @@ import ( "fmt" "time" + "github.com/getprobo/probo/pkg/crypto/cipher" "github.com/getprobo/probo/pkg/gid" "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" @@ -169,7 +170,7 @@ WHERE return nil } -func (cc *CachedCertificate) RefreshFromDomain(ctx context.Context, conn pg.Conn, domain *CustomDomain) error { +func (cc *CachedCertificate) RefreshFromDomain(ctx context.Context, conn pg.Conn, domain *CustomDomain, encryptionKey cipher.EncryptionKey) error { if domain.SSLCertificate == nil { return fmt.Errorf("domain has no parsed certificate") } @@ -178,7 +179,12 @@ func (cc *CachedCertificate) RefreshFromDomain(ctx context.Context, conn pg.Conn return fmt.Errorf("domain has no certificate PEM") } - if len(domain.SSLPrivateKeyPEM) == 0 { + privateKeyPEM, err := domain.DecryptPrivateKey(encryptionKey) + if err != nil { + return fmt.Errorf("cannot decrypt private key: %w", err) + } + + if len(privateKeyPEM) == 0 { return fmt.Errorf("domain has no private key PEM") } @@ -189,7 +195,7 @@ func (cc *CachedCertificate) RefreshFromDomain(ctx context.Context, conn pg.Conn cache := &CachedCertificate{ Domain: domain.Domain, CertificatePEM: string(domain.SSLCertificatePEM), - PrivateKeyPEM: string(domain.SSLPrivateKeyPEM), + PrivateKeyPEM: string(privateKeyPEM), CertificateChain: domain.SSLCertificateChain, ExpiresAt: *domain.SSLExpiresAt, CachedAt: time.Now(), diff --git a/pkg/coredata/custom_domain.go b/pkg/coredata/custom_domain.go index 6048f9e5a..77760d80a 100644 --- a/pkg/coredata/custom_domain.go +++ b/pkg/coredata/custom_domain.go @@ -38,7 +38,6 @@ type ( HTTPOrderURL *string `db:"http_order_url"` SSLCertificate *tls.Certificate `db:"-"` SSLCertificatePEM []byte `db:"ssl_certificate"` - SSLPrivateKeyPEM []byte `db:"-"` EncryptedSSLPrivateKey []byte `db:"encrypted_ssl_private_key"` SSLCertificateChain *string `db:"ssl_certificate_chain"` SSLStatus CustomDomainSSLStatus `db:"ssl_status"` @@ -74,6 +73,62 @@ func (cd *CustomDomain) CursorKey(field CustomDomainOrderField) page.CursorKey { panic(fmt.Sprintf("unsupported order by: %s", field)) } +func (cd *CustomDomain) DecryptPrivateKey(encryptionKey cipher.EncryptionKey) ([]byte, error) { + if len(cd.EncryptedSSLPrivateKey) == 0 { + return nil, nil + } + + decrypted, err := cipher.Decrypt(cd.EncryptedSSLPrivateKey, encryptionKey) + if err != nil { + return nil, fmt.Errorf("cannot decrypt SSL private key: %w", err) + } + + return decrypted, nil +} + +func (cd *CustomDomain) EncryptPrivateKey(privateKeyPEM []byte, encryptionKey cipher.EncryptionKey) error { + if len(privateKeyPEM) == 0 { + cd.EncryptedSSLPrivateKey = nil + return nil + } + + encrypted, err := cipher.Encrypt(privateKeyPEM, encryptionKey) + if err != nil { + return fmt.Errorf("cannot encrypt SSL private key: %w", err) + } + + cd.EncryptedSSLPrivateKey = encrypted + return nil +} + +func (cd *CustomDomain) ParseCertificate(encryptionKey cipher.EncryptionKey) error { + if len(cd.SSLCertificatePEM) == 0 { + return fmt.Errorf("no certificate PEM data") + } + + privateKeyPEM, err := cd.DecryptPrivateKey(encryptionKey) + if err != nil { + return fmt.Errorf("cannot decrypt private key: %w", err) + } + + if len(privateKeyPEM) == 0 { + return fmt.Errorf("no private key data") + } + + fullCertPEM := string(cd.SSLCertificatePEM) + if cd.SSLCertificateChain != nil && *cd.SSLCertificateChain != "" { + fullCertPEM += "\n" + *cd.SSLCertificateChain + } + + tlsCert, err := tls.X509KeyPair([]byte(fullCertPEM), privateKeyPEM) + if err != nil { + return fmt.Errorf("cannot parse certificate and key: %w", err) + } + + cd.SSLCertificate = &tlsCert + return nil +} + func (cd *CustomDomain) LoadByID( ctx context.Context, conn pg.Conn, @@ -121,29 +176,6 @@ LIMIT 1 *cd = customDomain - // Decrypt SSL private key - if len(cd.EncryptedSSLPrivateKey) > 0 { - decrypted, err := cipher.Decrypt(cd.EncryptedSSLPrivateKey, encryptionKey) - if err != nil { - return fmt.Errorf("cannot decrypt SSL private key: %w", err) - } - cd.SSLPrivateKeyPEM = decrypted - } - - // Parse certificate and key into tls.Certificate if both are present - if len(cd.SSLCertificatePEM) > 0 && len(cd.SSLPrivateKeyPEM) > 0 { - fullCertPEM := string(cd.SSLCertificatePEM) - if cd.SSLCertificateChain != nil && *cd.SSLCertificateChain != "" { - fullCertPEM += "\n" + *cd.SSLCertificateChain - } - - tlsCert, err := tls.X509KeyPair([]byte(fullCertPEM), cd.SSLPrivateKeyPEM) - if err != nil { - return fmt.Errorf("cannot parse certificate and key: %w", err) - } - cd.SSLCertificate = &tlsCert - } - return nil } @@ -194,29 +226,6 @@ FOR UPDATE *cd = customDomain - // Decrypt SSL private key - if len(cd.EncryptedSSLPrivateKey) > 0 { - decrypted, err := cipher.Decrypt(cd.EncryptedSSLPrivateKey, encryptionKey) - if err != nil { - return fmt.Errorf("cannot decrypt SSL private key: %w", err) - } - cd.SSLPrivateKeyPEM = decrypted - } - - // Parse certificate and key into tls.Certificate if both are present - if len(cd.SSLCertificatePEM) > 0 && len(cd.SSLPrivateKeyPEM) > 0 { - fullCertPEM := string(cd.SSLCertificatePEM) - if cd.SSLCertificateChain != nil && *cd.SSLCertificateChain != "" { - fullCertPEM += "\n" + *cd.SSLCertificateChain - } - - tlsCert, err := tls.X509KeyPair([]byte(fullCertPEM), cd.SSLPrivateKeyPEM) - if err != nil { - return fmt.Errorf("cannot parse certificate and key: %w", err) - } - cd.SSLCertificate = &tlsCert - } - return nil } @@ -267,29 +276,6 @@ LIMIT 1 *cd = customDomain - // Decrypt SSL private key - if len(cd.EncryptedSSLPrivateKey) > 0 { - decrypted, err := cipher.Decrypt(cd.EncryptedSSLPrivateKey, encryptionKey) - if err != nil { - return fmt.Errorf("cannot decrypt SSL private key: %w", err) - } - cd.SSLPrivateKeyPEM = decrypted - } - - // Parse certificate and key into tls.Certificate if both are present - if len(cd.SSLCertificatePEM) > 0 && len(cd.SSLPrivateKeyPEM) > 0 { - fullCertPEM := string(cd.SSLCertificatePEM) - if cd.SSLCertificateChain != nil && *cd.SSLCertificateChain != "" { - fullCertPEM += "\n" + *cd.SSLCertificateChain - } - - tlsCert, err := tls.X509KeyPair([]byte(fullCertPEM), cd.SSLPrivateKeyPEM) - if err != nil { - return fmt.Errorf("cannot parse certificate and key: %w", err) - } - cd.SSLCertificate = &tlsCert - } - return nil } @@ -300,12 +286,8 @@ func (cd *CustomDomain) Insert( encryptionKey cipher.EncryptionKey, ) error { var encryptedKey []byte - if len(cd.SSLPrivateKeyPEM) > 0 { - var err error - encryptedKey, err = cipher.Encrypt(cd.SSLPrivateKeyPEM, encryptionKey) - if err != nil { - return fmt.Errorf("cannot encrypt SSL private key: %w", err) - } + if len(cd.EncryptedSSLPrivateKey) > 0 { + encryptedKey = cd.EncryptedSSLPrivateKey } q := ` @@ -376,12 +358,8 @@ func (cd *CustomDomain) Update( encryptionKey cipher.EncryptionKey, ) error { var encryptedKey []byte - if len(cd.SSLPrivateKeyPEM) > 0 { - var err error - encryptedKey, err = cipher.Encrypt(cd.SSLPrivateKeyPEM, encryptionKey) - if err != nil { - return fmt.Errorf("cannot encrypt SSL private key: %w", err) - } + if len(cd.EncryptedSSLPrivateKey) > 0 { + encryptedKey = cd.EncryptedSSLPrivateKey } q := ` @@ -651,17 +629,6 @@ WHERE return fmt.Errorf("cannot collect custom domains: %w", err) } - for _, cd := range result { - // Decrypt SSL private key - if len(cd.EncryptedSSLPrivateKey) > 0 { - decrypted, err := cipher.Decrypt(cd.EncryptedSSLPrivateKey, encryptionKey) - if err != nil { - return fmt.Errorf("cannot decrypt SSL private key: %w", err) - } - cd.SSLPrivateKeyPEM = decrypted - } - } - *domains = result return nil }