package letsencrypt
import (
internalcertapi "envoy-control-plane/internal/pkg/cert/api"
"fmt"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/registration"
)
// ------------------------------------------------------------------------------------------------
// RenewCertificate renews an existing certificate using go-acme/lego.
func (l *LetsEncryptIssuer) RenewCertificate(oldCert *internalcertapi.Certificate, webrootPath string, email string) (*internalcertapi.Certificate, error) {
// FIX 1: Load the ACME Account Private Key from the D-value stored in AccountKey.
acmePrivateKey, err := loadACMEKeyFromDValue(oldCert.AccountKey)
if err != nil {
return nil, fmt.Errorf("failed to load ACME account key: %w", err)
}
// 2. Setup ACME User (LEOptions) with the loaded account key
acmeUser := &LEOptions{
Email: email,
key: acmePrivateKey,
// FIX 2: Provide the Registration URI (KID) to the client for JWS signing.
// ASSUMPTION: The stored oldCert.AccountURL contains the ACME account URI (KID).
Registration: ®istration.Resource{
URI: oldCert.AccountURL,
},
}
// 3. Configure and create the ACME client
client, err := createClient(acmeUser, webrootPath, l.UseStaging)
if err != nil {
return nil, err
}
// 4. Reconstruct the certificate.Resource for renewal
certResource := certificate.Resource{
Domain: oldCert.Domain,
Certificate: oldCert.CertPEM,
// The PrivateKey here is the DOMAIN's old private key.
PrivateKey: oldCert.KeyPEM,
}
// 5. Renew the certificate
newCertResources, err := client.Certificate.Renew(certResource, false, false, "")
if err != nil {
// Fallback: If renewal fails (e.g., certificate not found on ACME server, 404), try obtaining a fresh certificate.
// This handles cases where the certificate is expired beyond the renewal window or revoked/missing on the CA side.
fmt.Printf("Warning: Failed to renew certificate for domain %s: %v. Attempting fresh issuance...\n", oldCert.Domain, err)
request := certificate.ObtainRequest{
Domains: []string{oldCert.Domain},
Bundle: true,
}
newCertResources, err = client.Certificate.Obtain(request)
if err != nil {
return nil, fmt.Errorf("failed to renew certificate and failed fallback issuance: %w", err)
}
}
// 6. Map the results
return &internalcertapi.Certificate{
Domain: oldCert.Domain,
CertPEM: newCertResources.Certificate,
// The renewed key (newCertResources.PrivateKey) is the domain's NEW private key.
KeyPEM: newCertResources.PrivateKey,
FullChain: append(newCertResources.Certificate, newCertResources.IssuerCertificate...), // Ensure full chain is stored
// The ACME account key and URL remain the same.
AccountKey: oldCert.AccountKey,
AccountURL: oldCert.AccountURL,
// The rotation strategy remains the same.
EnableRotation: oldCert.EnableRotation,
RenewBefore: oldCert.RenewBefore,
}, nil
}