Skip to content

feat: add mtls support for NetHttpTransport #1147

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

Merged
merged 17 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
create keystore from cert and key string
  • Loading branch information
arithmetic1728 committed Oct 23, 2020
commit e21791c6ae750d70fc89710304e429de2b141cb1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
Expand All @@ -31,6 +32,7 @@
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
import javax.net.ssl.X509TrustManager;

Expand Down Expand Up @@ -258,5 +260,62 @@ public static void loadKeyStoreFromCertificates(
}
}

/**
* Create a keystore for mutual TLS with the certificate and private key provided.
*
* <p>certAndKey should have the following format:
*
* <pre>
* -----BEGIN CERTIFICATE-----
* ......
* -----END CERTIFICATE------
* ----BEGIN PRIVATE KEY-----
* ......
* -----END PRIVATE KEY-----
* </pre>
*
* @param certAndKey Concatenation of a x509 certificate PEM string and a PKCS#8 unencrypted
* private key PEM string.
* @return keystore for mutual TLS.
*/
public static KeyStore createMtlsKeyStore(String certAndKey)
throws GeneralSecurityException, IOException {
KeyStore keystore = KeyStore.getInstance("JKS");
try {
keystore.load(null);
} catch (IOException ignored) {
// shouldn't throw any exception to load a null keystore.
}

byte[] certAndKeyBytes = certAndKey.getBytes();

// Read the certificate.
InputStreamReader reader = new InputStreamReader(new ByteArrayInputStream(certAndKeyBytes));
PemReader.Section section = PemReader.readFirstSectionAndClose(reader, "CERTIFICATE");
if (section == null) {
throw new IllegalArgumentException("certificate is missing from certAndKey string");
}
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert =
(X509Certificate)
certFactory.generateCertificate(
new ByteArrayInputStream(section.getBase64DecodedBytes()));

// Read the private key.
reader = new InputStreamReader(new ByteArrayInputStream(certAndKeyBytes));
section = PemReader.readFirstSectionAndClose(reader, "PRIVATE KEY");
if (section == null) {
throw new IllegalArgumentException("private key is missing from certAndKey string");
}
PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(section.getBase64DecodedBytes());
PrivateKey key =
KeyFactory.getInstance(cert.getPublicKey().getAlgorithm()).generatePrivate(keySpecPKCS8);

// Fit the certificate and private key into the keystore.
keystore.setKeyEntry("alias", key, new char[] {}, new X509Certificate[] {cert});

return keystore;
}

private SecurityUtils() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,16 @@

package com.google.api.client.util;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.api.client.testing.json.webtoken.TestCertificates;
import com.google.api.client.testing.util.SecurityTestUtils;
import com.google.common.io.Resources;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
Expand All @@ -25,6 +32,7 @@
import javax.net.ssl.X509TrustManager;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.function.ThrowingRunnable;

/**
* Tests {@link SecurityUtils}.
Expand Down Expand Up @@ -160,4 +168,51 @@ public void testVerifyX509() throws Exception {
public void testVerifyX509WrongCa() throws Exception {
assertNull(verifyX509(TestCertificates.BOGUS_CA_CERT));
}

public void testCreateMtlsKeyStoreNoCert() throws Exception {
URL url = getClass().getClassLoader().getResource("com/google/api/client/util/privateKey.pem");
final String certMissing = Resources.toString(url, StandardCharsets.UTF_8);
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
new ThrowingRunnable() {
@Override
public void run() throws Throwable {
SecurityUtils.createMtlsKeyStore(certMissing);
}
});
assertThat(exception)
.hasMessageThat()
.isEqualTo("certificate is missing from certAndKey string");
}

public void testCreateMtlsKeyStoreNoPrivateKey() throws Exception {
URL url = getClass().getClassLoader().getResource("com/google/api/client/util/cert.pem");
final String privateKeyMissing = Resources.toString(url, StandardCharsets.UTF_8);
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
new ThrowingRunnable() {
@Override
public void run() throws Throwable {
SecurityUtils.createMtlsKeyStore(privateKeyMissing);
}
});
assertThat(exception)
.hasMessageThat()
.isEqualTo("private key is missing from certAndKey string");
}

public void testCreateMtlsKeyStoreSuccess() throws Exception {
URL url = getClass().getClassLoader().getResource("com/google/api/client/util/cert.pem");
String cert = Resources.toString(url, StandardCharsets.UTF_8);

url = getClass().getClassLoader().getResource("com/google/api/client/util/privateKey.pem");
String privateKey = Resources.toString(url, StandardCharsets.UTF_8);

String certAndKey = cert + "\n" + privateKey;
KeyStore mtlsKeyStore = SecurityUtils.createMtlsKeyStore(certAndKey);

assertEquals(mtlsKeyStore.size(), 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE
AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x
MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3
MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB
BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ
GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ
Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB
AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM
MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4
fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4
uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1
kWwa9n19NFiV0z3m6isj
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/
XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ
oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu
uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy
sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK
/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY
lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1
fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq
RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB
Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno
fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L
XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw
ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE
VUV6b25kqrcu
-----END PRIVATE KEY-----