Skip to content

Commit 74932d4

Browse files
committed
feat(pqc): replace provider-specific PQC TLS setup with generic SslSocketConfigurator callback
Introduces SslSocketConfigurator interface and ConfigurableSSLSocketFactory wrapper. ConfigurableSSLSocketFactory wraps the active SSLSocketFactory, intercepts socket creation, and invokes the configurator callback, enabling client-level socket configuration (e.g. for Conscrypt or BouncyCastle). Includes JreNamedGroupsSslSocketConfigurator implementing reflective named groups configuration natively on JDK 20+. Removes obsolete wrapTrustManagers code and animal-sniffer dependencies from SslUtils. TAG=agy CONV=385b9ab5-874c-4c9a-b331-66dab51fef61
1 parent cf8ec81 commit 74932d4

6 files changed

Lines changed: 495 additions & 12 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2026 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.api.client.http.javanet;
16+
17+
import java.io.IOException;
18+
import java.net.InetAddress;
19+
import java.net.Socket;
20+
import javax.net.ssl.SSLSocket;
21+
import javax.net.ssl.SSLSocketFactory;
22+
23+
/**
24+
* An {@link SSLSocketFactory} wrapper that intercepts all socket creation entrypoints (both default
25+
* and bound socket creations) and applies the custom user-provided {@link SslSocketConfigurator}
26+
* callback to the socket before returning it.
27+
*
28+
* <p>This factory delegates all standard socket actions to the underlying default or custom {@link
29+
* SSLSocketFactory} instance. Sockets are intercepted and cast to {@link SSLSocket} for callback
30+
* execution.
31+
*
32+
* @since 2.1.2
33+
*/
34+
final class ConfigurableSSLSocketFactory extends SSLSocketFactory {
35+
private final SSLSocketFactory delegate;
36+
private final SslSocketConfigurator configurator;
37+
38+
ConfigurableSSLSocketFactory(SSLSocketFactory delegate, SslSocketConfigurator configurator) {
39+
this.delegate = delegate;
40+
this.configurator = configurator;
41+
}
42+
43+
private Socket configure(Socket socket) {
44+
if (socket instanceof SSLSocket && configurator != null) {
45+
configurator.configure((SSLSocket) socket);
46+
}
47+
return socket;
48+
}
49+
50+
@Override
51+
public String[] getDefaultCipherSuites() {
52+
return delegate.getDefaultCipherSuites();
53+
}
54+
55+
@Override
56+
public String[] getSupportedCipherSuites() {
57+
return delegate.getSupportedCipherSuites();
58+
}
59+
60+
@Override
61+
public Socket createSocket() throws IOException {
62+
return configure(delegate.createSocket());
63+
}
64+
65+
@Override
66+
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
67+
throws IOException {
68+
return configure(delegate.createSocket(s, host, port, autoClose));
69+
}
70+
71+
@Override
72+
public Socket createSocket(String host, int port) throws IOException {
73+
return configure(delegate.createSocket(host, port));
74+
}
75+
76+
@Override
77+
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
78+
throws IOException {
79+
return configure(delegate.createSocket(host, port, localHost, localPort));
80+
}
81+
82+
@Override
83+
public Socket createSocket(InetAddress host, int port) throws IOException {
84+
return configure(delegate.createSocket(host, port));
85+
}
86+
87+
@Override
88+
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
89+
throws IOException {
90+
return configure(delegate.createSocket(address, port, localAddress, localPort));
91+
}
92+
}

google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java

Lines changed: 127 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@
2828
import java.net.URL;
2929
import java.security.GeneralSecurityException;
3030
import java.security.KeyStore;
31+
import java.security.Provider;
3132
import java.security.cert.CertificateFactory;
3233
import java.util.Arrays;
3334
import javax.net.ssl.HostnameVerifier;
3435
import javax.net.ssl.HttpsURLConnection;
3536
import javax.net.ssl.SSLContext;
3637
import javax.net.ssl.SSLSocketFactory;
38+
import javax.net.ssl.TrustManager;
39+
import javax.net.ssl.TrustManagerFactory;
3740

3841
/**
3942
* Thread-safe HTTP low-level transport based on the {@code java.net} package.
@@ -84,7 +87,7 @@ private static Proxy defaultProxy() {
8487
private final ConnectionFactory connectionFactory;
8588

8689
/** SSL socket factory or {@code null} for the default. */
87-
private final SSLSocketFactory sslSocketFactory;
90+
final SSLSocketFactory sslSocketFactory;
8891

