Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mtls support to GoogleNetHttpTransport and GoogleApacheHttpTransport #1619

Merged
merged 33 commits into from Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4205102
feat: add mtls support
arithmetic1728 Oct 23, 2020
fa08020
add run cert command
arithmetic1728 Oct 27, 2020
b2d8891
javanet
arithmetic1728 Oct 30, 2020
c7a0e71
Merge branch 'master' of https://github.com/googleapis/google-api-jav…
arithmetic1728 Oct 30, 2020
79e9ac1
add apache
arithmetic1728 Oct 30, 2020
f860389
update
arithmetic1728 Oct 30, 2020
43655ab
fix test
arithmetic1728 Oct 30, 2020
2849ee7
tmp
arithmetic1728 Oct 30, 2020
46d8973
fix
arithmetic1728 Oct 30, 2020
5e063b3
added tests
arithmetic1728 Oct 31, 2020
dceaacf
add apache
arithmetic1728 Oct 31, 2020
0f4f878
finished apache
arithmetic1728 Oct 31, 2020
9e33fac
doc string
arithmetic1728 Nov 1, 2020
547a6c5
Merge branch 'master' of https://github.com/googleapis/google-api-jav…
arithmetic1728 Nov 4, 2020
0442025
update code
arithmetic1728 Nov 4, 2020
d83c115
lint
arithmetic1728 Nov 4, 2020
d92d3b3
refactor: implement MtlsUtils.MtlsProvider interface with default imp…
chingor13 Nov 5, 2020
aa72510
Merge pull request #1 from chingor13/mtls-refactor
arithmetic1728 Nov 5, 2020
05e052c
refactor mtls to a module
arithmetic1728 Nov 5, 2020
312e73a
rename
arithmetic1728 Nov 5, 2020
5209bec
fix exceptions
arithmetic1728 Nov 5, 2020
1edf060
add timeout
arithmetic1728 Nov 6, 2020
daeb967
fix util test
arithmetic1728 Nov 6, 2020
a3cea68
Merge pull request #3 from arithmetic1728/refactor
arithmetic1728 Nov 6, 2020
377f944
add tests
arithmetic1728 Nov 6, 2020
533c76e
lint
arithmetic1728 Nov 6, 2020
9ad39a5
make constructor public
arithmetic1728 Nov 6, 2020
47f8c07
lint
arithmetic1728 Nov 6, 2020
7bf8d8c
make constructor public
arithmetic1728 Nov 6, 2020
55d4eb5
update
arithmetic1728 Nov 9, 2020
46dd6b8
use bigger try catch block
arithmetic1728 Nov 9, 2020
9eee919
update code
arithmetic1728 Nov 10, 2020
5593a90
Update google-api-client/src/main/java/com/google/api/client/googleap…
arithmetic1728 Nov 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions google-api-client/pom.xml
Expand Up @@ -154,5 +154,17 @@
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-apache-v2</artifactId>
</dependency>
<dependency>
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Expand Up @@ -15,7 +15,9 @@
package com.google.api.client.googleapis.apache.v2;

import com.google.api.client.googleapis.GoogleUtils;
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;
import java.io.IOException;
import java.net.ProxySelector;
Expand All @@ -24,7 +26,6 @@
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import org.apache.http.client.HttpClient;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
Expand All @@ -39,11 +40,33 @@
public final class GoogleApacheHttpTransport {

/**
* Returns a new instance of {@link ApacheHttpTransport} that uses
* {@link GoogleUtils#getCertificateTrustStore()} for the trusted certificates.
* 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 ApacheHttpTransport newTrustedTransport() throws GeneralSecurityException,
IOException {
public static ApacheHttpTransport newTrustedTransport()
throws GeneralSecurityException, IOException {
return newTrustedTransport(null, "");
}

/**
* {@link Beta} <br/>
* 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", the function uses
* the provided mtlsKeyStore or the default key store from {@link Utils#loadDefaultMtlsKeyStore()}
* to create the transport. If either key store exists, then the created transport is mutual TLS.
* The provided key store takes precedence over the default one.
*
* @param mtlsKeyStore KeyStore for mutual TLS client certificate and private key
* @param mtlsKeyStorePassword KeyStore password
*/
@Beta
public static ApacheHttpTransport newTrustedTransport(
KeyStore mtlsKeyStore, String mtlsKeyStorePassword)
throws GeneralSecurityException, IOException {
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(-1, TimeUnit.MILLISECONDS);

Expand All @@ -53,22 +76,44 @@ public static ApacheHttpTransport newTrustedTransport() throws GeneralSecurityEx
// Use the included trust store
KeyStore trustStore = GoogleUtils.getCertificateTrustStore();
SSLContext sslContext = SslUtils.getTlsSslContext();
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());

// Figure out if mTLS is needed and what key store to use.
Boolean useMtls = Utils.useMtlsClientCertificate();
KeyStore mtlsKeyStoreToUse = mtlsKeyStore;
String mtlsKeyStorePasswordToUse = mtlsKeyStorePassword;
if (useMtls && mtlsKeyStoreToUse == null) {
// Use the default mTLS key store if not provided.
mtlsKeyStoreToUse = Utils.loadDefaultMtlsKeyStore();
mtlsKeyStorePasswordToUse = "";
}
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved

Boolean isMtls = useMtls && mtlsKeyStoreToUse != null && mtlsKeyStoreToUse.size() > 0;
if (isMtls) {
SslUtils.initSslContext(
sslContext,
trustStore,
SslUtils.getPkixTrustManagerFactory(),
mtlsKeyStoreToUse,
mtlsKeyStorePasswordToUse,
SslUtils.getDefaultKeyManagerFactory());
} else {
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());
}
LayeredConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);

