{"record":{"id":"9b32e6030f76f9bc","repo":"apache/pulsar","slug":"tlspolicy-sets-certificatefilepath-certificatefil","errorCode":null,"errorMessage":"TlsPolicy sets certificateFilePath='certificateFilePath' but leaves keyFilePath unset; a certificate without its private key yields no usable TLS identity. Set keyFilePath, or unset certificateFilePath.","messagePattern":"TlsPolicy sets certificateFilePath='certificateFilePath' but leaves keyFilePath unset; a certificate without its private key yields no usable TLS identity\\. Set keyFilePath, or unset certificateFilePath\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"critical","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java","lineNumber":198,"sourceCode":"                    + \"' holds no usable key entry (a private key with an X.509 certificate chain); no TLS \"\n                    + \"identity would be presented. Fix the keystore or its password, or unset keyStorePath.\");\n        }\n    }\n\n    /**\n     * Reject a half-configured PEM identity that would be silently dropped. A certificate without its key\n     * yields {@link TlsMaterial#hasKeyMaterial()} {@code == false}, so the identity is omitted from the built\n     * context and the misconfiguration only surfaces as a handshake/authentication failure much later. The\n     * check is deliberately <em>asymmetric</em>: a key without a certificate is what v4 silently tolerated, so\n     * it stays a WARN rather than a new startup failure. Enforced here rather than in {@code TlsPolicy.Builder}\n     * so custom {@code PulsarTlsFactory} implementations that build their own policies are not constrained by\n     * this default factory's requirement.\n     */\n    private void validatePemIdentity() {\n        boolean hasCert = StringUtils.isNotBlank(policy.certificateFilePath());\n        boolean hasKey = StringUtils.isNotBlank(policy.keyFilePath());\n        if (hasCert && !hasKey) {\n            throw new IllegalArgumentException(\"TlsPolicy sets certificateFilePath='\" + policy.certificateFilePath()\n                    + \"' but leaves keyFilePath unset; a certificate without its private key yields no usable TLS \"\n                    + \"identity. Set keyFilePath, or unset certificateFilePath.\");\n        }\n        if (hasKey && !hasCert) {\n            log.warn().attr(\"keyFilePath\", policy.keyFilePath())\n                    .log(\"TlsPolicy sets keyFilePath but no certificateFilePath; no TLS identity will be presented\");\n        }\n    }\n\n    private List<X509Certificate> loadTrustCerts() throws Exception {\n        if (StringUtils.isNotBlank(policy.trustStorePath())) {\n            List<X509Certificate> trustCerts = TlsKeyStoreLoader.extractTrustCerts(\n                    TlsKeyStoreLoader.loadKeyStore(policy.trustStoreType(), policy.trustStorePath(),\n                            policy.trustStorePassword(), jcaProvider));\n            if (trustCerts.isEmpty()) {\n                // An empty trust list is indistinguishable from \"no truststore configured\" downstream, and both\n                // context builders then install the platform default trust manager — silently trusting every\n                // public CA. v4 initialised the TrustManagerFactory with the explicit store and rejected every peer.","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java#L180-L216","documentation":"TlsMaterialSource.validatePemIdentity enforces PEM config consistency: certificateFilePath set without keyFilePath cannot yield a usable TLS identity (a certificate alone is public material), so IllegalArgumentException is thrown. The inverse (key without cert) is only logged as a warning elsewhere in the same method.","triggerScenarios":"Calling load on a TlsMaterialSource where policy.certificateFilePath() is non-blank but policy.keyFilePath() is blank or unset, detected before any material loading.","commonSituations":"Partial PEM config where only the certificate path property was set in broker.conf/client config; typo'd key-file property name; a config template that omitted keyFilePath; incorrectly split server/client TLS configs.","solutions":["Set keyFilePath to the private key matching certificateFilePath","If only trust material was intended, unset certificateFilePath and configure trust material instead","Check the config file so both certificate and key path properties are present","After fixing paths, verify the PEM pair matches (cert and key belong together)"],"exampleFix":"// before\nTlsPolicy policy = TlsPolicy.builder()\n    .certificateFilePath(\"/etc/pulsar/broker-cert.pem\")\n    .build(); // keyFilePath missing\n// after\nTlsPolicy policy = TlsPolicy.builder()\n    .certificateFilePath(\"/etc/pulsar/broker-cert.pem\")\n    .keyFilePath(\"/etc/pulsar/broker-key.pem\")\n    .build();","handlingStrategy":"validation","validationCode":"static void checkPemPair(String certPath, String keyPath) {\n    boolean hasCert = certPath != null && !certPath.isBlank();\n    boolean hasKey = keyPath != null && !keyPath.isBlank();\n    if (hasCert && !hasKey) throw new IllegalArgumentException(\"certificateFilePath set without keyFilePath\");\n    if (!hasCert && hasKey) log.warn(\"keyFilePath set without certificateFilePath\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    TlsMaterialSource.load(policy);\n} catch (IllegalArgumentException e) {\n    log.error(\"PEM identity config invalid: {}\", e.getMessage());\n}","preventionTips":["Always configure certificate and key PEM paths together","Use config templates that pair tlsCertificateFilePath with tlsKeyFilePath","Validate PEM config symmetry at application startup"],"tags":["tls","pem","missing-configuration","private-key"],"backgroundTag":"cert-without-private-key","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}