{"record":{"id":"915ab320f7839b20","repo":"Billionmail/BillionMail","slug":"private-key-data-is-empty","errorCode":null,"errorMessage":"private key data is empty","messagePattern":"private key data is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/mail_service/certificate.go","lineNumber":214,"sourceCode":"\t\treturn err\n\t}\n\n\t// Restart Dovecot service\n\tif err := c.restartDovecot(); err != nil {\n\t\treturn err\n\t}\n\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)","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/mail_service/certificate.go#L196-L232","documentation":"Validation guard in verifyCertificate: the keyPem argument (TLS private key PEM) is an empty string, so the certificate/key pair cannot be validated before being installed into Postfix/Dovecot. verifyCertificate rejects it before calling acme.GetCertInfo, since cert data without a key is unusable for TLS.","triggerScenarios":"Calling SetSSL/SetSNI/SetPostfixSSL/SetDovecotSSL/SetPostfixVMailCert with keyPem == \"\" — key not yet generated, wrong field read from the store, or key lost during export.","commonSituations":"ACME flow that stored only the cert; migrating certificates and losing the .key file; copy/paste omitting the key PEM block.","solutions":["Ensure the private key PEM is present and passed as the second argument","Re-export or regenerate the key pair if the key is truly lost","Verify field mapping when loading cert/key from the database or API response","Validate inputs before the call the same way verifyCertificate does"],"exampleFix":"// before\nif err := c.SetSSL(certPem, \"\"); err != nil { ... }\n// after\nif keyPem == \"\" {\n    return errors.New(\"private key PEM is required to deploy the certificate\")\n}\nif err := c.SetSSL(certPem, keyPem); err != nil { ... }","handlingStrategy":"validation","validationCode":"func readyToDeploy(certPem, keyPem string) error {\n    if strings.TrimSpace(keyPem) == \"\" {\n        return errors.New(\"private key PEM is empty; regenerate or re-export the key\")\n    }\n    return nil\n}","typeGuard":"func hasPrivateKeyPEM(s string) bool {\n    return strings.Contains(s, \"-----BEGIN \") && strings.Contains(s, \"PRIVATE KEY-----\")\n}","tryCatchPattern":"if err := certService.SetSSL(csrPem, keyPem); err != nil {\n    if strings.Contains(err.Error(), \"private key data is empty\") {\n        return fmt.Errorf(\"missing key for certificate; re-export key pair: %w\", err)\n    }\n    return err\n}","preventionTips":["Always persist the key alongside the cert at issuance time","Back up the key pair before migrations","Assert key PEM presence in integration tests for all Set* paths"],"tags":["tls","certificate","private-key","validation"],"backgroundTag":"empty-certificate-data","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}