apache/pulsar · critical · KeyStoreException
Configured truststore 'trustStorePath' holds no X.509 certif
Error message
Configured truststore 'trustStorePath' holds no X.509 certificates; refusing to fall back to the platform default trust store, which would trust every public CA. Fix the truststore, or unset trustStorePath.
What it means
This KeyStoreException is thrown by TlsMaterialSource.loadTrustCerts when a truststore file was explicitly configured via trustStorePath but contains zero X.509 certificates. The library refuses to proceed because an empty trust list would be indistinguishable from 'no truststore configured' downstream, causing context builders to silently install the platform default trust manager and trust every public CA — a serious security regression versus v4 behavior, which rejected all peers.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java:217
+ "' but leaves keyFilePath unset; a certificate without its private key yields no usable TLS "
+ "identity. Set keyFilePath, or unset certificateFilePath.");
}
if (hasKey && !hasCert) {
log.warn().attr("keyFilePath", policy.keyFilePath())
.log("TlsPolicy sets keyFilePath but no certificateFilePath; no TLS identity will be presented");
}
}
private List<X509Certificate> loadTrustCerts() throws Exception {
if (StringUtils.isNotBlank(policy.trustStorePath())) {
List<X509Certificate> trustCerts = TlsKeyStoreLoader.extractTrustCerts(
TlsKeyStoreLoader.loadKeyStore(policy.trustStoreType(), policy.trustStorePath(),
policy.trustStorePassword(), jcaProvider));
if (trustCerts.isEmpty()) {
// An empty trust list is indistinguishable from "no truststore configured" downstream, and both
// context builders then install the platform default trust manager — silently trusting every
// public CA. v4 initialised the TrustManagerFactory with the explicit store and rejected every peer.
throw new KeyStoreException("Configured truststore '" + policy.trustStorePath()
+ "' holds no X.509 certificates; refusing to fall back to the platform default trust "
+ "store, which would trust every public CA. Fix the truststore, or unset trustStorePath.");
}
return trustCerts;
}
if (StringUtils.isNotBlank(policy.trustCertsFilePath())) {
X509Certificate[] certs =
PemReader.loadCertificatesFromPemFile(policy.trustCertsFilePath(), jcaProvider);
if (certs == null || certs.length == 0) {
// Unlike the keystore axis above, the PEM axis keeps 4.x behaviour and falls back to the
// platform trust store rather than failing, so existing deployments are not broken. That
// fallback silently trusts every public CA, so a truncated, mis-mounted or empty file is
// logged: it is the only signal an operator gets that their pinned private CA is no longer
// in effect.
log.warn().attr("trustCertsFilePath", policy.trustCertsFilePath())
.log("Configured PEM trust file holds no X.509 certificates; falling back to the "
+ "platform default trust store, which trusts every public CA");
return List.of();View on GitHub (pinned to 820761864e)
Solutions
- Open the truststore (keytool -list -keystore <path>) and confirm it contains trustedCertEntry entries; if not, import the CA cert with keytool -importcert
- Verify trustStoreType matches the actual file format (JKS vs PKCS12); fix or remove the mismatched type
- If you actually want the JVM/platform default trust store, remove trustStorePath from the policy entirely instead of pointing it at an empty store
- Regenerate the truststore from the correct CA/intermediate chain used by the broker
Example fix
// before: policy points at a keystore with no trusted certs
policy.trustStorePath("/etc/pulsar/client-keystore.p12")
// after: export the CA cert and import into a dedicated truststore
// keytool -exportcert -alias ca -file ca.pem -keystore client-keystore.p12
// keytool -importcert -alias pulsar-ca -file ca.pem -keystore truststore.p12
policy.trustStorePath("/etc/pulsar/truststore.p12").trustStoreType("PKCS12") Defensive patterns
Strategy: validation
Validate before calling
KeyStore ks = KeyStore.getInstance("PKCS12");
try (InputStream in = Files.newInputStream(Paths.get(trustStorePath))) {
ks.load(in, password);
}
int certs = java.util.Collections.list(ks.aliases()).stream()
.filter(a -> { try { return ks.isCertificateEntry(a); } catch (Exception e) { return false; } })
.count();
if (certs == 0) throw new IllegalStateException("truststore has no trusted certificates: " + trustStorePath); Type guard
static boolean hasTrustedCerts(String path, char[] pass, String type) {
try {
KeyStore ks = KeyStore.getInstance(type);
try (InputStream in = Files.newInputStream(Paths.get(path))) { ks.load(in, pass); }
return java.util.Collections.list(ks.aliases()).stream()
.anyMatch(a -> { try { return ks.isCertificateEntry(a); } catch (Exception e) { return false; } });
} catch (Exception e) { return false; }
} Prevention
- Run keytool -list and confirm trustedCertEntry count > 0 before pointing trustStorePath at a file
- Keep client identity keystores and CA truststores in separate files
- Always set trustStoreType to match the actual file format
- If relying on platform defaults, omit trustStorePath entirely rather than passing an empty store
When it happens
Trigger: Calling trustCerts/loadTrustCerts with a TlsPolicy whose trustStorePath points to a keystore that loads successfully but yields an empty certificate collection — e.g. a corrupt file, a keystore containing only private keys, a wrong trustStoreType so entries are skipped, or a password that decrypts aliases whose certs fail to load.
Common situations: Generating a keystore with keytool but forgetting -file/import of the CA cert; pointing trustStorePath at the client's own keystore (only private entries, no trustedCertEntry); mis-typed trustStoreType (PKCS12 vs JKS) causing no entries to be read; a file truncated or zero-byte after a bad copy; migrating v4 TLS config where a store previously behaved differently.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Issuer URL does not use https, but must:
- Passed in parameter empty. KEYSTORE_PATH: ${keyStorePath} KE
- Configured keystore 'keyStorePath' holds no usable key entry
- KeyStore creation error
- Failed to set the certificate
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/e5c8296f503ffdfb.
Report an issue: GitHub.