Apache Pulsar 中 Bouncy Castle 安全提供者的打包机制与 FIPS 切换实战指南【免费下载链接】pulsarApache Pulsar - distributed pub-sub messaging system项目地址: https://gitcode.com/gh_mirrors/pulsar28/pulsar导读Apache Pulsar 的 TLS 认证、传输加密等安全与加密能力底层依赖Bouncy CastleBC这个 Java 加密库。为了让用户能够在一套构建体系下轻松地在 BC 非 FIPS 版本与 FIPS 版本之间切换Pulsar 在bouncy-castle模块中设计了jar-in-jar的打包方案并配套提供了BouncyCastleLoader/BouncyCastleFipsLoader两个 Provider 加载器。本文将基于 Pulsar 仓库中的实际文档与源码讲清 BC 在 Pulsar 中如何被打包、如何被引入、为什么 shadedfat jar模块必须排除 BC以及如何把 Broker 从 BC-non-FIPS 平滑切换到 BC-FIPS。Bouncy Castle 与 Pulsar 的关系Bouncy Castle是一个补充 Java 默认 JCEJava Cryptographic Extension的 Java 加密库。相对于 Sun/Oracle JVM 自带的 JCE它提供了更多密码套件cipher suites与算法同时还内置了大量解析 PEM、ASN.1 等晦涩格式的工具类——这些格式通常没有开发者愿意自己重新实现。在 Pulsar 中安全与加密切面普遍依赖 Bouncy Castle 的 Jar 包典型场景包括TLS 认证TLS Authentication详见 site2/docs/security-tls-authentication.md该文档明确指出Bouncy Castle Provider 为 Pulsar 提供 TLS 相关的密码套件与算法如果需要 FIPS 版本请参考 Bouncy Castle 页面。传输加密Transport Encryption / 端到端消息加密详见 site2/docs/security-encryption.md。由于安全与加密是 Pulsar 的刚性依赖BC 的 Jar 会以多种方式出现在 Broker、Client 及其 shaded 产物中这直接导致了本文要讨论的打包与版本切换问题。FIPS 与非 FIPS二者不可共存Bouncy Castle官方同时提供FIPS 版本与非 FIPS 版本本文简称 BC-FIPS 与 BC-non-FIPS。二者在 JVM 中不能同时存在在一个 JVM 里引入其中一个版本时必须先排除另一个版本否则 Provider 注册会出现冲突。关于 BC-FIPS 的详细安装与配置如 FIPS 模式下的 self-test、Policy 文件等Bouncy Castle 官方文档提供了专门的User Guides与Security Policy两份 PDF需要部署 FIPS 环境时务必参考官方材料。本仓库侧重点是 Pulsar 如何在构建层面支持两种版本的切换。从 Pulsar 源码可以看到两套 Provider 在运行时拥有独立的注册名称非 FIPS 版本注册名为BC对应类org.bouncycastle.jce.provider.BouncyCastleProviderFIPS 版本注册名为BCFIPS对应类org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider。这两个常量定义在 pulsar-common/src/main/java/org/apache/pulsar/common/util/SecurityUtility.java 中public static final String BC_FIPS_PROVIDER_CLASS org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; public static final String BC_NON_FIPS_PROVIDER_CLASS org.bouncycastle.jce.provider.BouncyCastleProvider; public static final String BC_FIPS BCFIPS; public static final String BC BC;运行时SecurityUtility.getProvider()会先检查BC与BCFIPS是否已注册再决定返回哪个 Provider从 classpath 加载时则优先尝试非 FIPS 版本失败后再尝试 FIPS 版本这是出于向后兼容性的考虑见 SecurityUtility.java。isBCFIPS()方法则通过比对 Provider 的类名来判断当前是否处于 FIPS 模式。Pulsar 的 bouncy-castle 模块两个子模块 一个测试模块Pulsar 在 bouncy-castle/pom.xml 中定义了一个名为bouncy-castle-parent的父模块其注释点明了设计目标make it easy for user to load Bouncy Castle and Bouncy Castle FIPS即让用户能方便地引入/排除 BC 与 BC-FIPS。它聚合了三个子模块子模块ArtifactId用途bcbouncy-castle-bc打包 Pulsar 需要的 BC 非 FIPS Jar供 NAR/常规依赖使用bcfipsbouncy-castle-bcfips打包 Pulsar 需要的 BC FIPS Jar供 NAR/常规依赖使用bcfips-include-testbcfips-include-test用于验证 Broker Client 在引入 FIPS 版本后认证功能正常的测试模块jar-in-jar为什么不能直接打一个 uber-jar打包思路是把多个 Bouncy Castle Jar 合并进一个bouncy-castle-bc/bouncy-castle-bcfipsJar 中以简化引入与排除。但这里有一个关键障碍签名。每个原始 Bouncy Castle Jar 都与安全相关BC 官方对每个 JAR 都进行了签名。使用常规 Maven Shade 插件做 re-package 时Shade 会把 BC Jar解包explode并把签名文件放入META-INF。由于签名只对原始 BC Jar 有效重新合并出的 uber-jar 里这些签名就是非法的。此时运行会报出经典错误java.lang.SecurityException: Invalid signature file digest for Manifest main attributes常规解法是在 pom 中把这些签名文件排除掉excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude但排除签名会引发另一类更难排查的错误例如java.security.NoSuchAlgorithmException: PBEWithSHA256And256BitAES-CBC-BC SecretKeyFactory not available当显式指定算法来源后SecretKeyFactory.getInstance(PBEWithSHA256And256BitAES-CBC-BC, BC)会暴露真正的根因java.security.NoSuchProviderException: JCE cannot authenticate the provider BC这正是 JCE 无法认证 Provider 的典型表现——Provider 的 Jar 签名不合法。为此Pulsar 采用了executable-packer-maven-pluginde.ntcomputer:executable-packer-maven-plugin的jar-in-jar方案外层是一个可执行 jar内部嵌套存放原始的、保持完整签名的 Bouncy Castle Jar从而既保留 BC 的签名有效性又得到单一可用的 Jar 产物。两个模块的 pom 中都以mainClass指定了对应的 Loader 类bouncy-castle/bc/pom.xmlmainClass为org.apache.pulsar.bcloader.BouncyCastleLoaderbouncy-castle/bcfips/pom.xmlmainClass为org.apache.pulsar.bcloader.BouncyCastleFipsLoader。使用 jar-in-jar 产物时需要在依赖声明中显式带上classifierpkg/classifier。Loader 类的职责jar-in-jar 的入口即 pom 中指定的 mainClass是 Pulsar 自定义的 Provider 加载器二者均实现org.apache.pulsar.common.util.BCLoader接口见 pulsar-common/src/main/java/org/apache/pulsar/common/util/BCLoader.javapublic interface BCLoader { Provider getProvider(); }BouncyCastleLoader.java非 FIPS静态初始化块中检查Security.getProvider(BC)是否为空为空则Security.addProvider(new BouncyCastleProvider())并记录 Provider 信息BouncyCastleFipsLoader.javaFIPS逻辑相同但注册的是BouncyCastleFipsProvider名称是BCFIPS。运行时 Pulsar 的SecurityUtility会根据 classpath 上实际存在的 Provider 类自动选择加载哪一套这也是两种版本二选一在代码层面的落点。引入 BC-non-FIPSbouncy-castle-bc 模块bouncy-castle-bc由 bouncy-castle/bc/pom.xml 定义打包了 Pulsar 所需的非 FIPS Jar以 jar-in-jar 形式发布需要classifierpkg/classifier。其依赖如下dependency groupIdorg.bouncycastle/groupId artifactIdbcpkix-jdk15on/artifactId version${bouncycastle.version}/version /dependency dependency groupIdorg.bouncycastle/groupId artifactIdbcprov-ext-jdk15on/artifactId version${bouncycastle.version}/version /dependency当前仓库根 pom.xml 中定义的版本为bouncycastle.version1.69/bouncycastle.version bouncycastlefips.version1.0.2/bouncycastlefips.version其中bcprov-ext-jdk15on提供带扩展算法实现的加密 Providerbcpkix-jdk15on提供 PKIX、CMS、TSP 等更高层的证书与消息处理 API。通过bouncy-castle-bc这一个模块用户即可完成对 BC 非 FIPS Jar 集合的整体引入或整体排除。哪些模块默认带上了 bouncy-castle-bcPulsar Client 侧需要使用 Bouncy Castle因此pulsar-client-originalpulsar-client模块会引入bouncy-castle-bc并设置classifierpkg/classifier指向 jar-in-jar 产物见 pulsar-client/pom.xmldependency groupIdorg.apache.pulsar/groupId artifactIdbouncy-castle-bc/artifactId version${project.parent.version}/version classifierpkg/classifier /dependency而pulsar-client-original又被大量其他模块依赖例如pulsar-client-admin、pulsar-brokerpulsar-broker/pom.xml 依赖pulsar-client-original因此默认情况下 BC 非 FIPS Jar 会随着这些模块一起进入 classpath。shaded 模块为何必须排除 BC由于上文所述的 jar 签名原因Pulsar不会把bouncy-castle相关模块直接打进pulsar-client-all及其他的 shaded 产物例如pulsar-client-shaded、pulsar-client-admin-shaded、pulsar-broker-shaded。在这些 shaded 模块的 maven-shade-plugin 配置中会对pulsar-client-original做如下过滤filters filter artifactorg.apache.pulsar:pulsar-client-original/artifact includes include**/include /includes excludes excludeorg/bouncycastle/**/exclude /excludes /filter /filters仓库中 pulsar-broker-shaded/pom.xml、pulsar-client-shaded/pom.xml、pulsar-client-admin-shaded/pom.xml 均有同样的排除配置并配有注释 bouncycastle jars could not be shaded, or the signatures will be wrong。这意味着这些 fat jar 中不会包含 bouncy-castle 相关 Jar。使用 shaded 产物的用户需要按自己的安全策略自行引入 BC通常显式声明bouncy-castle-bc或bouncy-castle-bcfips依赖从而避免因 shade 解包导致的签名失效问题。引入 BC-FIPSbouncy-castle-bcfips 模块bouncy-castle-bcfips由 bouncy-castle/bcfips/pom.xml 定义打包了 Pulsar 所需的 FIPS Jar。与bouncy-castle-bc类似它同样以 jar-in-jar 形式发布便于整体引入与排除依赖如下dependency groupIdorg.bouncycastle/groupId artifactIdbc-fips/artifactId version${bouncycastlefips.version}/version /dependency dependency groupIdorg.bouncycastle/groupId artifactIdbcpkix-fips/artifactId version${bouncycastlefips.version}/version /dependency即 FIPS 版本对应替换为bc-fips与bcpkix-fips版本由${bouncycastlefips.version}统一管理当前仓库为1.0.2。实战从 BC-non-FIPS 切换到 BC-FIPS切换的本质是先排除非 FIPS 的bouncy-castle-bc再引入 FIPS 的bouncy-castle-bcfipspkgclassifier。以pulsar-broker模块为例dependency groupIdorg.apache.pulsar/groupId artifactIdpulsar-broker/artifactId version${pulsar.version}/version exclusions exclusion groupIdorg.apache.pulsar/groupId artifactIdbouncy-castle-bc/artifactId /exclusion /exclusions /dependency dependency groupIdorg.apache.pulsar/groupId artifactIdbouncy-castle-bcfips/artifactId version${pulsar.version}/version classifierpkg/classifier /dependency同样的思路也适用于 Client 侧pulsar-client-original、pulsar-client-admin等依赖了bouncy-castle-bc的模块先排除再引入 FIPS 版本。参考实现bcfips-include-test 模块仓库中 bouncy-castle/bcfips-include-test/pom.xml 是官方提供的最完整切换示例它在依赖pulsar-broker的两处声明test-jar 与普通 test 依赖中都排除了bouncy-castle-bc然后单独引入bouncy-castle-bcfipsdependency groupIdorg.apache.pulsar/groupId artifactIdbouncy-castle-bcfips/artifactId version${project.version}/version classifierpkg/classifier /dependency其注释明确写道exclude bouncy castle, then load fips version。配套的测试代码 bouncy-castle/bcfips-include-test/src/test/java/org/apache/pulsar/client/TlsProducerConsumerTest.java 在 FIPS 环境下验证了三类 TLS 场景大消息传输验证超过单个 TLS chunk 上限2^14字节的16KB1字节消息可以正常生产/消费二进制协议双向 TLS 认证不带客户端证书时握手应失败携带证书后消费可成功HTTP 协议双向 TLS 认证同样验证无证书失败、有证书成功。这套用例证明了切换后的 FIPS 环境在真实 Broker/Client 交互中可用。可以按需在本地运行该模块的测试来验证自己的切换配置mvn test -pl bouncy-castle/bcfips-include-test切换后运行时的注意事项FIPS 模式下 Provider 名称是BCFIPS。若代码中硬编码了SecretKeyFactory.getInstance(..., BC)这类写法需要确认 Pulsar 的SecurityUtility已统一通过 Provider 常量获取实例从 SecurityUtility.java 的注释可见BC/BCFIPS常量同时用于Security.getProvider以及CertificateFactory.getInstance(X.509, BCFIPS)之类的工厂调用。一个 JVM 内不得同时出现bouncy-castle-bc与bouncy-castle-bcfips切换时必须借助exclusions彻底排除旧版本避免Security.addProvider阶段出现 Provider 冲突或算法查找异常。FIPS 本身对算法与密钥强度有严格约束涉及 Policy、Self-test 等这些属于 BC 官方 Security Policy 文档的范畴在搭建 FIPS 合规环境前应完整阅读官方材料。常见错误速查现象根因处理方式java.lang.SecurityException: Invalid signature file digest for Manifest main attributesShade 解包导致 BC 签名失效不要对 BC 做 shade改用bouncy-castle-bc/bouncy-castle-bcfips的 jar-in-jarpkgclassifier产物java.security.NoSuchAlgorithmException: PBEWithSHA256And256BitAES-CBC-BC SecretKeyFactory not availableProvider 未正确注册或签名被破坏确认引入的是完整签名产物并检查是否同时混入了两套 BCjava.security.NoSuchProviderException: JCE cannot authenticate the provider BCProvider 认证失败签名不合法在 shaded 模块中排除org/bouncycastle/**单独引入官方打包模块FIPS 切换后算法不可用JVM 中同时存在两套 BC或仍在使用BC硬编码使用exclusions排除bouncy-castle-bc统一走SecurityUtility的 Provider 常量总结Pulsar 通过bouncy-castle父模块下的bouncy-castle-bc与bouncy-castle-bcfips两个 jar-in-jar 产物将 Bouncy Castle 的引入与排除收敛为两个清晰的坐标从根本上规避了 Shade 解包破坏 Jar 签名导致的 Provider 认证问题BouncyCastleLoader/BouncyCastleFipsLoader与SecurityUtility则在运行时完成 Provider 的选择与注册。无论是默认的非 FIPS 部署还是合规要求下的 FIPS 部署只需遵循排除 bouncy-castle-bc → 引入 bouncy-castle-bcfipspkg classifier这一条主线即可完成安全底座的整体切换并借助bcfips-include-test模块完成端到端验证。【免费下载链接】pulsarApache Pulsar - distributed pub-sub messaging system项目地址: https://gitcode.com/gh_mirrors/pulsar28/pulsar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
