Billionmail/BillionMail · error

certificate is invalid

Error message

certificate is invalid

What it means

After the emptiness checks, verifyCertificate parses the certificate with acme.GetCertInfo and requires cInfo.Endtime != 0. Endtime == 0 means the PEM could not be parsed as a valid X.509 certificate, so it is reported as 'certificate is invalid'.

Source

Thrown at core/internal/service/mail_service/certificate.go:221

	return nil
}

// verifyCertificate validates certificate data
func (c *Certificate) verifyCertificate(csrPem, keyPem string) error {
	// Check if certificate data is empty
	if csrPem == "" {
		return fmt.Errorf("certificate data is empty")
	}
	if keyPem == "" {
		return fmt.Errorf("private key data is empty")
	}

	// Validate certificate
	cInfo := acme.GetCertInfo(csrPem)

	if cInfo.Endtime == 0 {
		return fmt.Errorf("certificate is invalid")
	}

	return nil
}

// updatePostfixConfig updates Postfix configuration with new certificate
func (c *Certificate) updatePostfixConfig(csrPem, keyPem string) error {
	mainCf := public.AbsPath(consts.POSTFIX_MAIN_CONF)
	content, err := os.ReadFile(mainCf)
	if err != nil {
		return fmt.Errorf("failed to read postfix config: %v", err)
	}

	// Write certificate and key to files
	certPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "postfix.crt"))
	keyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, "postfix.key"))

	if err := os.WriteFile(certPath, []byte(csrPem), 0755); err != nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Validate the PEM with openssl x509 -in cert.pem -noout before deploying
  2. Confirm the value is the issued certificate, not the CSR or key
  3. Re-issue/download the certificate from the ACME provider
  4. Check for content corruption in storage (encoding, truncation, escaping)
Defensive patterns

Strategy: validation

Validate before calling

func certParses(pemStr string) error {
    blk, _ := pem.Decode([]byte(pemStr))
    if blk == nil || blk.Type != "CERTIFICATE" {
        return errors.New("not a CERTIFICATE PEM block")
    }
    _, err := x509.ParseCertificate(blk.Bytes)
    return err
}

Type guard

func isValidCertPEM(s string) bool {
    blk, _ := pem.Decode([]byte(s))
    if blk == nil || blk.Type != "CERTIFICATE" { return false }
    _, err := x509.ParseCertificate(blk.Bytes)
    return err == nil
}

Try / catch

if err := certService.SetSSL(csrPem, keyPem); err != nil {
    if strings.Contains(err.Error(), "certificate is invalid") {
        log.Errorf("PEM rejected by x509 parse; first 60 chars: %.60s", csrPem)
        return ErrBadCertificatePEM
    }
    return err
}

Prevention

When it happens

Trigger: csrPem is non-empty but not a parseable certificate: truncated PEM, wrong content (e.g. a CSR, a private key, or HTML/error page) passed as the cert, or malformed base64 in the PEM body.

Common situations: Saving an ACME error page instead of the issued cert; confusing CSR with certificate; PEM with altered line breaks/base64 corruption in a database text column.

Understand the failure class

Related errors


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