{"record":{"id":"5e324644ec09097b","repo":"apache/pulsar","slug":"cross-format-tls-material-tlspolicy-configur-5e3246","errorCode":null,"errorMessage":"Cross-format TLS material: tlsPolicy(...) configures a PEM truststore (trustCertsFilePath) but the authentication plugin supplies a keystore client certificate/key. Folding these would silently drop the configured truststore. Configure the trust material and the client identity in the same format (both PEM, or both keystore).","messagePattern":"Cross-format TLS material: tlsPolicy\\(\\.\\.\\.\\) configures a PEM truststore \\(trustCertsFilePath\\) but the authentication plugin supplies a keystore client certificate/key\\. Folding these would silently drop the configured truststore\\. Configure the trust material and the client identity in the same format \\(both PEM, or both keystore\\)\\.","errorType":"exception","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java","lineNumber":520,"sourceCode":"    }\n\n    /** Copy the trust material and flags of {@code base} (if any) into a keystore-format builder. */\n    private static TlsPolicy.Builder keyStoreBuilder(TlsPolicy base) {\n        TlsPolicy.Builder b = copyFlags(base).format(TlsPolicy.Format.KEYSTORE);\n        if (base != null && base.format() == TlsPolicy.Format.KEYSTORE) {\n            // Preserve the base truststore (path, password, and TYPE): folding the auth plugin's keystore must\n            // not clobber the truststore type configured via tlsPolicy(...) — the keystore and truststore may\n            // use different types (e.g. a PKCS12 keystore with a JKS truststore).\n            b.trustStorePath(base.trustStorePath())\n                    .trustStorePassword(base.trustStorePassword())\n                    .trustStoreType(base.trustStoreType());\n        } else if (base != null && isNotBlank(base.trustCertsFilePath())) {\n            // Cross-format fold: the tlsPolicy(...) carries a PEM truststore (trustCertsFilePath) but the auth\n            // plugin's client identity is a keystore. A keystore policy has no PEM trust field, so folding here\n            // would silently drop the configured trust anchors and fall back to the system trust store. Fail loud\n            // (matching TlsPolicy.build()'s fail-loud format validation) rather than silently broadening/breaking\n            // trust.\n            throw new IllegalArgumentException(\"Cross-format TLS material: tlsPolicy(...) configures a PEM \"\n                    + \"truststore (trustCertsFilePath) but the authentication plugin supplies a keystore client \"\n                    + \"certificate/key. Folding these would silently drop the configured truststore. Configure the \"\n                    + \"trust material and the client identity in the same format (both PEM, or both keystore).\");\n        }\n        return b;\n    }\n\n    private static TlsPolicy.Builder copyFlags(TlsPolicy base) {\n        TlsPolicy.Builder b = TlsPolicy.builder();\n        if (base != null) {\n            b.allowInsecureConnection(base.allowInsecureConnection())\n                    .enableHostnameVerification(base.enableHostnameVerification())\n                    .protocols(base.protocols())\n                    .ciphers(base.ciphers())\n                    // Preserve the pinned JSSE (SSLContext) provider across the fold. A FIPS deployment\n                    // pins it via tlsPolicy(...); dropping it here would let the transport fall back to\n                    // the default JDK engine, silently defeating the pin.\n                    .jsseProvider(base.jsseProvider())","sourceCodeStart":502,"sourceCodeEnd":538,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java#L502-L538","documentation":"Mirror of the PEM-side guard: when the auth plugin supplies a keystore client identity (AuthenticationKeyStoreTls or a generic plugin exposing KeyStoreParams), the builder folds it into a KEYSTORE-format CLIENT_DEFAULT policy. If the configured tlsPolicy instead carries a PEM truststore (trustCertsFilePath), folding would drop those trust anchors, so build() fails loudly with this IllegalArgumentException.","triggerScenarios":"Calling tlsPolicy(policy) with a PEM-format policy that sets trustCertsFilePath, AND configuring a keystore-based auth plugin (AuthenticationKeyStoreTls with KeyStoreParams, or a generic v4 plugin exposing getTlsKeyStoreParams()), then calling build().","commonSituations":"The common Java deployment uses a JKS/PKCS12 client keystore but the trust anchors were given as a PEM CA bundle (e.g. copied from a curl/openssl setup or a Kubernetes CA cert); teams migrating from system-trust defaults then adding one side in the other format.","solutions":["Import the PEM CA bundle into a keystore (keytool -importcert) and configure trustStorePath/trustStorePassword/trustStoreType on the tlsPolicy instead of trustCertsFilePath.","Or switch the client identity to PEM (AuthenticationTls with cert/key file paths) so both sides are PEM.","Remove trustCertsFilePath if the keystore already contains the needed trust anchors and system/default trust is intended.","Verify which format each side uses before building: policy.format() vs the plugin type."],"exampleFix":"// before\nbuilder.tlsPolicy(TlsPolicy.builder().format(PEM).trustCertsFilePath(\"ca.pem\").build())\n       .authentication(new AuthenticationKeyStoreTls(ksParams)); // IllegalArgumentException at build()\n// after\nbuilder.tlsPolicy(TlsPolicy.builder().format(KEYSTORE)\n        .trustStorePath(\"truststore.jks\").trustStorePassword(pw).trustStoreType(\"JKS\").build())\n       .authentication(new AuthenticationKeyStoreTls(ksParams));","handlingStrategy":"validation","validationCode":"// Before build(), when using a keystore auth plugin with a tlsPolicy:\nTlsPolicy p = clientPolicy;\nboolean keystorePlugin = authPlugin instanceof AuthenticationKeyStoreTls;\nif (keystorePlugin && p != null && p.format() == TlsPolicy.Format.PEM\n        && p.trustCertsFilePath() != null && !p.trustCertsFilePath().isBlank()) {\n    throw new IllegalStateException(\"Use trustStorePath (keystore) with a keystore auth plugin\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    client = builder.build();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Cross-format TLS material\")) {\n        // import the PEM CA into a keystore or switch the plugin to PEM, then rebuild\n        throw new IllegalStateException(\"Align trust + identity TLS formats (both PEM or both keystore)\", e);\n    }\n    throw e;\n}","preventionTips":["keytool -importcert the PEM CA bundle into your truststore when using keystore identity.","Decide the format once per deployment and enforce it in configuration templates.","Build the client in a startup smoke test to catch mismatches early.","Remember keystore and truststore types may differ (PKCS12 keystore + JKS truststore) — that is fine; the format axis is what must match."],"tags":["pulsar","tls","pem","keystore","configuration-conflict"],"backgroundTag":"cross-format-tls-material","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}