mirror of https://github.com/grpc/grpc-java.git
advancedtls: Add a Utility Class For Loading Certs/Keys (#8023)
This commit is contained in:
parent
8a9aa41416
commit
1703d692bc
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue