{"record":{"id":"b7dfc4d18faab1ab","repo":"apache/pulsar","slug":"configured-keystore-keystorepath-holds-no-usable","errorCode":null,"errorMessage":"Configured keystore 'keyStorePath' holds no usable key entry (a private key with an X.509 certificate chain); no TLS identity would be presented. Fix the keystore or its password, or unset keyStorePath.","messagePattern":"Configured keystore 'keyStorePath' holds no usable key entry \\(a private key with an X\\.509 certificate chain\\); no TLS identity would be presented\\. Fix the keystore or its password, or unset keyStorePath\\.","errorType":"exception","errorClass":"KeyStoreException","httpStatus":null,"severity":"critical","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java","lineNumber":179,"sourceCode":"            TlsMaterial.KeyEntry first = entries.get(0);\n            return new TlsMaterial(first.privateKey(), first.chain(), trustCerts, entries);\n        }\n        validatePemIdentity();\n        return new TlsMaterial(loadPemPrivateKey(), loadPemCertificateChain(), trustCerts);\n    }\n\n    /**\n     * Reject a keystore that holds no usable key entry. v4 handed such a store to\n     * {@code KeyManagerFactory.init}, which initialised fine but produced a key manager with no aliases — a\n     * certain, undiagnosed handshake failure for a server and a silently identity-less client. Failing the\n     * load is a deliberate tightening: the only deployments it can break already presented no identity.\n     *\n     * @param entries the key entries extracted from the configured keystore\n     * @throws KeyStoreException if the keystore carries no usable key entry\n     */\n    private void validateKeyStoreIdentity(List<TlsMaterial.KeyEntry> entries) throws KeyStoreException {\n        if (entries.isEmpty()) {\n            throw new KeyStoreException(\"Configured keystore '\" + policy.keyStorePath()\n                    + \"' 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) {","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java#L161-L197","documentation":"TlsMaterialSource.load extracts key entries from the configured keystore and validateKeyStoreIdentity checks that at least one entry is a private key with an X.509 certificate chain. If none is found, the keystore presents no TLS identity and KeyStoreException is thrown with remediation guidance naming the keystore path.","triggerScenarios":"Calling load on a TlsMaterialSource whose policy.keyStorePath() points to a keystore with no usable key entries — e.g. a trust-only keystore (trusted certs only), an empty/corrupt store, or a wrong password causing an unintended load.","commonSituations":"Pointing keyStorePath at the truststore instead of the identity keystore; keystore created without a key pair; wrong keyStorePassword loading an unintended store; alias selection filtering out all key entries.","solutions":["Verify keyStorePath points to a keystore containing a PrivateKeyEntry with certificate chain, not a truststore","Inspect with keytool -list -v -keystore broker.keystore.jks and confirm a key entry exists","Correct keyStorePassword; regenerate or re-import the key pair if the store is empty","If only trust is intended, unset keyStorePath so it is not treated as identity material"],"exampleFix":"// before\nTlsPolicy policy = TlsPolicy.builder().keyStorePath(\"/etc/pulsar/truststore.jks\").build(); // trust-only\n// after\nTlsPolicy policy = TlsPolicy.builder()\n    .keyStorePath(\"/etc/pulsar/broker.keystore.jks\") // contains PrivateKeyEntry\n    .keyStorePassword(\"********\")\n    .build();","handlingStrategy":"validation","validationCode":"static boolean keystoreHasIdentity(String path, char[] password) throws Exception {\n    KeyStore ks = KeyStore.getInstance(\"JKS\");\n    try (InputStream in = Files.newInputStream(Path.of(path))) { ks.load(in, password); }\n    for (Enumeration<String> e = ks.aliases(); e.hasMoreElements(); ) {\n        String a = e.nextElement();\n        if (ks.isKeyEntry(a)) {\n            java.security.cert.Certificate[] c = ks.getCertificateChain(a);\n            if (c != null && c.length > 0 && ks.getKey(a, password) instanceof java.security.PrivateKey) return true;\n        }\n    }\n    return false;\n}","typeGuard":null,"tryCatchPattern":"try {\n    TlsMaterialSource.load(policy);\n} catch (KeyStoreException e) {\n    log.error(\"Keystore '{}' has no usable key entry: {}\", policy.keyStorePath(), e.getMessage());\n}","preventionTips":["Distinguish identity keystores from truststores by path convention","Verify with keytool -list that the store contains a PrivateKeyEntry before deploying","Validate keystore passwords and aliases at config load time"],"tags":["tls","keystore","missing-identity","configuration"],"backgroundTag":"keystore-no-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"}