Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2036bc3
chore: [wip] PQC POC 2
diegomarquezp May 14, 2026
1aa3581
feat: use pqc mtls by default
diegomarquezp May 23, 2026
27a56bb
fix: revert local changes
diegomarquezp May 23, 2026
b6944b7
test: move pqc tests to this repository
diegomarquezp May 25, 2026
6b82602
chore: ignore vscode files
diegomarquezp May 25, 2026
3f4387b
fix: simplify implementation
diegomarquezp May 25, 2026
5adf526
docs: correct released version
diegomarquezp May 25, 2026
b08be58
build: stages
diegomarquezp May 25, 2026
cd0dc4d
fix: simplify grpc server setup
diegomarquezp May 25, 2026
95d0601
ci: implement sequential two-phase build in build.sh to resolve pqc-t…
diegomarquezp May 25, 2026
166d83c
build: fix ci
diegomarquezp May 26, 2026
977b9b4
fix: use BC trust managers when resolving SSL context
diegomarquezp May 26, 2026
d278c83
chore: format
diegomarquezp May 26, 2026
102dd77
fix: enable PQC on java 8 and 21
diegomarquezp May 26, 2026
5d39b91
feat: move PqcPeerHostSSLSocketFactory to a new class
diegomarquezp May 26, 2026
fe6eb10
test: partial implementation of pqc grpc server
diegomarquezp May 26, 2026
938edaa
fix: simplify setup of BC provider
diegomarquezp May 27, 2026
077baf9
test: enforce pqc using local providers
diegomarquezp May 27, 2026
44fb51c
fix: remove logging from SslUtils
diegomarquezp May 27, 2026
6834f71
fix: do not use global props
diegomarquezp May 27, 2026
dce7756
build: fix animal sniffer
diegomarquezp May 27, 2026
27e3fae
fix: local support of MLKEM in SSLUtils
diegomarquezp May 27, 2026
50adbc1
fix: support java 8
diegomarquezp May 27, 2026
cff2440
chore: format
diegomarquezp May 27, 2026
c780afb
fix: remove global providers
diegomarquezp May 28, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ nosetests.xml
.DS_Store
**/.classpath
**/.checkstyle
**/.vscode/

# Python utilities
*.pyc
17 changes: 15 additions & 2 deletions .kokoro/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,26 @@ source ${scriptDir}/common.sh
mvn -version
echo ${JOB_TYPE}

# attempt to install 3 times with exponential backoff (starting with 10 seconds)
retry_with_backoff 3 10 \
mvn install -B -V -ntp \
-DskipTests=true \
-Dclirr.skip=true \
-Dcheckstyle.skip=true \
-Denforcer.skip=true \
-Dmaven.javadoc.skip=true \
-Dgcloud.download.skip=true \
-pl !pqc-test,!pqc-test/pqc-test-common,!pqc-test/pqc-test-snapshot,!pqc-test/pqc-test-release \
-T 1C

retry_with_backoff 3 10 \
mvn install -B -V -ntp \
-DskipTests=true \
-Dclirr.skip=true \
-Dcheckstyle.skip=true \
-Denforcer.skip=true \
-Dmaven.javadoc.skip=true \
-Dgcloud.download.skip=true \
-pl pqc-test,pqc-test/pqc-test-common,pqc-test/pqc-test-snapshot,pqc-test/pqc-test-release \
-T 1C

# if GOOGLE_APPLICATION_CREDENTIALS is specified as a relative path, prepend Kokoro root directory onto it
Expand All @@ -47,7 +59,7 @@ set +e

case ${JOB_TYPE} in
test)
mvn test -B -ntp -Dclirr.skip=true -Denforcer.skip=true
mvn test -B -ntp -Dclirr.skip=true -Denforcer.skip=true -Dcheckstyle.skip=true
RETURN_CODE=$?
;;
lint)
Expand All @@ -65,6 +77,7 @@ integration)
-DtrimStackTrace=false \
-Dclirr.skip=true \
-Denforcer.skip=true \
-Dcheckstyle.skip=true \
-fae \
verify
RETURN_CODE=$?
Expand Down
2 changes: 1 addition & 1 deletion google-http-client-appengine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java17</artifactId>
<artifactId>java18</artifactId>
<version>1.0</version>
</signature>
</configuration>
Expand Down
16 changes: 16 additions & 0 deletions google-http-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,22 @@
<artifactId>opencensus-contrib-http-util</artifactId>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${project.bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-annotations</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
<version>${project.bouncycastle.version}</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import javax.net.ssl.HostnameVerifier;
Expand Down Expand Up @@ -92,13 +94,26 @@ private static Proxy defaultProxy() {
/** Whether the transport is mTLS. Default value is {@code false}. */
private final boolean isMtls;

/**
* Returns the default SSL socket factory, which is PQC-enabled if Bouncy Castle JJSSE is on the
* classpath.
*/
private static SSLSocketFactory getDefaultSslSocketFactory() {
try {
SSLContext sslContext = SslUtils.getTlsSslContext();
return sslContext.getSocketFactory();
} catch (Exception e) {
return null; // Fallback to default HttpsURLConnection behavior
}
}

/**
* Constructor with the default behavior.
*
* <p>Instead use {@link Builder} to modify behavior.
*/
public NetHttpTransport() {
this((ConnectionFactory) null, null, null, false);
this((ConnectionFactory) null, getDefaultSslSocketFactory(), null, false);
}

/**
Expand Down Expand Up @@ -171,7 +186,8 @@ protected NetHttpRequest buildRequest(String method, String url) throws IOExcept
secureConnection.setHostnameVerifier(hostnameVerifier);
}
if (sslSocketFactory != null) {
secureConnection.setSSLSocketFactory(sslSocketFactory);
secureConnection.setSSLSocketFactory(
new PqcPeerHostSSLSocketFactory(sslSocketFactory, connUrl.getHost()));
}
}
return new NetHttpRequest(connection);
Expand Down Expand Up @@ -294,6 +310,40 @@ public Builder trustCertificates(KeyStore trustStore) throws GeneralSecurityExce
return setSslSocketFactory(sslContext.getSocketFactory());
}

/**
* Sets the SSL socket factory based on a root certificate trust store and a specific security
* provider.
*
* @param trustStore certificate trust store
* @param provider security provider to use for SSL context
* @since 1.39
*/
public Builder trustCertificates(KeyStore trustStore, Provider provider)
throws GeneralSecurityException {
SSLContext sslContext = SslUtils.getTlsSslContext(provider);
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());
return setSslSocketFactory(sslContext.getSocketFactory());
}

/**
* Sets the SSL socket factory based on a root certificate trust store and a specific security
* provider name.
*
* @param trustStore certificate trust store
* @param providerName security provider name to use for SSL context
* @since 1.39
*/
public Builder trustCertificates(KeyStore trustStore, String providerName)
throws GeneralSecurityException {
try {
SSLContext sslContext = SslUtils.getTlsSslContext(providerName);
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());
return setSslSocketFactory(sslContext.getSocketFactory());
} catch (NoSuchProviderException e) {
throw new GeneralSecurityException(e);
}
}