HttpClient client = HttpClientBuilder.create()
.useSystemProperties()
.setSSLSocketFactory(socketFactory)
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault()))
.setConnectionManager(connectionManager)
.disableRedirectHandling()
.disableAutomaticRetries()
.build();
return new ApacheHttpTransport(client);
HttpClient client =
HttpClientBuilder.create()
.useSystemProperties()
.setSSLSocketFactory(socketFactory)
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault()))
.setConnectionManager(connectionManager)
.disableRedirectHandling()
.disableAutomaticRetries()
.build();
return new ApacheHttpTransport(client, isMtls);
}

private GoogleApacheHttpTransport() {
}
private GoogleApacheHttpTransport() {}
}
Expand Up @@ -15,7 +15,9 @@
package com.google.api.client.googleapis.javanet;

import com.google.api.client.googleapis.GoogleUtils;
import com.google.api.client.googleapis.util.Utils;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.util.Beta;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
Expand All @@ -31,8 +33,11 @@ public class GoogleNetHttpTransport {
/**
* Returns a new instance of {@link NetHttpTransport} that uses
* {@link GoogleUtils#getCertificateTrustStore()} for the trusted certificates using
* {@link com.google.api.client.http.javanet.NetHttpTransport.Builder#trustCertificates(KeyStore)}
* .
* {@link com.google.api.client.http.javanet.NetHttpTransport.Builder#trustCertificates(KeyStore)}.
* 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.
*
* <p>
* This helper method doesn't provide for customization of the {@link NetHttpTransport}, such as
Expand All @@ -51,10 +56,44 @@ static HttpTransport newProxyTransport() throws GeneralSecurityException, IOExce
*/
public static NetHttpTransport newTrustedTransport()
throws GeneralSecurityException, IOException {
return new NetHttpTransport.Builder().trustCertificates(GoogleUtils.getCertificateTrustStore())
.build();
return newTrustedTransport(null, "");
}

private GoogleNetHttpTransport() {
/**
* {@link Beta} <br>
* Returns a new instance of {@link NetHttpTransport} that uses
* {@link GoogleUtils#getCertificateTrustStore()} for the trusted certificates using
* {@link com.google.api.client.http.javanet.NetHttpTransport.Builder#trustCertificates(KeyStore)}.
* If `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true",
* the function uses the provided mtlsKeyStore or the default key store from
* {@link Utils#loadDefaultMtlsKeyStore()} to create the transport. If either key
* store exists, then the created transport is mutual TLS. The provided key store
* takes precedence over the default one.
*
* @param mtlsKeyStore KeyStore for mutual TLS client certificate and private key
* @param mtlsKeyStorePassword KeyStore password
* @since 1.31
*/
@Beta
public static NetHttpTransport newTrustedTransport(KeyStore mtlsKeyStore, String mtlsKeyStorePassword)
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
throws GeneralSecurityException, IOException {
// Figure out if mTLS is needed and what key store to use.
Boolean useMtls = Utils.useMtlsClientCertificate();
KeyStore mtlsKeyStoreToUse = mtlsKeyStore;
String mtlsKeyStorePasswordToUse = mtlsKeyStorePassword;
if (useMtls && mtlsKeyStoreToUse == null) {
// Use the default mTLS key store if not provided.
mtlsKeyStoreToUse = Utils.loadDefaultMtlsKeyStore();
mtlsKeyStorePasswordToUse = "";
}
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved

if (useMtls && mtlsKeyStoreToUse != null && mtlsKeyStoreToUse.size() > 0) {
return new NetHttpTransport.Builder()
.trustCertificates(GoogleUtils.getCertificateTrustStore(), mtlsKeyStoreToUse, mtlsKeyStorePasswordToUse)
.build();
}
return new NetHttpTransport.Builder().trustCertificates(GoogleUtils.getCertificateTrustStore()).build();
}

private GoogleNetHttpTransport() {}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2020 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.googleapis.util;

import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Beta;
import com.google.api.client.util.Key;
import java.util.List;

/**
* {@link Beta} <br/>
* Data class representing context_aware_metadata.json file.
*
* @since 1.31
*/
@Beta
public class ContextAwareMetadataJson extends GenericJson {
/** Cert provider command */
@Key("cert_provider_command")
private List<String> commands;

/**
* Returns the cert provider command.
*
* @since 1.31
*/
public final List<String> getCommands() {
return commands;
}
}
Expand Up @@ -17,8 +17,18 @@
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Beta;
import com.google.api.client.util.SecurityUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessControlException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.List;

/**
* {@link Beta} <br/>
Expand All @@ -28,10 +38,14 @@
*/
@Beta
public final class Utils {
private static final String CONTEXT_AWARE_METADATA_PATH =
System.getProperty("user.home") + "/.secureConnect/context_aware_metadata.json";

/**
* Returns a cached default implementation of the JsonFactory interface.
*/
/** GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable */
public static final String GOOGLE_API_USE_CLIENT_CERTIFICATE =
"GOOGLE_API_USE_CLIENT_CERTIFICATE";

/** Returns a cached default implementation of the JsonFactory interface. */
public static JsonFactory getDefaultJsonFactory() {
return JsonFactoryInstanceHolder.INSTANCE;
}
Expand All @@ -44,9 +58,7 @@ private static class JsonFactoryInstanceHolder {
static final JsonFactory INSTANCE = new JacksonFactory();
}

/**
* Returns a cached default implementation of the HttpTransport interface.
*/
/** Returns a cached default implementation of the HttpTransport interface. */
public static HttpTransport getDefaultTransport() {
return TransportInstanceHolder.INSTANCE;
}
Expand All @@ -55,6 +67,68 @@ private static class TransportInstanceHolder {
static final HttpTransport INSTANCE = new NetHttpTransport();
}

private Utils() {
/**
* Returns the `cert_provider_command` field in context_aware_metadata.json file.
*
* @param contextAwareMetadata Input stream for ~/.secureConnect/context_aware_metadata.json file.
* @return `cert_provider_command` field
* @since 1.31
*/
static List<String> extractCertificateProviderCommand(InputStream contextAwareMetadata)
throws IOException {
JsonParser parser = JsonFactoryInstanceHolder.INSTANCE.createJsonParser(contextAwareMetadata);
ContextAwareMetadataJson json = parser.parse(ContextAwareMetadataJson.class);
return json.getCommands();
}

/**
* Returns if mTLS client certificate should be used. mTLS client certificate is used if and only
* if "GOOGLE_API_USE_CLIENT_CERTIFICATE" environment variable value is "true".
*
* @return If mTLS client certificate should be used
* @since 1.31
*/
public static Boolean useMtlsClientCertificate() {
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
String useClientCertificate = System.getenv(GOOGLE_API_USE_CLIENT_CERTIFICATE);
return "true".equals(useClientCertificate);
}

/**
* Returns the default KeyStore for mutual TLS.
*
* <p>Default client certificate can be generated by running the commands in
* ~/.secureConnect/context_aware_metadata.json file. If this json exists, and the default client
* certificate exists, this function returns a KeyStore object created with the default client
* certificate and the key store password is "". Otherwise, null will be returned.
*
* @return KeyStore for mutual TLS.
* @since 1.31
*/
public static KeyStore loadDefaultMtlsKeyStore() throws IOException, GeneralSecurityException {
File file = new File(CONTEXT_AWARE_METADATA_PATH);
if (!file.exists()) {
return null;
}

// Load the cert provider command from the json file.
InputStream stream = new FileInputStream(file);
List<String> command = extractCertificateProviderCommand(stream);

// Call the command.
Process process = new ProcessBuilder(command).start();
int exitCode = 0;
try {
exitCode = process.waitFor();
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
} catch (InterruptedException exception) {
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
throw new AccessControlException(exception.toString());
}
if (exitCode != 0) {
throw new AccessControlException("Failed to execute cert provider command");
}

InputStream certificateToUse = process.getInputStream();
return SecurityUtils.createMtlsKeyStore(certificateToUse);
}

private Utils() {}
}