advancedtls: Add a Utility Class For Loading Certs/Keys (#8023)

This commit is contained in:
ZhenLian 2021-04-21 10:07:44 -07:00 committed by GitHub
parent 8a9aa41416
commit 1703d692bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 192 additions and 0 deletions

View File

@ -0,0 +1,88 @@
/*
* Copyright 2021 The gRPC Authors
*
* 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 io.grpc.util;
import com.google.common.io.BaseEncoding;
import io.grpc.ExperimentalApi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collection;
/**
* Contains certificate/key PEM file utility method(s).
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024")
public final class CertificateUtils {
/**
* Generates X509Certificate array from a PEM file.
* The PEM file should contain one or more items in Base64 encoding, each with
* plain-text headers and footers
* (e.g. -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----).
*
* @param inputStream is a {@link InputStream} from the certificate files
*/
public static X509Certificate[] getX509Certificates(InputStream inputStream)
throws CertificateException {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certs = factory.generateCertificates(inputStream);
return certs.toArray(new X509Certificate[0]);
}
/**
* Generates a {@link PrivateKey} from a PEM file.
* The key should be PKCS #8 formatted.
* The PEM file should contain one item in Base64 encoding, with plain-text headers and footers
* (e.g. -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----).
*
* @param inputStream is a {@link InputStream} from the private key file
*/
public static PrivateKey getPrivateKey(InputStream inputStream)
throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException,
InvalidKeySpecException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
if ("-----BEGIN PRIVATE KEY-----".equals(line)) {
break;
}
}
StringBuilder keyContent = new StringBuilder();
while ((line = reader.readLine()) != null) {
if ("-----END PRIVATE KEY-----".equals(line)) {
break;
}
keyContent.append(line);
}
byte[] decodedKeyBytes = BaseEncoding.base64().decode(keyContent.toString());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKeyBytes);
return keyFactory.generatePrivate(keySpec);
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2021 The gRPC Authors
*
* 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 io.grpc.util;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Charsets;
import io.grpc.internal.testing.TestUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link CertificateUtilsTest}. */
@RunWith(JUnit4.class)
public class CertificateUtilsTest {
public static final String SERVER_0_PEM_FILE = "server0.pem";
public static final String SERVER_0_KEY_FILE = "server0.key";
public static final String CA_PEM_FILE = "ca.pem";
public static final String BAD_PEM_FORMAT = "This is a bad key pem format";
public static final String BAD_PEM_CONTENT = "----BEGIN PRIVATE KEY-----\n"
+ "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDvdzKDTYvRgjBO\n"
+ "-----END PRIVATE KEY-----";
@Test
public void readPemCertFile() throws CertificateException, IOException {
InputStream in = TestUtils.class.getResourceAsStream("/certs/" + SERVER_0_PEM_FILE);
X509Certificate[] cert = CertificateUtils.getX509Certificates(in);
assertThat(cert.length).isEqualTo(1);
// Checks some information on the test certificate.
assertThat(cert[0].getSerialNumber()).isEqualTo(new BigInteger(
"6c97d344427a93affea089d6855d4ed63dd94f38", 16));
assertThat(cert[0].getSubjectDN().getName()).isEqualTo(
"CN=*.test.google.com.au, O=Internet Widgits Pty Ltd, ST=Some-State, C=AU");
}
@Test
public void readPemKeyFile() throws Exception {
InputStream in = TestUtils.class.getResourceAsStream("/certs/" + SERVER_0_KEY_FILE);
PrivateKey key = CertificateUtils.getPrivateKey(in);
// Checks some information on the test key.
assertThat(key.getAlgorithm()).isEqualTo("RSA");
assertThat(key.getFormat()).isEqualTo("PKCS#8");
}
@Test
public void readCaPemFile() throws CertificateException, IOException {
InputStream in = TestUtils.class.getResourceAsStream("/certs/" + CA_PEM_FILE);
X509Certificate[] cert = CertificateUtils.getX509Certificates(in);
assertThat(cert.length).isEqualTo(1);
// Checks some information on the test certificate.
assertThat(cert[0].getSerialNumber()).isEqualTo(new BigInteger(
"5ab3f456f1dccbe2cfe94b9836d88bf600610f9a", 16));
assertThat(cert[0].getSubjectDN().getName()).isEqualTo(
"CN=testca, O=Internet Widgits Pty Ltd, ST=Some-State, C=AU");
}
@Test
public void readBadFormatKeyFile() throws Exception {
InputStream in = new ByteArrayInputStream(BAD_PEM_FORMAT.getBytes(Charsets.UTF_8));
try {
CertificateUtils.getPrivateKey(in);
Assert.fail("no exception thrown");
} catch (InvalidKeySpecException expected) {
// The error messages for OpenJDK 11 and 8 are different, and for Windows it will generate a
// different exception, so we only check if a general exception is thrown.
}
}
@Test
public void readBadContentKeyFile() {
InputStream in = new ByteArrayInputStream(BAD_PEM_CONTENT.getBytes(Charsets.UTF_8));
try {
CertificateUtils.getPrivateKey(in);
Assert.fail("no exception thrown");
} catch (Exception expected) {
// The error messages for OpenJDK 11 and 8 are different, and for Windows it will generate a
// different exception, so we only check if a general exception is thrown.
}
}
}