apache/pulsar · critical · KeyStoreException
Configured keystore 'keyStorePath' holds no usable key entry
Error message
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.
What it means
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.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java:179
TlsMaterial.KeyEntry first = entries.get(0);
return new TlsMaterial(first.privateKey(), first.chain(), trustCerts, entries);
}
validatePemIdentity();
return new TlsMaterial(loadPemPrivateKey(), loadPemCertificateChain(), trustCerts);
}
/**
* Reject a keystore that holds no usable key entry. v4 handed such a store to
* {@code KeyManagerFactory.init}, which initialised fine but produced a key manager with no aliases — a
* certain, undiagnosed handshake failure for a server and a silently identity-less client. Failing the
* load is a deliberate tightening: the only deployments it can break already presented no identity.
*
* @param entries the key entries extracted from the configured keystore
* @throws KeyStoreException if the keystore carries no usable key entry
*/
private void validateKeyStoreIdentity(List<TlsMaterial.KeyEntry> entries) throws KeyStoreException {
if (entries.isEmpty()) {
throw new KeyStoreException("Configured keystore '" + policy.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.");
}
}
/**
* Reject a half-configured PEM identity that would be silently dropped. A certificate without its key
* yields {@link TlsMaterial#hasKeyMaterial()} {@code == false}, so the identity is omitted from the built
* context and the misconfiguration only surfaces as a handshake/authentication failure much later. The
* check is deliberately <em>asymmetric</em>: a key without a certificate is what v4 silently tolerated, so
* it stays a WARN rather than a new startup failure. Enforced here rather than in {@code TlsPolicy.Builder}
* so custom {@code PulsarTlsFactory} implementations that build their own policies are not constrained by
* this default factory's requirement.
*/
private void validatePemIdentity() {
boolean hasCert = StringUtils.isNotBlank(policy.certificateFilePath());
boolean hasKey = StringUtils.isNotBlank(policy.keyFilePath());
if (hasCert && !hasKey) {View on GitHub (pinned to 820761864e)
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
Example fix
// before
TlsPolicy policy = TlsPolicy.builder().keyStorePath("/etc/pulsar/truststore.jks").build(); // trust-only
// after
TlsPolicy policy = TlsPolicy.builder()
.keyStorePath("/etc/pulsar/broker.keystore.jks") // contains PrivateKeyEntry
.keyStorePassword("********")
.build(); Defensive patterns
Strategy: validation
Validate before calling
static boolean keystoreHasIdentity(String path, char[] password) throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream in = Files.newInputStream(Path.of(path))) { ks.load(in, password); }
for (Enumeration<String> e = ks.aliases(); e.hasMoreElements(); ) {
String a = e.nextElement();
if (ks.isKeyEntry(a)) {
java.security.cert.Certificate[] c = ks.getCertificateChain(a);
if (c != null && c.length > 0 && ks.getKey(a, password) instanceof java.security.PrivateKey) return true;
}
}
return false;
} Try / catch
try {
TlsMaterialSource.load(policy);
} catch (KeyStoreException e) {
log.error("Keystore '{}' has no usable key entry: {}", policy.keyStorePath(), e.getMessage());
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Passed in parameter empty. KEYSTORE_PATH: ${keyStorePath} KE
- Configured truststore 'trustStorePath' holds no X.509 certif
- jcaProvider='${providerName}' does not supply KeyStore type
- jcaProvider='${providerName}' supplies none of the in-memory
- TlsPolicy field '${field}' is set but is not valid for forma
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/b7dfc4d18faab1ab.
Report an issue: GitHub.