{"record":{"id":"94840a23ee5669cd","repo":"Billionmail/BillionMail","slug":"certificate-is-invalid","errorCode":null,"errorMessage":"certificate is invalid","messagePattern":"certificate is invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/mail_service/certificate.go","lineNumber":221,"sourceCode":"\n\treturn nil\n}\n\n// verifyCertificate validates certificate data\nfunc (c *Certificate) verifyCertificate(csrPem, keyPem string) error {\n\t// Check if certificate data is empty\n\tif csrPem == \"\" {\n\t\treturn fmt.Errorf(\"certificate data is empty\")\n\t}\n\tif keyPem == \"\" {\n\t\treturn fmt.Errorf(\"private key data is empty\")\n\t}\n\n\t// Validate certificate\n\tcInfo := acme.GetCertInfo(csrPem)\n\n\tif cInfo.Endtime == 0 {\n\t\treturn fmt.Errorf(\"certificate is invalid\")\n\t}\n\n\treturn nil\n}\n\n// updatePostfixConfig updates Postfix configuration with new certificate\nfunc (c *Certificate) updatePostfixConfig(csrPem, keyPem string) error {\n\tmainCf := public.AbsPath(consts.POSTFIX_MAIN_CONF)\n\tcontent, err := os.ReadFile(mainCf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read postfix config: %v\", err)\n\t}\n\n\t// Write certificate and key to files\n\tcertPath := public.AbsPath(filepath.Join(consts.SSL_PATH, \"postfix.crt\"))\n\tkeyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, \"postfix.key\"))\n\n\tif err := os.WriteFile(certPath, []byte(csrPem), 0755); err != nil {","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/mail_service/certificate.go#L203-L239","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Validate the PEM with openssl x509 -in cert.pem -noout before deploying","Confirm the value is the issued certificate, not the CSR or key","Re-issue/download the certificate from the ACME provider","Check for content corruption in storage (encoding, truncation, escaping)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func certParses(pemStr string) error {\n    blk, _ := pem.Decode([]byte(pemStr))\n    if blk == nil || blk.Type != \"CERTIFICATE\" {\n        return errors.New(\"not a CERTIFICATE PEM block\")\n    }\n    _, err := x509.ParseCertificate(blk.Bytes)\n    return err\n}","typeGuard":"func isValidCertPEM(s string) bool {\n    blk, _ := pem.Decode([]byte(s))\n    if blk == nil || blk.Type != \"CERTIFICATE\" { return false }\n    _, err := x509.ParseCertificate(blk.Bytes)\n    return err == nil\n}","tryCatchPattern":"if err := certService.SetSSL(csrPem, keyPem); err != nil {\n    if strings.Contains(err.Error(), \"certificate is invalid\") {\n        log.Errorf(\"PEM rejected by x509 parse; first 60 chars: %.60s\", csrPem)\n        return ErrBadCertificatePEM\n    }\n    return err\n}","preventionTips":["Store only the issued certificate, never the CSR or ACME response page","Run openssl x509 -noout -enddate as a pre-deploy sanity check","Avoid transformations (trim/escape) that corrupt base64 when persisting PEM"],"tags":["tls","certificate","x509","validation"],"backgroundTag":"invalid-pem-certificate","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}