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

  1. Validate the stored value is a full PEM cert (-----BEGIN CERTIFICATE----- ... END CERTIFICATE-----) and re-upload if not.
  2. Re-issue the certificate through the standard flow to regenerate clean PEM.
  3. Confirm you didn't store the private key or chain-only content in the certificate column.
  4. 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

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

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/4e8971e37095707e. Report an issue: GitHub.