diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index db2d8ad17..f90453e92 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,10 +4,10 @@ # For syntax help see: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax -* @googleapis/yoshi-java +* @googleapis/cloud-sdk-java-team # The java-samples-reviewers team is the default owner for samples changes samples/**/*.java @googleapis/java-samples-reviewers # Generated snippets should not be owned by samples reviewers -samples/snippets/generated/ @googleapis/yoshi-java +samples/snippets/generated/ @googleapis/cloud-sdk-java-team diff --git a/.github/release-please.yml b/.github/release-please.yml index 3497d88b7..0e6e915af 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -26,3 +26,7 @@ branches: handleGHRelease: true releaseType: java-backport branch: 2.7.x + - bumpMinorPreMajor: true + handleGHRelease: true + releaseType: java-backport + branch: 2.8.x diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 9bf331a37..98249d537 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -101,6 +101,20 @@ branchProtectionRules: - lint - clirr - cla/google + - pattern: 2.8.x + isAdminEnforced: true + requiredApprovingReviewCount: 1 + requiresCodeOwnerReviews: true + requiresStrictStatusChecks: false + requiredStatusCheckContexts: + - units (8) + - units (11) + - windows + - dependencies (8) + - dependencies (11) + - lint + - clirr + - cla/google permissionRules: - team: yoshi-admins permission: admin diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 05593d27a..c1a809fe1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -117,15 +117,15 @@ jobs: - run: .kokoro/build.sh env: JOB_TYPE: test - units-java24: + units-java25: # Building using Java 8 and run the tests with Java 24 runtime - name: "units (24)" + name: "units (25)" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v3 with: - java-version: 24 + java-version: 25 distribution: temurin - name: "Set jvm system property environment variable for surefire plugin (unit tests)" # Maven surefire plugin (unit tests) allows us to specify JVM to run the tests. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d2e9ecee..e5f2958b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [2.9.0](https://github.com/googleapis/google-api-java-client/compare/v2.8.1...v2.9.0) (2026-02-24) + + +### Features + +* Next release from main branch is 2.9.0 ([#2614](https://github.com/googleapis/google-api-java-client/issues/2614)) ([857362a](https://github.com/googleapis/google-api-java-client/commit/857362a29c6100fad69ccad3c78ec421ca317c44)) +* Update GoogleUtils#getCertificateTrustStore to first attempt t… ([#2613](https://github.com/googleapis/google-api-java-client/issues/2613)) ([dddb15b](https://github.com/googleapis/google-api-java-client/commit/dddb15bbc5ae59c06ebd39990b925db3ca1e08bc)) + + +### Bug Fixes + +* **sec:** Warn users of unsafe credential generation methods ([#2604](https://github.com/googleapis/google-api-java-client/issues/2604)) ([d4c0a33](https://github.com/googleapis/google-api-java-client/commit/d4c0a33ecfd21f049de320c4a078518187862c8a)) +* Use Cloud RAD in deprecation link in GoogleCredential class ([#2606](https://github.com/googleapis/google-api-java-client/issues/2606)) ([f238d59](https://github.com/googleapis/google-api-java-client/commit/f238d59a661cf05ce0a8585c0d00ac50d832f6bc)) + ## [2.8.1](https://github.com/googleapis/google-api-java-client/compare/v2.8.0...v2.8.1) (2025-08-14) diff --git a/README.md b/README.md index 40291459f..bd8852fd0 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,7 @@ mvn install:install-file \ cd - ``` -## CI Status -Java Version | Status ------------- | ------ -Java 7 | [![Kokoro CI](https://storage.googleapis.com/cloud-devrel-public/java/badges/google-api-java-client/java7.svg)](https://storage.googleapis.com/cloud-devrel-public/java/badges/google-api-java-client/java7.html) -Java 8 | [![Kokoro CI](https://storage.googleapis.com/cloud-devrel-public/java/badges/google-api-java-client/java8.svg)](https://storage.googleapis.com/cloud-devrel-public/java/badges/google-api-java-client/java8.html) -Java 11 | [![Kokoro CI](https://storage.googleapis.com/cloud-devrel-public/java/badges/google-api-java-client/java11.svg)](https://storage.googleapis.com/cloud-devrel-public/java/badges/google-api-java-client/java11.html) ## Dependencies This library is built on top of two common libraries, also built by Google, and also designed to diff --git a/docs/oauth-2.0.md b/docs/oauth-2.0.md index 56a72b165..029b12bef 100644 --- a/docs/oauth-2.0.md +++ b/docs/oauth-2.0.md @@ -47,7 +47,7 @@ For instructions on setting up your credentials properly, see the already have an access token, you can make a request in the following way: ```java -import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import com.google.api.services.books.Books; import com.google.auth.http.HttpCredentialsAdapter; @@ -59,7 +59,7 @@ GoogleCredentials credentials = Books books = new Books.Builder( - GoogleNetHttpTransport.newTrustedTransport(), + new NetHttpTransport(), GsonFactory.getDefaultInstance(), new HttpCredentialsAdapter(credentials)) .setApplicationName("BooksExample/1.0") @@ -79,7 +79,7 @@ App Engine takes care of all of the details. You only specify the OAuth 2.0 scope you need. ```java -import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import com.google.api.services.books.Books; import com.google.appengine.api.appidentity.AppIdentityService; @@ -99,7 +99,7 @@ GoogleCredentials credentials = Books books = new Books.Builder( - GoogleNetHttpTransport.newTrustedTransport(), + new NetHttpTransport(), GsonFactory.getDefaultInstance(), new HttpCredentialsAdapter(credentials)) .setApplicationName("BooksExample/1.0") @@ -373,7 +373,7 @@ a private key downloaded from the [Google API Console][console]. For example, you can make a request in the following way: ```java -HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); +HttpTransport httpTransport = new NetHttpTransport(); JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); //Build service account credential @@ -405,12 +405,12 @@ additionally call [`GoogleCredential.Builder.setServiceAccountUser(String)`][set This is the command-line authorization code flow described in [Using OAuth 2.0 for Installed Applications][oauth2-installed-app]. -Example snippet from [plus-cmdline-sample][plus-sample]: +Example usage: ```java public static void main(String[] args) { try { - httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + httpTransport = new NetHttpTransport(); dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR); // authorization Credential credential = authorize(); diff --git a/google-api-client-android/pom.xml b/google-api-client-android/pom.xml index 852ffdc95..8c1f65cac 100644 --- a/google-api-client-android/pom.xml +++ b/google-api-client-android/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client-android diff --git a/google-api-client-apache-v5/pom.xml b/google-api-client-apache-v5/pom.xml index 08e13e78c..ad83fc8d5 100644 --- a/google-api-client-apache-v5/pom.xml +++ b/google-api-client-apache-v5/pom.xml @@ -6,7 +6,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 google-api-client-apache-v5 Apache extensions to the Google APIs Client Library for Java diff --git a/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransport.java b/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransport.java index 00a7673b3..f056024eb 100644 --- a/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransport.java +++ b/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransport.java @@ -14,10 +14,8 @@ package com.google.api.client.googleapis.apache.v5; -import com.google.api.client.googleapis.GoogleUtils; import com.google.api.client.googleapis.mtls.MtlsProvider; import com.google.api.client.googleapis.mtls.MtlsUtils; -import com.google.api.client.googleapis.util.Utils; import com.google.api.client.http.apache.v5.Apache5HttpTransport; import com.google.api.client.util.SslUtils; import com.google.common.annotations.Beta; @@ -48,11 +46,11 @@ public final class GoogleApache5HttpTransport { /** - * Returns a new instance of {@link Apache5HttpTransport} that uses {@link - * GoogleUtils#getCertificateTrustStore()} for the trusted certificates. If - * `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true", and the default - * client certificate key store from {@link Utils#loadDefaultMtlsKeyStore()} is not null, then the - * transport uses the default client certificate and is mutual TLS. + * Returns a new instance of {@link Apache5HttpTransport} that uses default jdk certificates for + * the trusted certificates. If `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to + * "true", and the default client certificate key store from {@link + * Utils#loadDefaultMtlsKeyStore()} is not null, then the transport uses the default client + * certificate and is mutual TLS. */ public static Apache5HttpTransport newTrustedTransport() throws GeneralSecurityException, IOException { @@ -61,9 +59,8 @@ public static Apache5HttpTransport newTrustedTransport() /** * {@link Beta}
- * Returns a new instance of {@link Apache5HttpTransport} that uses {@link - * GoogleUtils#getCertificateTrustStore()} for the trusted certificates. mtlsProvider can be used - * to configure mutual TLS for the transport. + * Returns a new instance of {@link Apache5HttpTransport} that uses default jdk certificates for + * the trusted certificates. mtlsProvider can be used to configure mutual TLS for the transport. * * @param mtlsProvider MtlsProvider to configure mutual TLS for the transport */ @@ -109,22 +106,20 @@ public SocketFactoryRegistryHandler(MtlsProvider mtlsProvider) mtlsKeyStorePassword = mtlsProvider.getKeyStorePassword(); } - // Use the included trust store - KeyStore trustStore = GoogleUtils.getCertificateTrustStore(); SSLContext sslContext = SslUtils.getTlsSslContext(); if (mtlsKeyStore != null && mtlsKeyStorePassword != null) { this.isMtls = true; SslUtils.initSslContext( sslContext, - trustStore, + null, SslUtils.getPkixTrustManagerFactory(), mtlsKeyStore, mtlsKeyStorePassword, SslUtils.getDefaultKeyManagerFactory()); } else { this.isMtls = false; - SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory()); + SslUtils.initSslContext(sslContext, null, SslUtils.getPkixTrustManagerFactory()); } LayeredConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext); diff --git a/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/ITGoogleApache5HttpTransportTest.java b/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/ITGoogleApache5HttpTransportTest.java index f8e9cbed1..8398606d3 100644 --- a/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/ITGoogleApache5HttpTransportTest.java +++ b/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/ITGoogleApache5HttpTransportTest.java @@ -30,10 +30,11 @@ public class ITGoogleApache5HttpTransportTest { @Test - public void testHttpRequestFailsWhenMakingRequestToSiteWithoutGoogleCerts() + public void testHttpRequestFailsWhenMakingRequestToSiteWithoutDefaultJdkCerts() throws GeneralSecurityException, IOException { Apache5HttpTransport apache5HttpTransport = GoogleApache5HttpTransport.newTrustedTransport(); - HttpGet httpGet = new HttpGet("https://maven.com/"); + // Use a self-signed certificate site that won't be trusted by default trust store + HttpGet httpGet = new HttpGet("https://self-signed.badssl.com/"); Exception exception = null; try { apache5HttpTransport @@ -43,7 +44,7 @@ public void testHttpRequestFailsWhenMakingRequestToSiteWithoutGoogleCerts() new HttpClientResponseHandler() { @Override public Void handleResponse(ClassicHttpResponse response) { - fail("Should not have been able to complete SSL request on non google site."); + fail("Should not have been able to complete SSL request with untrusted cert."); return null; } }); @@ -73,4 +74,23 @@ public Void handleResponse(ClassicHttpResponse response) { } }); } + + @Test + public void testHttpRequestPassesWhenMakingRequestToSiteContainedInDefaultCerts() + throws Exception { + Apache5HttpTransport apache5HttpTransport = GoogleApache5HttpTransport.newTrustedTransport(); + HttpGet httpGet = new HttpGet("https://central.sonatype.com/"); + + apache5HttpTransport + .getHttpClient() + .execute( + httpGet, + new HttpClientResponseHandler() { + @Override + public Void handleResponse(ClassicHttpResponse response) { + assertEquals(200, response.getCode()); + return null; + } + }); + } } diff --git a/google-api-client-appengine/pom.xml b/google-api-client-appengine/pom.xml index 256676ef1..c8711597d 100644 --- a/google-api-client-appengine/pom.xml +++ b/google-api-client-appengine/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client-appengine diff --git a/google-api-client-assembly/pom.xml b/google-api-client-assembly/pom.xml index 09c1b9b82..940358c9a 100644 --- a/google-api-client-assembly/pom.xml +++ b/google-api-client-assembly/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml com.google.api-client diff --git a/google-api-client-bom/pom.xml b/google-api-client-bom/pom.xml index 3b06e6795..aa398f1f0 100644 --- a/google-api-client-bom/pom.xml +++ b/google-api-client-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.api-client google-api-client-bom - 2.8.2-SNAPSHOT + 2.9.0 pom Google API Client Library for Java BOM @@ -63,48 +63,48 @@ com.google.api-client google-api-client - 2.8.2-SNAPSHOT + 2.9.0 com.google.api-client google-api-client-android - 2.8.2-SNAPSHOT + 2.9.0 com.google.api-client google-api-client-appengine - 2.8.2-SNAPSHOT + 2.9.0 com.google.api-client google-api-client-assembly - 2.8.2-SNAPSHOT + 2.9.0 pom com.google.api-client google-api-client-gson - 2.8.2-SNAPSHOT + 2.9.0 com.google.api-client google-api-client-jackson2 - 2.8.2-SNAPSHOT + 2.9.0 com.google.api-client google-api-client-protobuf - 2.8.2-SNAPSHOT + 2.9.0 com.google.api-client google-api-client-servlet - 2.8.2-SNAPSHOT + 2.9.0 com.google.api-client google-api-client-xml - 2.8.2-SNAPSHOT + 2.9.0 diff --git a/google-api-client-gson/pom.xml b/google-api-client-gson/pom.xml index e48550fde..b326dab67 100644 --- a/google-api-client-gson/pom.xml +++ b/google-api-client-gson/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client-gson diff --git a/google-api-client-jackson2/pom.xml b/google-api-client-jackson2/pom.xml index 8f7323128..94015fe65 100644 --- a/google-api-client-jackson2/pom.xml +++ b/google-api-client-jackson2/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client-jackson2 diff --git a/google-api-client-protobuf/pom.xml b/google-api-client-protobuf/pom.xml index 795123e50..7ea74d8b5 100644 --- a/google-api-client-protobuf/pom.xml +++ b/google-api-client-protobuf/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client-protobuf diff --git a/google-api-client-servlet/pom.xml b/google-api-client-servlet/pom.xml index 287a8d391..719eb10d3 100644 --- a/google-api-client-servlet/pom.xml +++ b/google-api-client-servlet/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client-servlet diff --git a/google-api-client-xml/pom.xml b/google-api-client-xml/pom.xml index a06186c04..d03e39d2e 100644 --- a/google-api-client-xml/pom.xml +++ b/google-api-client-xml/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client-xml diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml index db16f6a5a..fc268e47d 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -4,7 +4,7 @@ com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 ../pom.xml google-api-client diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/GoogleUtils.java b/google-api-client/src/main/java/com/google/api/client/googleapis/GoogleUtils.java index c15c8a458..7a6fb7bc5 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/GoogleUtils.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/GoogleUtils.java @@ -16,6 +16,8 @@ import com.google.api.client.util.SecurityUtils; import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; @@ -69,21 +71,87 @@ public final class GoogleUtils { } /** Cached value for {@link #getCertificateTrustStore()}. */ - static KeyStore certTrustStore; + @VisibleForTesting static KeyStore certTrustStore; + + /** Name of bundled keystore. */ + static final String BUNDLED_KEYSTORE = "google.p12"; + + /** Bundled keystore password. */ + static final String BUNDLED_KEYSTORE_PASSWORD = "notasecret"; + + /** Default JDK cacerts file path relative to java.home. */ + @VisibleForTesting + static String[] possibleJdkPaths = {"lib/security/cacerts", "jre/lib/security/cacerts"}; + + /** Java home system property key. */ + static final String JAVA_HOME_KEY = "java.home"; + + /** Default password for JDK cacerts file. */ + static final String DEFAULT_CACERTS_PASSWORD = "changeit"; + + /** + * Loads the bundled google.p12 keystore containing trusted root certificates. + * + * @return the loaded keystore + */ + @VisibleForTesting + static KeyStore getBundledKeystore() throws IOException, GeneralSecurityException { + KeyStore ks = SecurityUtils.getPkcs12KeyStore(); + InputStream is = GoogleUtils.class.getResourceAsStream(BUNDLED_KEYSTORE); + SecurityUtils.loadKeyStore(ks, is, BUNDLED_KEYSTORE_PASSWORD); + return ks; + } /** - * Returns the key store for trusted root certificates to use for Google APIs. + * Loads the default JDK keystore (cacerts) containing trusted root certificates. Uses Java's + * system properties + known cert locations to locate the default trust store. + * + * @return the loaded keystore or null if unable to find/load keystore + */ + @VisibleForTesting + static KeyStore getJdkDefaultKeyStore() throws IOException, GeneralSecurityException { + KeyStore keyStore = SecurityUtils.getDefaultKeyStore(); + + // Find the default JDK cacerts location + String javaHome = System.getProperty(JAVA_HOME_KEY); + + for (String path : possibleJdkPaths) { + File cacertsFile = new File(javaHome, path); + try (FileInputStream fis = new FileInputStream(cacertsFile)) { + keyStore.load(fis, DEFAULT_CACERTS_PASSWORD.toCharArray()); + return keyStore; + } catch (IOException e) { + // File doesn't exist or can't be read, try next path + } + } + return null; + } + + /** + * Returns a keystore for trusted root certificates to use for Google APIs. * *

Value is cached, so subsequent access is fast. * + *

This method first attempts to load the JDK default keystore. If that fails or is not + * available, it falls back to loading the bundled Google certificate store. + * * @since 1.14 + * @deprecated Depending on your build environment this method potentially can contain outdated + * certs if loading jdk default certs fails. Instead of getting trusted certs directly use an + * HttpTransport wrapper such as {@link NetHttpTransport} + * which uses java jdk internal classes to load default jdk certs specifically for a build + * environment. If you need to access the keystore directly please create your own keystore + * file. */ + @Deprecated public static synchronized KeyStore getCertificateTrustStore() throws IOException, GeneralSecurityException { if (certTrustStore == null) { - certTrustStore = SecurityUtils.getPkcs12KeyStore(); - InputStream keyStoreStream = GoogleUtils.class.getResourceAsStream("google.p12"); - SecurityUtils.loadKeyStore(certTrustStore, keyStoreStream, "notasecret"); + certTrustStore = getJdkDefaultKeyStore(); + if (certTrustStore == null) { + certTrustStore = getBundledKeystore(); + } } return certTrustStore; } diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/apache/v2/GoogleApacheHttpTransport.java b/google-api-client/src/main/java/com/google/api/client/googleapis/apache/v2/GoogleApacheHttpTransport.java index 93347cd3e..11c8dbbf3 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/apache/v2/GoogleApacheHttpTransport.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/apache/v2/GoogleApacheHttpTransport.java @@ -14,10 +14,8 @@ package com.google.api.client.googleapis.apache.v2; -import com.google.api.client.googleapis.GoogleUtils; import com.google.api.client.googleapis.mtls.MtlsProvider; import com.google.api.client.googleapis.mtls.MtlsUtils; -import com.google.api.client.googleapis.util.Utils; import com.google.api.client.http.apache.v2.ApacheHttpTransport; import com.google.api.client.util.Beta; import com.google.api.client.util.SslUtils; @@ -47,11 +45,11 @@ public final class GoogleApacheHttpTransport { /** - * Returns a new instance of {@link ApacheHttpTransport} that uses {@link - * GoogleUtils#getCertificateTrustStore()} for the trusted certificates. If - * `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true", and the default - * client certificate key store from {@link Utils#loadDefaultMtlsKeyStore()} is not null, then the - * transport uses the default client certificate and is mutual TLS. + * Returns a new instance of {@link ApacheHttpTransport} that uses default jdk certificates for + * the trusted certificates. If `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to + * "true", and the default client certificate key store from {@link + * Utils#loadDefaultMtlsKeyStore()} is not null, then the transport uses the default client + * certificate and is mutual TLS. */ public static ApacheHttpTransport newTrustedTransport() throws GeneralSecurityException, IOException { @@ -60,9 +58,8 @@ public static ApacheHttpTransport newTrustedTransport() /** * {@link Beta}
- * Returns a new instance of {@link ApacheHttpTransport} that uses {@link - * GoogleUtils#getCertificateTrustStore()} for the trusted certificates. mtlsProvider can be used - * to configure mutual TLS for the transport. + * Returns a new instance of {@link ApacheHttpTransport} that default jdk certs for the trusted + * certificates. mtlsProvider can be used to configure mutual TLS for the transport. * * @param mtlsProvider MtlsProvider to configure mutual TLS for the transport */ @@ -105,22 +102,20 @@ public SocketFactoryRegistryHandler(MtlsProvider mtlsProvider) mtlsKeyStorePassword = mtlsProvider.getKeyStorePassword(); } - // Use the included trust store - KeyStore trustStore = GoogleUtils.getCertificateTrustStore(); SSLContext sslContext = SslUtils.getTlsSslContext(); if (mtlsKeyStore != null && mtlsKeyStorePassword != null) { this.isMtls = true; SslUtils.initSslContext( sslContext, - trustStore, + null, SslUtils.getPkixTrustManagerFactory(), mtlsKeyStore, mtlsKeyStorePassword, SslUtils.getDefaultKeyManagerFactory()); } else { this.isMtls = false; - SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory()); + SslUtils.initSslContext(sslContext, null, SslUtils.getPkixTrustManagerFactory()); } LayeredConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext); diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleCredential.java b/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleCredential.java index 590a422e2..1d8c70a51 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleCredential.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleCredential.java @@ -197,42 +197,64 @@ public static GoogleCredential getApplicationDefault( /** * {@link Beta}
- * Return a credential defined by a Json file. * - *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an - * external source for authentication to Google Cloud Platform, you must validate it before - * providing it to any Google API or library. Providing an unvalidated credential configuration to - * Google APIs can compromise the security of your systems and data. For more information, refer - * to {@link documentation}. + *

Important: This method does not validate the credential configuration. A security risk holds + * when a credential configuration is accepted from a source that is not under your control and + * used without validation on your side. + * + *

If you are loading your credential configuration from an untrusted source and have not + * mitigated the risks (e.g. by validating the configuration yourself), make these changes as soon + * as possible to prevent security risks to your environment. + * + *

Regardless of the method used, it is always your responsibility to validate configurations + * received from external sources. + * + *

See the {@link documentation} + * for more details. + * + *

Returns a credential defined by a Json file. * * @param credentialStream the stream with the credential definition. * @return the credential defined by the credentialStream. * @throws IOException if the credential cannot be created from the stream. + * @deprecated This method is being deprecated because of a potential security risk. + * Please use {@link GoogleCredentials instead. */ @Beta + @Deprecated public static GoogleCredential fromStream(InputStream credentialStream) throws IOException { return fromStream(credentialStream, Utils.getDefaultTransport(), Utils.getDefaultJsonFactory()); } /** * {@link Beta}
- * Return a credential defined by a Json file. * - *

Important: If you accept a credential configuration (credential JSON/File/Stream) from an - * external source for authentication to Google Cloud Platform, you must validate it before - * providing it to any Google API or library. Providing an unvalidated credential configuration to - * Google APIs can compromise the security of your systems and data. For more information, refer - * to {@link documentation}. + *

Important: This method does not validate the credential configuration. A security risk holds + * when a credential configuration is accepted from a source that is not under your control and + * used without validation on your side. + * + *

If you are loading your credential configuration from an untrusted source and have not + * mitigated the risks (e.g. by validating the configuration yourself), make these changes as soon + * as possible to prevent security risks to your environment. + * + *

Regardless of the method used, it is always your responsibility to validate configurations + * received from external sources. + * + *

See the {@link documentation} + * for more details. + * + *

Returns a credential defined by a Json file. * * @param credentialStream the stream with the credential definition. - * @param transport the transport for Http calls. - * @param jsonFactory the factory for Json parsing and formatting. * @return the credential defined by the credentialStream. * @throws IOException if the credential cannot be created from the stream. + * @deprecated This method is being deprecated because of a potential security risk. + * Please use {@link GoogleCredentials instead. */ @Beta + @Deprecated public static GoogleCredential fromStream( InputStream credentialStream, HttpTransport transport, JsonFactory jsonFactory) throws IOException { diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/javanet/GoogleNetHttpTransport.java b/google-api-client/src/main/java/com/google/api/client/googleapis/javanet/GoogleNetHttpTransport.java index 66907fc72..eb4d00b46 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/javanet/GoogleNetHttpTransport.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/javanet/GoogleNetHttpTransport.java @@ -29,7 +29,10 @@ * * @since 1.14 * @author Yaniv Inbar + * @deprecated This legacy HttpTransport implementation is no longer being maintained. + * Please use {@link NetHttpTransport instead. */ +@Deprecated public class GoogleNetHttpTransport { /** diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/GoogleUtilsTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/GoogleUtilsTest.java index ef8aa80b1..9e531ab30 100644 --- a/google-api-client/src/test/java/com/google/api/client/googleapis/GoogleUtilsTest.java +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/GoogleUtilsTest.java @@ -14,8 +14,9 @@ package com.google.api.client.googleapis; +import static org.junit.Assert.assertNotEquals; + import java.security.KeyStore; -import java.util.Enumeration; import java.util.regex.Matcher; import junit.framework.TestCase; @@ -26,16 +27,43 @@ */ public class GoogleUtilsTest extends TestCase { - public void testGetCertificateTrustStore() throws Exception { + public void testGetCertificateTrustStore_LoadsJdkDefaultFirst() throws Exception { + GoogleUtils.certTrustStore = null; + KeyStore trustStore = GoogleUtils.getCertificateTrustStore(); + + // Load bundled keystore to compare + KeyStore bundled = GoogleUtils.getBundledKeystore(); + + assertNotEquals( + "Certificate truststore should NOT contain the same amount of certificates as the bundled keystore", + bundled.size(), + trustStore.size()); + } + + public void testGetCertificateTrustStore_LoadsBundledKeystoreIfJdkDefaultLoadFails() + throws Exception { + GoogleUtils.certTrustStore = null; + String[] originalPaths = GoogleUtils.possibleJdkPaths; + GoogleUtils.possibleJdkPaths = new String[0]; + KeyStore trustStore = GoogleUtils.getCertificateTrustStore(); - Enumeration aliases = trustStore.aliases(); - while (aliases.hasMoreElements()) { - String alias = aliases.nextElement(); - assertTrue(trustStore.isCertificateEntry(alias)); - } - // intentionally check the count of certificates, so it can help us detect if a new certificate - // has been added or removed - assertEquals(71, trustStore.size()); + + // Load bundled keystore to compare + KeyStore bundled = GoogleUtils.getBundledKeystore(); + assertEquals( + "Certificate truststore should contain the same amount of certificates as the bundled keystore", + trustStore.size(), + bundled.size()); + + GoogleUtils.possibleJdkPaths = originalPaths; + } + + public void testGetCertificateTrustStore_IsCached() throws Exception { + KeyStore trustStore1 = GoogleUtils.getCertificateTrustStore(); + KeyStore trustStore2 = GoogleUtils.getCertificateTrustStore(); + + // Should return the exact same instance due to caching + assertSame("Trust store should be cached", trustStore1, trustStore2); } public void testVersionMatcher() { diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/apache/v2/ITGoogleApacheHttpTransportTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/apache/v2/ITGoogleApacheHttpTransportTest.java new file mode 100644 index 000000000..ef02117ad --- /dev/null +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/apache/v2/ITGoogleApacheHttpTransportTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Google LLC + * + * 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.googleapis.apache.v2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import com.google.api.client.http.apache.v2.ApacheHttpTransport; +import java.io.IOException; +import java.security.GeneralSecurityException; +import javax.net.ssl.SSLHandshakeException; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpGet; +import org.junit.Test; + +public class ITGoogleApacheHttpTransportTest { + + @Test + public void testHttpRequestFailsWhenMakingRequestToSiteWithoutDefaultJdkCerts() + throws GeneralSecurityException, IOException { + ApacheHttpTransport apacheHttpTransport = GoogleApacheHttpTransport.newTrustedTransport(); + // Use a self-signed certificate site that won't be trusted by default trust store + HttpGet httpGet = new HttpGet("https://self-signed.badssl.com/"); + Exception exception = null; + try { + apacheHttpTransport + .getHttpClient() + .execute( + httpGet, + new ResponseHandler() { + + @Override + public Object handleResponse(HttpResponse httpResponse) + throws ClientProtocolException, IOException { + fail("Should not have been able to complete SSL request with untrusted cert."); + return null; + } + }); + fail("Expected SSLHandshakeException was not thrown"); + } catch (SSLHandshakeException e) { + exception = e; + } + + assertNotNull(exception); + assertEquals(exception.getClass(), SSLHandshakeException.class); + } + + @Test + public void testHttpRequestPassesWhenMakingRequestToGoogleSite() throws Exception { + ApacheHttpTransport apacheHttpTransport = GoogleApacheHttpTransport.newTrustedTransport(); + HttpGet httpGet = new HttpGet("https://www.google.com/"); + + apacheHttpTransport + .getHttpClient() + .execute( + httpGet, + new ResponseHandler() { + @Override + public Object handleResponse(HttpResponse httpResponse) + throws ClientProtocolException, IOException { + assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + return null; + } + }); + } + + @Test + public void testHttpRequestPassesWhenMakingRequestToSiteContainedInDefaultCerts() + throws Exception { + + ApacheHttpTransport apacheHttpTransport = GoogleApacheHttpTransport.newTrustedTransport(); + HttpGet httpGet = new HttpGet("https://central.sonatype.com/"); + + apacheHttpTransport + .getHttpClient() + .execute( + httpGet, + new ResponseHandler() { + @Override + public Object handleResponse(HttpResponse httpResponse) + throws ClientProtocolException, IOException { + assertEquals(200, httpResponse.getStatusLine().getStatusCode()); + return null; + } + }); + } +} diff --git a/pom.xml b/pom.xml index a93b3bc7a..43349765d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.google.api-client google-api-client-parent - 2.8.2-SNAPSHOT + 2.9.0 pom Parent for the Google API Client Library for Java The Google APIs Client Library for Java is a Java client library diff --git a/versions.txt b/versions.txt index fd11cc93e..e06c40f0a 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-api-client:2.8.1:2.8.2-SNAPSHOT +google-api-client:2.9.0:2.9.0