Skip to content
Draft
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
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
11 changes: 11 additions & 0 deletions google-http-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,17 @@
<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.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 @@ -294,6 +309,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 +416,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
Expand Up @@ -17,7 +17,11 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
Expand All @@ -27,6 +31,8 @@
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;

/**
* SSL utilities.
Expand All @@ -46,12 +52,65 @@ public static SSLContext getSslContext() throws NoSuchAlgorithmException {
}

/**
* Returns the SSL context for "TLS" algorithm.
* Returns the SSL context for "TLS" algorithm using Bouncy Castle JJSSE provider
* scope-specifically.
*
* @since 1.14
* @since 2.1.1
*/
public static SSLContext getTlsSslContext() throws NoSuchAlgorithmException {
return SSLContext.getInstance("TLS");
// 1. Explicitly register Bouncy Castle cryptographic provider globally if not already present.
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}

// 2. Explicitly instantiate Bouncy Castle cryptographic (JCA) provider instance.
BouncyCastleProvider cryptoProvider = new BouncyCastleProvider();

// 3. Explicitly instantiate Bouncy Castle JJSSE provider bound to our crypto provider.
BouncyCastleJsseProvider provider = new BouncyCastleJsseProvider(cryptoProvider);

// 3. Create standard TLS context instance bound specifically to our Bouncy Castle JJSSE
// provider.
SSLContext bcContext = SSLContext.getInstance("TLS", provider);

try {
// 4. Initialize the Bouncy Castle SSLContext with default managers.
bcContext.init(null, null, null);
} catch (GeneralSecurityException e) {
// Print diagnostic trace to help understand why Bouncy Castle JSSE failed to initialize.
e.printStackTrace();
// 5. Retrieve standard JJSSE default context if BC JJSSE initialization fails.
SSLContext fallbackContext = SSLContext.getInstance("TLS");
try {
// Initialize the fallback context with default managers as well.
fallbackContext.init(null, null, null);
} catch (GeneralSecurityException ex) {
// Ignore fallback initialization failure
}
return fallbackContext;
}

// 6. Return the raw Bouncy Castle SSLContext.
return bcContext;
}

/**
* Returns the SSL context for "TLS" algorithm using the specified provider.
*
* @since 1.39
*/
public static SSLContext getTlsSslContext(Provider provider) throws NoSuchAlgorithmException {
return SSLContext.getInstance("TLS", provider);
}

/**
* Returns the SSL context for "TLS" algorithm using the specified provider name.
*
* @since 2.1.1
*/
public static SSLContext getTlsSslContext(String providerName)
throws NoSuchAlgorithmException, NoSuchProviderException {
return SSLContext.getInstance("TLS", providerName);
}

/**
Expand Down Expand Up @@ -106,8 +165,8 @@ public static KeyManagerFactory getPkixKeyManagerFactory() throws NoSuchAlgorith
public static SSLContext initSslContext(
SSLContext sslContext, KeyStore trustStore, TrustManagerFactory trustManagerFactory)
throws GeneralSecurityException {
trustManagerFactory.init(trustStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
sslContext.init(
null, getCompatibleTrustManagers(sslContext, trustStore, trustManagerFactory), null);
return sslContext;
}

Expand Down Expand Up @@ -137,13 +196,38 @@ public static SSLContext initSslContext(
String mtlsKeyStorePassword,
KeyManagerFactory keyManagerFactory)
throws GeneralSecurityException {
trustManagerFactory.init(trustStore);
keyManagerFactory.init(mtlsKeyStore, mtlsKeyStorePassword.toCharArray());
sslContext.init(
keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
keyManagerFactory.getKeyManagers(),
getCompatibleTrustManagers(sslContext, trustStore, trustManagerFactory),
null);
return sslContext;
}

/**
* Resolves trust managers compatible with the active security provider. If the SSLContext is
* managed by the Bouncy Castle JJSSE provider, it retrieves Bouncy Castle's native trust managers
* instead of standard JDK trust managers. This prevents JCA trust manager wrapping mismatches and
* unresolved peer host certificate exceptions on strict JVMs (e.g., Java 8/21).
*/
private static TrustManager[] getCompatibleTrustManagers(
SSLContext sslContext, KeyStore trustStore, TrustManagerFactory trustManagerFactory)
throws GeneralSecurityException {
if (sslContext.getProvider() instanceof BouncyCastleJsseProvider) {
try {
TrustManagerFactory bcTmf =
TrustManagerFactory.getInstance(
trustManagerFactory.getAlgorithm(), sslContext.getProvider());
bcTmf.init(trustStore);
return bcTmf.getTrustManagers();
} catch (KeyStoreException | NoSuchAlgorithmException e) {
// Fallback to default trust managers
}
}
trustManagerFactory.init(trustStore);
return trustManagerFactory.getTrustManagers();
}

/**
* {@link Beta} <br>
* Returns an SSL context in which all X.509 certificates are trusted.
Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@

<!-- A deployable artifact must be last or deploys are skipped -->
<module>google-http-client-bom</module>
<module>pqc-test</module>
</modules>

<pluginRepositories>
Expand Down Expand Up @@ -547,6 +548,7 @@
- google-api-java-client/google-api-client-assembly/android-properties (make the filenames match the version here)
- Internally, update the default features.json file
-->
<project.bouncycastle.version>1.80</project.bouncycastle.version>
<project.http-client.version>2.1.1-SNAPSHOT</project.http-client.version><!-- {x-version-update:google-http-client-parent:current} -->
<project.appengine.version>2.0.32</project.appengine.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
33 changes: 33 additions & 0 deletions pqc-test/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#118019",
"activityBar.background": "#118019",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#261ac5",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#118019",
"statusBar.background": "#0b5310",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#118019",
"statusBarItem.remoteBackground": "#0b5310",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#0b5310",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#0b531099",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.color": "#0b5310",
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/*.class": true
},
"java.autobuild.enabled": true,
"java.import.maven.enabled": true,
"java.configuration.updateBuildConfiguration": "interactive",
}
23 changes: 23 additions & 0 deletions pqc-test/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-shared-config</artifactId>
<version>1.17.0</version>
</parent>

<groupId>com.google.api</groupId>
<artifactId>pqc-test-parent</artifactId>
<packaging>pom</packaging>
<version>2.81.0-SNAPSHOT</version>

<modules>
<module>pqc-test-common</module>
<module>pqc-test-snapshot</module>
<module>pqc-test-release</module>
</modules>
</project>
Loading
Loading