{"record":{"id":"e5c8296f503ffdfb","repo":"apache/pulsar","slug":"configured-truststore-truststorepath-holds-no-x","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":"KeyStoreException","httpStatus":null,"severity":"critical","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java","lineNumber":217,"sourceCode":"                    + \"' 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.\n                throw new KeyStoreException(\"Configured truststore '\" + policy.trustStorePath()\n                        + \"' holds no X.509 certificates; refusing to fall back to the platform default trust \"\n                        + \"store, which would trust every public CA. Fix the truststore, or unset trustStorePath.\");\n            }\n            return trustCerts;\n        }\n        if (StringUtils.isNotBlank(policy.trustCertsFilePath())) {\n            X509Certificate[] certs =\n                    PemReader.loadCertificatesFromPemFile(policy.trustCertsFilePath(), jcaProvider);\n            if (certs == null || certs.length == 0) {\n                // Unlike the keystore axis above, the PEM axis keeps 4.x behaviour and falls back to the\n                // platform trust store rather than failing, so existing deployments are not broken. That\n                // fallback silently trusts every public CA, so a truncated, mis-mounted or empty file is\n                // logged: it is the only signal an operator gets that their pinned private CA is no longer\n                // in effect.\n                log.warn().attr(\"trustCertsFilePath\", policy.trustCertsFilePath())\n                        .log(\"Configured PEM trust file holds no X.509 certificates; falling back to the \"\n                                + \"platform default trust store, which trusts every public CA\");\n                return List.of();","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java#L199-L235","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: policy points at a keystore with no trusted certs\npolicy.trustStorePath(\"/etc/pulsar/client-keystore.p12\")\n// after: export the CA cert and import into a dedicated truststore\n// keytool -exportcert -alias ca -file ca.pem -keystore client-keystore.p12\n// keytool -importcert -alias pulsar-ca -file ca.pem -keystore truststore.p12\npolicy.trustStorePath(\"/etc/pulsar/truststore.p12\").trustStoreType(\"PKCS12\")","handlingStrategy":"validation","validationCode":"KeyStore ks = KeyStore.getInstance(\"PKCS12\");\ntry (InputStream in = Files.newInputStream(Paths.get(trustStorePath))) {\n    ks.load(in, password);\n}\nint certs = java.util.Collections.list(ks.aliases()).stream()\n    .filter(a -> { try { return ks.isCertificateEntry(a); } catch (Exception e) { return false; } })\n    .count();\nif (certs == 0) throw new IllegalStateException(\"truststore has no trusted certificates: \" + trustStorePath);","typeGuard":"static boolean hasTrustedCerts(String path, char[] pass, String type) {\n    try {\n        KeyStore ks = KeyStore.getInstance(type);\n        try (InputStream in = Files.newInputStream(Paths.get(path))) { ks.load(in, pass); }\n        return java.util.Collections.list(ks.aliases()).stream()\n            .anyMatch(a -> { try { return ks.isCertificateEntry(a); } catch (Exception e) { return false; } });\n    } catch (Exception e) { return false; }\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["tls","security","keystore","configuration"],"backgroundTag":"empty-truststore","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"}