Billionmail/BillionMail · error
failed to parse certificate info: %v
Error message
failed to parse certificate info: %v
What it means
The stored PEM is parsed via acme.GetCertInfo and mapped into certInfo with gconv.Struct. If the PEM is malformed, encrypted, not a certificate, or GetCertInfo returns data that can't map onto certInfo, this error is thrown.
Source
Thrown at core/internal/service/mail_service/certificate.go:519
Where("dns::jsonb ? $1", public.FormatMX(domain)).
Where("status = 1").
Where("endtime > ?", time.Now().Unix()).
Order("endtime desc").
Limit(1).
Scan(&cert)
if err != nil {
return certInfo, fmt.Errorf("certificate not found in database: %v", err)
}
if cert.Certificate == "" {
return certInfo, fmt.Errorf("certificate content is empty in database")
}
// Parse certificate information
err = gconv.Struct(acme.GetCertInfo(cert.Certificate), &certInfo)
if err != nil {
return certInfo, fmt.Errorf("failed to parse certificate info: %v", err)
}
// Set certificate content
certInfo.CertPem = cert.Certificate
certInfo.KeyPem = cert.PrivateKey
return certInfo, nil
}
// getSSLInfoFromFiles retrieves SSL certificate from file system (legacy method)
func (c *Certificate) getSSLInfoFromFiles(domain string) (certInfo v1.CertInfo, err error) {
csrPath := filepath.Join(consts.SSL_PATH, domain, "/fullchain.pem")
keyPath := filepath.Join(consts.SSL_PATH, domain, "/privkey.pem")
if !c.checkCertificateFiles(csrPath, keyPath) {
return certInfo, nil
}
View on GitHub (pinned to fc36c76c05)
Solutions
- Validate the stored value is a full PEM cert (-----BEGIN CERTIFICATE----- ... END CERTIFICATE-----) and re-upload if not.
- Re-issue the certificate through the standard flow to regenerate clean PEM.
- Confirm you didn't store the private key or chain-only content in the certificate column.
- Check acme.GetCertInfo output for nil/err before gconv conversion to isolate which half fails.
Example fix
// before
certInfo.CertPem = cert.Certificate
// after
if !strings.Contains(cert.Certificate, "BEGIN CERTIFICATE") {
return certInfo, errors.New("stored value is not a PEM certificate")
}
certInfo.CertPem = cert.Certificate Defensive patterns
Strategy: validation
Validate before calling
block, _ := pem.Decode([]byte(certPem))
if block == nil || block.Type != "CERTIFICATE" {
return errors.New("stored certificate is not a valid PEM CERTIFICATE block")
}
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
return fmt.Errorf("stored certificate does not parse: %w", err)
} Type guard
func isValidPEMCert(s string) bool {
block, _ := pem.Decode([]byte(s))
return block != nil && block.Type == "CERTIFICATE"
} Try / catch
info, err := GetSSLInfo(ctx)
if err != nil {
if strings.Contains(err.Error(), "failed to parse certificate info") {
log.Printf("corrupt stored cert: %v — reissuing", err)
return reissueCertificate(ctx)
}
return err
} Prevention
- Validate PEM with pem.Decode + x509.ParseCertificate before storing.
- Never paste certificates manually — upload through the UI which validates format.
- Check that cert and key weren't swapped at upload time.
When it happens
Trigger: GetSSLInfo loads a cert row whose certificate column holds corrupt/truncated PEM, a private key instead of a cert, or an unsupported format that acme.GetCertInfo cannot decode.
Common situations: Manual copy-paste of the certificate introduced whitespace/wrong blocks; upload swapped cert and key; certificate file truncated during transfer; PEM with extra non-cert blocks.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- fail to set ssl: %w
- Failed to create directory: {}
- Failed to save certificate file: {}
- invalid certificate
- certificate not found in database: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/4e8971e37095707e.
Report an issue: GitHub.