diff --git a/docs/component-modules.md b/docs/component-modules.md index 7277febc0..ad780053b 100644 --- a/docs/component-modules.md +++ b/docs/component-modules.md @@ -55,4 +55,13 @@ XML extensions to the Google API Client Library for Java (`google-api-client-xml`). This module depends on `google-api-client` and `google-http-client-xml`. +## google-api-client-apache-v5 + +Provides Apache extension to the Google HTTP Client Library for Java (`google-api-client-apache-v5`) that +returns an implementation of `HttpTransport` based on the Apache HTTP Client (v5) with Google certificates loaded in +truststore. This module depends on `google-http-client`. + +Please note this is the preferred Apache extension to be used, over the GoogleApacheHttpTransport included +in `google-api-client` module. The previous google transport utilizes the EOL Apache HTTP Client v4. + [protobuf]: https://developers.google.com/protocol-buffers/docs/overview diff --git a/google-api-client-apache-v5/pom.xml b/google-api-client-apache-v5/pom.xml new file mode 100644 index 000000000..8c5350d79 --- /dev/null +++ b/google-api-client-apache-v5/pom.xml @@ -0,0 +1,177 @@ + + + 4.0.0 + + com.google.api-client + google-api-client-parent + 2.6.1-SNAPSHOT + + google-api-client-apache-v5 + Apache extensions to the Google APIs Client Library for Java + + + + org.apache.maven.plugins + maven-resources-plugin + + + + resources + + + + + + maven-javadoc-plugin + + + https://docs.oracle.com/javase/8/docs/api/ + https://cloud.google.com/appengine/docs/standard/java/javadoc/ + https://googleapis.dev/java/google-http-client/${project.http.version}/ + https://googleapis.dev/java/google-oauth-client/${project.oauth.version}/ + + ${project.name} ${project.version} + ${project.artifactId} ${project.version} + + + + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + true + + + google.api.client + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + org.apache.felix + maven-bundle-plugin + 5.1.9 + + + bundle-manifest + process-classes + + manifest + + + + + + https://developers.google.com/api-client-library/java/ + Google HTTP transport wrapper for the Apache 5 Http Client. + + com.google.api.client.googleapis.apache + + + + + maven-source-plugin + + + source-jar + + jar + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + validate-google-style + validate + + check + + + google_checks.xml + src/checkstyle/checkstyle-suppressions.xml + true + true + warning + + + + + + + + + src/main/resources + + + src/main/properties + true + + + + + + junit + junit + test + + + com.google.api-client + google-api-client + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + + + com.google.http-client + google-http-client-apache-v5 + + + com.google.guava + guava + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.apache.httpcomponents.core5 + httpcore5 + + + com.google.http-client + google-http-client + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + + + com.google.api-client + google-api-client + test-jar + test + ${project.version} + + + diff --git a/google-api-client-apache-v5/src/checkstyle/checkstyle-suppressions.xml b/google-api-client-apache-v5/src/checkstyle/checkstyle-suppressions.xml new file mode 100644 index 000000000..812319b66 --- /dev/null +++ b/google-api-client-apache-v5/src/checkstyle/checkstyle-suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + 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 new file mode 100644 index 000000000..00a7673b3 --- /dev/null +++ b/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransport.java @@ -0,0 +1,148 @@ +/* + * Copyright 2024 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.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; +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.net.ProxySelector; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; +import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; + +/** + * Utilities for Google APIs based on {@link Apache5HttpTransport}. + * + * @since 2.6.1 + */ +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. + */ + public static Apache5HttpTransport newTrustedTransport() + throws GeneralSecurityException, IOException { + return newTrustedTransport(MtlsUtils.getDefaultMtlsProvider()); + } + + /** + * {@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. + * + * @param mtlsProvider MtlsProvider to configure mutual TLS for the transport + */ + @Beta + public static Apache5HttpTransport newTrustedTransport(MtlsProvider mtlsProvider) + throws GeneralSecurityException, IOException { + + SocketFactoryRegistryHandler handler = new SocketFactoryRegistryHandler(mtlsProvider); + + PoolingHttpClientConnectionManager connectionManager = + new PoolingHttpClientConnectionManager(handler.getSocketFactoryRegistry()); + connectionManager.setMaxTotal(200); + connectionManager.setDefaultMaxPerRoute(20); + connectionManager.setDefaultConnectionConfig( + ConnectionConfig.custom() + .setTimeToLive(-1, TimeUnit.MILLISECONDS) + .setValidateAfterInactivity(-1L, TimeUnit.MILLISECONDS) + .build()); + + CloseableHttpClient client = + HttpClients.custom() + .useSystemProperties() + .setConnectionManager(connectionManager) + .setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault())) + .disableRedirectHandling() + .disableAutomaticRetries() + .build(); + + return new Apache5HttpTransport(client, handler.isMtls()); + } + + @VisibleForTesting + static class SocketFactoryRegistryHandler { + private final Registry socketFactoryRegistry; + private final boolean isMtls; + + public SocketFactoryRegistryHandler(MtlsProvider mtlsProvider) + throws GeneralSecurityException, IOException { + KeyStore mtlsKeyStore = null; + String mtlsKeyStorePassword = null; + if (mtlsProvider.useMtlsClientCertificate()) { + mtlsKeyStore = mtlsProvider.getKeyStore(); + 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, + SslUtils.getPkixTrustManagerFactory(), + mtlsKeyStore, + mtlsKeyStorePassword, + SslUtils.getDefaultKeyManagerFactory()); + } else { + this.isMtls = false; + SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory()); + } + LayeredConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext); + + this.socketFactoryRegistry = + RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", socketFactory) + .build(); + } + + public Registry getSocketFactoryRegistry() { + return this.socketFactoryRegistry; + } + + public boolean isMtls() { + return this.isMtls; + } + } + + private GoogleApache5HttpTransport() {} +} diff --git a/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/package-info.java b/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/package-info.java new file mode 100644 index 000000000..f4a1e9131 --- /dev/null +++ b/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2024 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. + */ + +/** + * Google APIs support based on the Apache HTTP Client v5. + * + * @since 2.6.1 + */ +package com.google.api.client.googleapis.apache.v5; diff --git a/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransportTest.java b/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransportTest.java new file mode 100644 index 000000000..07728325a --- /dev/null +++ b/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransportTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 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.v5; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.googleapis.mtls.MtlsProvider; +import com.google.api.client.googleapis.mtls.MtlsTransportBaseTest; +import com.google.api.client.http.HttpTransport; +import java.io.IOException; +import java.security.GeneralSecurityException; +import org.junit.Test; + +public class GoogleApache5HttpTransportTest extends MtlsTransportBaseTest { + @Override + protected HttpTransport buildTrustedTransport(MtlsProvider mtlsProvider) + throws GeneralSecurityException, IOException { + return GoogleApache5HttpTransport.newTrustedTransport(mtlsProvider); + } + + @Test + public void socketFactoryRegistryHandlerTest() throws GeneralSecurityException, IOException { + MtlsProvider mtlsProvider = new TestMtlsProvider(true, createTestMtlsKeyStore(), "", false); + GoogleApache5HttpTransport.SocketFactoryRegistryHandler handler = + new GoogleApache5HttpTransport.SocketFactoryRegistryHandler(mtlsProvider); + assertNotNull(handler.getSocketFactoryRegistry().lookup("http")); + assertNotNull(handler.getSocketFactoryRegistry().lookup("https")); + assertTrue(handler.isMtls()); + } +} 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 new file mode 100644 index 000000000..f8e9cbed1 --- /dev/null +++ b/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/ITGoogleApache5HttpTransportTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 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.v5; + +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.v5.Apache5HttpTransport; +import java.io.IOException; +import java.security.GeneralSecurityException; +import javax.net.ssl.SSLHandshakeException; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.junit.Test; + +public class ITGoogleApache5HttpTransportTest { + + @Test + public void testHttpRequestFailsWhenMakingRequestToSiteWithoutGoogleCerts() + throws GeneralSecurityException, IOException { + Apache5HttpTransport apache5HttpTransport = GoogleApache5HttpTransport.newTrustedTransport(); + HttpGet httpGet = new HttpGet("https://maven.com/"); + Exception exception = null; + try { + apache5HttpTransport + .getHttpClient() + .execute( + httpGet, + new HttpClientResponseHandler() { + @Override + public Void handleResponse(ClassicHttpResponse response) { + fail("Should not have been able to complete SSL request on non google site."); + 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 { + Apache5HttpTransport apache5HttpTransport = GoogleApache5HttpTransport.newTrustedTransport(); + HttpGet httpGet = new HttpGet("https://www.google.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/pom.xml b/google-api-client/pom.xml index 8d73dbad4..d42452c6d 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -87,6 +87,18 @@ jar + + Jar Tests Package + package + + test-jar + + + + **/Mtls/** + + + diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/mtls/MtlsProvider.java b/google-api-client/src/main/java/com/google/api/client/googleapis/mtls/MtlsProvider.java index cb0813b45..abec44b7a 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/mtls/MtlsProvider.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/mtls/MtlsProvider.java @@ -22,7 +22,8 @@ /** * {@link Beta}
* Provider interface for mutual TLS. It is used in {@link - * GoogleApacheHttpTransport#newTrustedTransport(MtlsProvider)} and {@link + * GoogleApacheHttpTransport#newTrustedTransport(MtlsProvider)}, {@link + * GoogleApache5HttpTransport#newTrustedTransport(MtlsProvider)} and {@link * GoogleNetHttpTransport#newTrustedTransport(MtlsProvider)} to configure the mutual TLS in the * transport. * diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/mtls/MtlsTransportBaseTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/mtls/MtlsTransportBaseTest.java index 096ff4abf..87b44fdf4 100644 --- a/google-api-client/src/test/java/com/google/api/client/googleapis/mtls/MtlsTransportBaseTest.java +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/mtls/MtlsTransportBaseTest.java @@ -35,11 +35,11 @@ protected KeyStore createTestMtlsKeyStore() throws IOException, GeneralSecurityE return SecurityUtils.createMtlsKeyStore(certAndKey); } - protected static class TestMtlsProvider implements MtlsProvider { - private boolean useClientCertificate; - private KeyStore keyStore; - private String keyStorePassword; - private boolean throwExceptionForGetKeyStore; + public static class TestMtlsProvider implements MtlsProvider { + private final boolean useClientCertificate; + private final KeyStore keyStore; + private final String keyStorePassword; + private final boolean throwExceptionForGetKeyStore; public TestMtlsProvider( boolean useClientCertificate, diff --git a/pom.xml b/pom.xml index 1d332f0f5..5fa6a62d4 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,7 @@ google-api-client-jackson2 google-api-client-protobuf google-api-client-xml + google-api-client-apache-v5 @@ -241,6 +242,21 @@ ${gson.version} test + + org.apache.httpcomponents.client5 + httpclient5 + ${project.httpclient5.version} + + + org.apache.httpcomponents.core5 + httpcore5 + ${project.httpcore5.version} + + + com.google.http-client + google-http-client-apache-v5 + ${project.http.version} + @@ -513,9 +529,11 @@ UTF-8 - 1.44.2 + 1.45.0 4.4.16 4.5.14 + 5.2.4 + 5.3.1 1.17.0 1.36.0 1.23.0