2828import java .net .URL ;
2929import java .security .GeneralSecurityException ;
3030import java .security .KeyStore ;
31+ import java .security .Provider ;
3132import java .security .cert .CertificateFactory ;
3233import java .util .Arrays ;
3334import javax .net .ssl .HostnameVerifier ;
3435import javax .net .ssl .HttpsURLConnection ;
3536import javax .net .ssl .SSLContext ;
3637import 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