8992
/** Host name verifier or {@code null} for the default. */
9093
private final HostnameVerifier hostnameVerifier;
@@ -189,6 +192,12 @@ public static final class Builder {
189192
/** SSL socket factory or {@code null} for the default. */
190193
private SSLSocketFactory sslSocketFactory;
191194

195+
/** Security provider to use or {@code null} for default. */
196+
private Provider securityProvider;
197+
198+
/** Custom SSLSocket configurator or {@code null} to disable callback configuration. */
199+
SslSocketConfigurator sslSocketConfigurator;
200+
192201
/** Host name verifier or {@code null} for the default. */
193202
private HostnameVerifier hostnameVerifier;
194203

@@ -289,8 +298,9 @@ public Builder trustCertificatesFromStream(InputStream certificateStream)
289298
* @since 1.14
290299
*/
291300
public Builder trustCertificates(KeyStore trustStore) throws GeneralSecurityException {
292-
SSLContext sslContext = SslUtils.getTlsSslContext();
293-
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());
301+
SSLContext sslContext = SslUtils.getTlsSslContext(securityProvider);
302+
SslUtils.initSslContext(
303+
sslContext, trustStore, SslUtils.getPkixTrustManagerFactory(securityProvider));
294304
return setSslSocketFactory(sslContext.getSocketFactory());
295305
}
296306

@@ -313,14 +323,14 @@ public Builder trustCertificates(
313323
if (mtlsKeyStore != null && mtlsKeyStore.size() > 0) {
314324
this.isMtls = true;
315325
}
316-
SSLContext sslContext = SslUtils.getTlsSslContext();
326+
SSLContext sslContext = SslUtils.getTlsSslContext(securityProvider);
317327
SslUtils.initSslContext(
318328
sslContext,
319329
trustStore,
320-
SslUtils.getPkixTrustManagerFactory(),
330+
SslUtils.getPkixTrustManagerFactory(securityProvider),
321331
mtlsKeyStore,
322332
mtlsKeyStorePassword,
323-
SslUtils.getDefaultKeyManagerFactory());
333+
SslUtils.getDefaultKeyManagerFactory(securityProvider));
324334
return setSslSocketFactory(sslContext.getSocketFactory());
325335
}
326336

@@ -345,12 +355,69 @@ public SSLSocketFactory getSslSocketFactory() {
345355
return sslSocketFactory;
346356
}
347357

348-
/** Sets the SSL socket factory or {@code null} for the default. */
358+
/**
359+
* Sets the SSL socket factory or {@code null} for the default.
360+
*
361+
* <p>Note: If a custom {@link SslSocketConfigurator} is also provided, it will wrap and apply
362+
* its configuration callback to all sockets created by this factory.
363+
*/
349364
public Builder setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
350365
this.sslSocketFactory = sslSocketFactory;
351366
return this;
352367
}
353368

