Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions conf/client.conf
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ authParams=
# authority.
tlsAllowInsecureConnection=false

# Whether the server hostname is verified against the subject alternative names (SANs) of the
# certificate the server is using (RFC 2818/6125; CN matching is not supported).
# Whether the server hostname is verified against the certificate the server is using. The subject
# alternative names (SANs) are authoritative; the CN is consulted only when the certificate carries no
# dNSName SAN (RFC 2818 section 3.1). A Conscrypt-pinned client does not fall back to the CN at all.
# Enabled by default since Pulsar 5.0 (PIP-478).
tlsEnableHostnameVerification=true

Expand Down
5 changes: 3 additions & 2 deletions conf/functions_worker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,9 @@ tlsKeyFilePath:
tlsTrustCertsFilePath:
# Accept untrusted TLS certificate from client
tlsAllowInsecureConnection: false
# Whether the server hostname is verified against the subject alternative names (SANs) of the
# server certificate (RFC 2818/6125; CN matching is not supported).
# Whether the server hostname is verified against the server certificate. The subject alternative names
# (SANs) are authoritative; the CN is consulted only when the certificate carries no dNSName SAN
# (RFC 2818 section 3.1). A Conscrypt-pinned client does not fall back to the CN at all.
# Enabled by default since Pulsar 5.0 (PIP-478).
tlsEnableHostnameVerification: true
# Tls cert refresh duration in seconds. Set 0 to disable the background rotation check, so the
Expand Down
12 changes: 8 additions & 4 deletions pip/pip-478.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pulsar-broker-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ dependencies {
implementation(libs.jetty.compression.gzip)
implementation(libs.jetty.ee10.servlet)

// Non-FIPS BouncyCastle provider for tests that exercise SecurityUtility (which loads
// org.bouncycastle.jce.provider.BouncyCastleProvider in a static initializer). This matches
// Non-FIPS BouncyCastle provider for tests that exercise JcaProviders (which resolves
// org.bouncycastle.jce.provider.BouncyCastleProvider reflectively, on first use). This matches
// the provider used in production. FIPS is covered separately by the bcfips-include-test
// module; bc-fips must not be on a classpath that also has the non-FIPS provider because both
// jars define org.bouncycastle.* and the JVM rejects the mismatched signers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public static SslProvider engineProvider(String providerString) {
* is conditional, because a default that breaks a supported platform is not a usable default.
*
* <p>This applies to server listeners only. Client-side hostname verification is unaffected: a server
* does not verify hostnames, so pinning Conscrypt here cannot interact with the SAN-only verification
* does not verify hostnames, so pinning Conscrypt here cannot interact with the hostname verification
* PIP-478 turns on.
*
* @param explicitJsseProvider the configured {@code jsseProvider} (may be null/blank)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ public Object[][] codecProvider() {
* 1. Client tries to connect to broker with hostname="localhost"
* 2. Broker sends an x509 certificate whose SubjectAltName does not match "localhost"
* (in fact it has no SAN at all, only CN=broker.pulsar.apache.org)
* 3. Client performs SAN-based (RFC 2818) host-name verification, closes the connection
* and fails consumer creation
* 3. Client performs host-name verification: there is no SAN to match, and the CN the default
* engines fall back to is "broker.pulsar.apache.org", so it closes the connection and fails
* consumer creation
* </pre>
*
* @throws Exception
Expand All @@ -140,7 +141,7 @@ public void testTlsSyncProducerAndConsumerWithInvalidBrokerHost(boolean hostname
this.hostnameVerificationEnabled = hostnameVerificationEnabled;
clientTrustCertFilePath = TLS_MIM_TRUST_CERT_FILE_PATH;
// setup broker cert which has no SAN matching the broker's hostname="localhost"
// (CN=broker.pulsar.apache.org, no subjectAltName): CN-based matching is no longer supported (PIP-478).
// (CN=broker.pulsar.apache.org, no subjectAltName), so it matches neither by SAN nor by CN fallback.
// The broker itself uses this deliberately-mismatched cert, so its own internal client (e.g. the
// system-topic reader for topic policies) cannot verify "localhost" against it; disable hostname
// verification for the broker's outbound client here. This test exercises the end-user client's
Expand Down Expand Up @@ -179,7 +180,7 @@ public void testTlsSyncProducerAndConsumerWithInvalidBrokerHost(boolean hostname
* <pre>
* 1. Client tries to connect to broker with hostname="localhost"
* 2. Broker sends an x509 certificate whose SubjectAltName includes DNS:localhost
* 3. Client performs SAN-based (RFC 2818) host-name verification and continues
* 3. Client performs host-name verification, matches DNS:localhost in the SAN, and continues
* </pre>
*
* @throws Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ PulsarAdminBuilder authentication(String authPluginClassName, Map<String, String
* certificate and matches provided hostname(CN/SAN) with expected broker's host name. It follows RFC 2818, 3.1.
* Server Identity hostname verification.
*
* <p>The CN is only a fallback, and only on the default engines: it is consulted when the client connects by
* hostname and the certificate carries no {@code dNSName} SAN, and ignored once any is present. A client that
* pins Conscrypt as its JSSE provider verifies against the SAN alone and never falls back to the CN (Pulsar
* 5.0, PIP-478). A connection to an IP literal is matched against {@code iPAddress} SANs, never the CN.
*
* @see <a href="https://tools.ietf.org/html/rfc2818">rfc2818</a>
*
* @param enableTlsHostnameVerification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
public class AdminOAuth2IdpTlsEndToEndTest {

// broker.cert.pem carries SAN:localhost and chains to ca.cert.pem, so both trust and hostname verification
// pass against the WireMock server (the shared broker.keystore.jks has no SAN).
// pass against the WireMock server (the shared broker.keystore.jks carries the same SAN but is self-signed,
// so it fails trust verification rather than hostname verification).
private static final String BROKER_CERT = resource("certificate-authority/server-keys/broker.cert.pem");
private static final String BROKER_KEY = resource("certificate-authority/server-keys/broker.key-pk8.pem");
private static final String CA_CERT = resource("certificate-authority/certs/ca.cert.pem");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,12 @@ ClientBuilder authentication(String authPluginClassName, Map<String, String> aut
* certificate and matches provided hostname(CN/SAN) with expected broker's host name. It follows RFC 2818, 3.1.
* Server Identity hostname verification.
*
* @see <a href="https://tools.ietf.org/html/rfc2818">RFC 818</a>
* <p>The CN is only a fallback, and only on the default engines: it is consulted when the client connects by
* hostname and the certificate carries no {@code dNSName} SAN, and ignored once any is present. A client that
* pins Conscrypt as its JSSE provider verifies against the SAN alone and never falls back to the CN (Pulsar
* 5.0, PIP-478). A connection to an IP literal is matched against {@code iPAddress} SANs, never the CN.
*
* @see <a href="https://tools.ietf.org/html/rfc2818">RFC 2818</a>
*
* @param enableTlsHostnameVerification whether to enable TLS hostname verification
* @return the client builder instance
Expand Down
2 changes: 1 addition & 1 deletion pulsar-client-messagecrypto-bc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies {
api(project(":pulsar-client-api"))
// MessageCryptoBc uses BouncyCastle types directly: bcpkix for PEM parsing (PEMParser,
// JcaPEMKeyConverter) and bcprov for the EC/IES key specs and ASN.1 types used in key handling.
// The JCA provider itself is resolved at runtime via SecurityUtility (pulsar-common), not here.
// The JCA provider itself is resolved at runtime via JcaProviders (pulsar-common), not here.
implementation(libs.bcpkix.jdk18on)
api(libs.bcprov.jdk18on)
implementation(libs.guava)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
import org.apache.pulsar.common.api.proto.EncryptionKeys;
import org.apache.pulsar.common.api.proto.KeyValue;
import org.apache.pulsar.common.api.proto.MessageMetadata;
import org.apache.pulsar.common.util.SecurityUtility;
import org.apache.pulsar.common.util.tls.JcaProviders;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
Expand Down Expand Up @@ -93,15 +93,16 @@ public class MessageCryptoBc implements MessageCrypto<MessageMetadata, MessageMe
public static final String AESGCM = "AES/GCM/NoPadding";

// BouncyCastle JCA provider, resolved lazily on first use via the initialization-on-demand holder
// idiom. Resolution is delegated to SecurityUtility.getProvider() — the same FIPS-agnostic lookup
// used elsewhere in Pulsar (e.g. TLS) — so message crypto uses whichever BouncyCastle provider is
// present on the classpath: the non-FIPS "BC" (org.bouncycastle.jce.provider.BouncyCastleProvider)
// idiom. Resolution is delegated to JcaProviders.requireBouncyCastleProvider() — the same
// FIPS-agnostic lookup used elsewhere in Pulsar (e.g. TLS) — so message crypto uses whichever
// BouncyCastle provider is present on the classpath: the non-FIPS "BC"
// (org.bouncycastle.jce.provider.BouncyCastleProvider)
// or the FIPS "BCFIPS" (org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider), rather than
// hardcoding one. The resolved provider is passed directly to the JCA getInstance(...) calls.
// Deferring the lookup to first use (asymmetric key wrapping with RSA-OAEP/ECIES, or EC key
// loading) keeps any resolution failure out of class loading.
private static final class BcProviderHolder {
static final Provider PROVIDER = SecurityUtility.getProvider();
static final Provider PROVIDER = JcaProviders.requireBouncyCastleProvider().provider();
}

private static Provider bcProvider() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@
*/
public class OAuth2IdpTlsFrameworkClientTest {

// The shared self-signed broker.keystore.jks carries no SAN, so it fails modern hostname verification (which
// the folded CLIENT_OAUTH2 policy keeps on, matching v4). Build the WireMock server keystore from
// broker.cert.pem instead — it has SAN:localhost and chains to ca.cert.pem — so both trust and hostname pass.
// The shared broker.keystore.jks is self-signed (issuer == subject), so it does not chain to ca.cert.pem and
// fails trust verification — its SAN is fine (DNS:localhost, IP:127.0.0.1). Build the WireMock server
// keystore from broker.cert.pem instead — same SAN, but chained to ca.cert.pem — so both trust and hostname
// verification pass (the folded CLIENT_OAUTH2 policy keeps hostname verification on, matching v4).
private static final String BROKER_CERT = resource("certificate-authority/server-keys/broker.cert.pem");
private static final String BROKER_KEY = resource("certificate-authority/server-keys/broker.key-pk8.pem");
private static final String CA_CERT = resource("certificate-authority/certs/ca.cert.pem");
Expand All @@ -91,7 +92,7 @@
.keyManagerPassword(STORE_PW).keystorePassword(STORE_PW));
idp.start();
idp.stubFor(get(urlEqualTo("/probe")).willReturn(aResponse().withStatus(200).withBody("ok")));
eventLoopGroup = new NioEventLoopGroup(1);

Check warning on line 95 in pulsar-client/src/test/java/org/apache/pulsar/client/impl/auth/oauth2/OAuth2IdpTlsFrameworkClientTest.java

View workflow job for this annotation

GitHub Actions / CI - Unit - Pulsar Client

[deprecation] NioEventLoopGroup in io.netty.channel.nio has been deprecated
timer = new HashedWheelTimer();
scheduler = Executors.newSingleThreadScheduledExecutor();
}
Expand Down
4 changes: 2 additions & 2 deletions pulsar-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ dependencies {
// opentelemetry-api as `api` (the SPI exposes OpenTelemetry on its surface), so it reaches this
// module's compile AND runtime classpaths through the api(project(":pulsar-tls-factory-api")) above.

// Non-FIPS BouncyCastle provider for tests that exercise SecurityUtility (which loads
// org.bouncycastle.jce.provider.BouncyCastleProvider in a static initializer). This matches
// Non-FIPS BouncyCastle provider for tests that exercise JcaProviders (which resolves
// org.bouncycastle.jce.provider.BouncyCastleProvider reflectively, on first use). This matches
// the provider used in production. FIPS is covered separately by the bcfips-include-test
// module; bc-fips must not be on a classpath that also has the non-FIPS provider because both
// jars define org.bouncycastle.* and the JVM rejects the mismatched signers.
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading