From ce808d7a45768aa8f237a5a174e3f8511d261826 Mon Sep 17 00:00:00 2001 From: detmerl Date: Wed, 14 Aug 2024 09:06:34 -0400 Subject: [PATCH] provide HttpTransport support for google certificates with apache HTTP client v5 --- google-api-client-apache-v5/pom.xml | 201 ++++++++++++++++++ .../apache/v5/GoogleApache5HttpTransport.java | 149 +++++++++++++ .../googleapis/apache/v5/package-info.java | 20 ++ .../v5/GoogleApache5HttpTransportTest.java | 43 ++++ .../v5/ITGoogleApache5HttpTransportTest.java | 50 +++++ google-api-client/pom.xml | 12 ++ .../mtls/MtlsTransportBaseTest.java | 10 +- pom.xml | 1 + 8 files changed, 481 insertions(+), 5 deletions(-) create mode 100644 google-api-client-apache-v5/pom.xml create mode 100644 google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransport.java create mode 100644 google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/package-info.java create mode 100644 google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransportTest.java create mode 100644 google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/ITGoogleApache5HttpTransportTest.java diff --git a/google-api-client-apache-v5/pom.xml b/google-api-client-apache-v5/pom.xml new file mode 100644 index 000000000..c92bf1feb --- /dev/null +++ b/google-api-client-apache-v5/pom.xml @@ -0,0 +1,201 @@ + + + 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 + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + resources + + + + + + maven-javadoc-plugin + + + https://docs.oracle.com/javase/7/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 Http Client. + com.google.api.client.googleapis.apache + + + + + maven-source-plugin + + + source-jar + + jar + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + commons-codec:commons-codec + + + + org.apache.maven.plugins + maven-surefire-plugin + + + !AbstractGoogleClientTest#testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar+testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar + + + + + + + src/main/resources + + + src/main/properties + true + + + + + + junit + junit + test + + + + commons-codec + commons-codec + + + + com.google.api-client + google-api-client + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + + + com.google.http-client + google-http-client-apache-v5 + 1.44.3-SNAPSHOT + + + org.apache.httpcomponents.client5 + httpclient5 + 5.3.1 + + + org.apache.httpcomponents.core5 + httpcore5-h2 + 5.2.4 + + + com.google.http-client + google-http-client + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + + + junit + junit + test + + + org.mockito + mockito-core + test + + + com.google.api-client + google-api-client + test-jar + test + 2.6.1-SNAPSHOT + + + + + + 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..b18fd4a8e --- /dev/null +++ b/google-api-client-apache-v5/src/main/java/com/google/api/client/googleapis/apache/v5/GoogleApache5HttpTransport.java @@ -0,0 +1,149 @@ +/* + * Copyright 2020 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.common.annotations.Beta; +import com.google.api.client.util.SslUtils; +import com.google.common.annotations.VisibleForTesting; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.impl.classic.HttpClients; +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.client5.http.impl.classic.CloseableHttpClient; +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.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.ProxySelector; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.concurrent.TimeUnit; + +/** + * Utilities for Google APIs based on {@link ApacheHttpTransport}. + * + * @since 1.31 + */ +public final class GoogleApache5HttpTransport { + + /** + * 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. + */ + public static Apache5HttpTransport newTrustedTransport() + throws GeneralSecurityException, IOException { + return newTrustedTransport(MtlsUtils.getDefaultMtlsProvider()); + } + + /** + * {@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. + * + * @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..25faf0191 --- /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 2020 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 1.31 + */ +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..a7d59c51e --- /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 2020 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.mtls.MtlsProvider; +import com.google.api.client.googleapis.mtls.MtlsTransportBaseTest; +import com.google.api.client.http.HttpTransport; +import org.junit.Test; + + +import java.io.IOException; +import java.security.GeneralSecurityException; + +import static org.junit.Assert.*; + +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..e45c55e47 --- /dev/null +++ b/google-api-client-apache-v5/src/test/java/com/google/api/client/googleapis/apache/v5/ITGoogleApache5HttpTransportTest.java @@ -0,0 +1,50 @@ +package com.google.api.client.googleapis.apache.v5; + +import com.google.api.client.http.apache.v5.Apache5HttpTransport; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.junit.Test; +import javax.net.ssl.SSLHandshakeException; +import java.io.IOException; +import java.security.GeneralSecurityException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +public class ITGoogleApache5HttpTransportTest { + + @Test + public void testHttpRequestFailsWhenMakingRequestToNonGoogleSite() + throws GeneralSecurityException, IOException { + Apache5HttpTransport apache5HttpTransport = GoogleApache5HttpTransport.newTrustedTransport(); + HttpGet httpGet = new HttpGet("https://www.bing.com/"); + + assertThrows( + SSLHandshakeException.class, + () -> + apache5HttpTransport + .getHttpClient() + .execute( + httpGet, + response -> { + fail( + "Should not have been able to complete SSL request to site without google certificates."); + return null; + })); + } + + @Test + public void testHttpRequestPassesWhenMakingRequestToGoogleSite() throws Exception { + Apache5HttpTransport apache5HttpTransport = GoogleApache5HttpTransport.newTrustedTransport(); + HttpGet httpGet = new HttpGet("https://www.google.com/"); + + apache5HttpTransport + .getHttpClient() + .execute( + httpGet, + 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/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..86b1a77a2 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..a7d286989 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