/**
* {@link Beta} <br>
* Sets the SSL socket factory based on a root certificate trust store and a client certificate
Expand Down Expand Up @@ -367,9 +417,11 @@ public NetHttpTransport build() {
if (System.getProperty(SHOULD_USE_PROXY_FLAG) != null) {
setProxy(defaultProxy());
}
SSLSocketFactory factory =
sslSocketFactory != null ? sslSocketFactory : getDefaultSslSocketFactory();
return this.proxy == null
? new NetHttpTransport(connectionFactory, sslSocketFactory, hostnameVerifier, isMtls)
: new NetHttpTransport(this.proxy, sslSocketFactory, hostnameVerifier, isMtls);
? new NetHttpTransport(connectionFactory, factory, hostnameVerifier, isMtls)
: new NetHttpTransport(this.proxy, factory, hostnameVerifier, isMtls);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2026 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

package com.google.api.client.http.javanet;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLSocketFactory;

/**
* A custom {@link SSLSocketFactory} wrapper designed to ensure that the peer hostname is preserved
* during connection establishment.
*
* <p>When secure connections are initiated via Java's default {@code HttpURLConnection}, some
* socket-creation flows only provide an {@link InetAddress} instead of the DNS hostname. Under
* hybrid TLS configurations—such as Post-Quantum Cryptography (PQC)—underlying JSSE security
* providers (Conscrypt or Bouncy Castle JSSE) rely on the peer hostname string to enable proper
* Server Name Indication (SNI) extensions, negotiate PQC cipher suites, and perform endpoint
* identification.
*
* <p>This wrapper intercepts socket creation requests, manually establishes the TCP socket
* connection to the target address, and wraps it using the delegate's hostname-aware factory
* method.
*/
class PqcPeerHostSSLSocketFactory extends SSLSocketFactory {

private final SSLSocketFactory delegate;
private final String host;

/**
* Constructs a new {@link PqcPeerHostSSLSocketFactory} wrapping the provided delegate.
*
* @param delegate the underlying {@link SSLSocketFactory}
* @param host the peer hostname to propagate to the delegate socket factory
*/
PqcPeerHostSSLSocketFactory(SSLSocketFactory delegate, String host) {
this.delegate = delegate;
this.host = host;
}

@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
throws IOException {
return configureSocket(delegate.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket() throws IOException {
return configureSocket(delegate.createSocket());
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return configureSocket(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort)
throws IOException, UnknownHostException {
return configureSocket(delegate.createSocket(host, port, localAddress, localPort));
}

@Override
public Socket createSocket(InetAddress address, int port) throws IOException {
Socket plainSocket = new Socket();
plainSocket.connect(new InetSocketAddress(address, port));
return configureSocket(delegate.createSocket(plainSocket, this.host, port, true));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
throws IOException {
Socket plainSocket = new Socket();
plainSocket.bind(new InetSocketAddress(localAddress, localPort));
plainSocket.connect(new InetSocketAddress(address, port));
return configureSocket(delegate.createSocket(plainSocket, this.host, port, true));
}

@org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
private Socket configureSocket(Socket socket) {
if (socket instanceof javax.net.ssl.SSLSocket) {
javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) socket;
try {
javax.net.ssl.SSLParameters params = sslSocket.getSSLParameters();
if (params != null) {
java.util.List<javax.net.ssl.SNIServerName> serverNames = new java.util.ArrayList<>();
serverNames.add(new javax.net.ssl.SNIHostName(this.host));
params.setServerNames(serverNames);
sslSocket.setSSLParameters(params);
}
} catch (Exception e) {
// Ignore
}
}
return socket;
}
}
Loading
Loading