369+
/**
370+
* Sets the custom security provider or {@code null} to use the default JRE provider.
371+
*
372+
* <p>When enabling Post-Quantum Cryptography (PQC) transport:
373+
*
374+
* <ul>
375+
* <li>On JDK 8-19: A custom JCA provider (such as Conscrypt or BouncyCastle) must be
376+
* configured via this method, in addition to configuring a custom {@link
377+
* SslSocketConfigurator} callback to select the hybrid/PQC curves using provider-specific
378+
* APIs.
379+
* <li>On JDK 20-26: A custom provider (like Conscrypt) is recommended, but a custom {@link
380+
* SslSocketConfigurator} invoking {@code SSLParameters.setNamedGroups(String[])} directly
381+
* can be used natively without a custom JCA provider if standard JSSE supports the
382+
* curves.
383+
* <li>On JDK 27+: Neither a custom provider nor a configurator is required as PQC algorithms
384+
* are negotiated natively by default.
385+
* </ul>
386+
*
387+
* @param securityProvider provider to use
388+
*/
389+
public Builder setSecurityProvider(Provider securityProvider) {
390+
this.securityProvider = securityProvider;
391+
return this;
392+
}
393+
394+
/**
395+
* Sets the custom {@link SslSocketConfigurator} callback to configure active SSLSockets.
396+
*
397+
* <p>If both a custom {@link SSLSocketFactory} (via {@link
398+
* #setSslSocketFactory(SSLSocketFactory)}) and a custom configurator are set, the configurator
399+
* callback will be applied to all sockets created by the custom socket factory. If no custom
400+
* factory is provided, the configurator will wrap and apply to sockets created by the default
401+
* resolved socket factory.
402+
*
403+
* <p>When enabling Post-Quantum Cryptography (PQC) transport:
404+
*
405+
* <ul>
406+
* <li>On JDK 20-26: Callers can configure a custom {@link SslSocketConfigurator} that sets
407+
* the named groups (such as {@code X25519MLKEM768}) directly on {@code SSLParameters}.
408+
* <li>On JDK 8-19: Callers must provide a custom {@link SslSocketConfigurator} implementation
409+
* to inspect the socket types and invoke provider-specific API extensions (e.g. Conscrypt
410+
* JNI interfaces) to configure the curves.
411+
* </ul>
412+
*
413+
* @param configurator the callback configurator
414+
* @since 2.1.2
415+
*/
416+
public Builder setSslSocketConfigurator(SslSocketConfigurator configurator) {
417+
this.sslSocketConfigurator = configurator;
418+
return this;
419+
}
420+
354421
/** Returns the host name verifier or {@code null} for the default. */
355422
public HostnameVerifier getHostnameVerifier() {
356423
return hostnameVerifier;
@@ -362,14 +429,65 @@ public Builder setHostnameVerifier(HostnameVerifier hostnameVerifier) {
362429
return this;
363430
}
364431

432+
/**
433+
* Resolves the {@link SSLSocketFactory} to be used by the transport.
434+
*
435+
* <p>If a custom factory has been set via {@link #setSslSocketFactory(SSLSocketFactory)}, it
436+
* will be returned. Otherwise, a default SSL socket factory will be constructed via {@link
437+
* #createDefaultSslSocketFactory()}.
438+
*
439+
* @return the resolved {@link SSLSocketFactory}
440+
*/
441+
SSLSocketFactory resolveSslSocketFactory() {
442+
if (securityProvider == null && sslSocketConfigurator == null) {
443+
return sslSocketFactory;
444+
}
445+
SSLSocketFactory factory =
446+
sslSocketFactory != null ? sslSocketFactory : createDefaultSslSocketFactory();
447+
if (sslSocketConfigurator != null) {
448+
return new ConfigurableSSLSocketFactory(factory, sslSocketConfigurator);
449+
}
450+
return factory;
451+
}
452+
453+
/**
454+
* Constructs a default {@link SSLSocketFactory} configured with the specified {@link Provider}.
455+
*
456+
* <p>This method initializes an {@link SSLContext} and resolves its {@link TrustManagerFactory}
457+
* using the same security provider (if provided), ensuring compatibility for TLS handshakes
458+
* when using custom providers (such as Conscrypt).
459+
*
460+
* @return the initialized default {@link SSLSocketFactory}
461+
*/
462+
SSLSocketFactory createDefaultSslSocketFactory() {
463+
try {
464+
SSLContext sslContext = SslUtils.getTlsSslContext(securityProvider);
465+
TrustManager[] trustManagers = null;
466+
if (securityProvider != null) {
467+
try {
468+
TrustManagerFactory tmf = SslUtils.getDefaultTrustManagerFactory(securityProvider);
469+
tmf.init((KeyStore) null);
470+
trustManagers = tmf.getTrustManagers();
471+
} catch (Exception e) {
472+
// Ignore and fall back to default
473+
}
474+
}
475+
sslContext.init(null, trustManagers, null);
476+
return sslContext.getSocketFactory();
477+
} catch (Exception e) {
478+
return (SSLSocketFactory) SSLSocketFactory.getDefault();
479+
}
480+
}
481+
365482
/** Returns a new instance of {@link NetHttpTransport} based on the options. */
366483
public NetHttpTransport build() {
367484
if (System.getProperty(SHOULD_USE_PROXY_FLAG) != null) {
368485
setProxy(defaultProxy());
369486
}
487+
SSLSocketFactory resolvedFactory = resolveSslSocketFactory();
370488
return this.proxy == null
371-
? new NetHttpTransport(connectionFactory, sslSocketFactory, hostnameVerifier, isMtls)
372-
: new NetHttpTransport(this.proxy, sslSocketFactory, hostnameVerifier, isMtls);
489+
? new NetHttpTransport(connectionFactory, resolvedFactory, hostnameVerifier, isMtls)
490+
: new NetHttpTransport(this.proxy, resolvedFactory, hostnameVerifier, isMtls);
373491
}
374492
}
375493
}

0 commit comments

Comments
 (0)