1 /* 2 * Copyright 2021 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.util; 18 19 import com.google.common.io.BaseEncoding; 20 import io.grpc.ExperimentalApi; 21 import java.io.BufferedReader; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.InputStreamReader; 25 import java.io.UnsupportedEncodingException; 26 import java.security.KeyFactory; 27 import java.security.NoSuchAlgorithmException; 28 import java.security.PrivateKey; 29 import java.security.cert.Certificate; 30 import java.security.cert.CertificateException; 31 import java.security.cert.CertificateFactory; 32 import java.security.cert.X509Certificate; 33 import java.security.spec.InvalidKeySpecException; 34 import java.security.spec.PKCS8EncodedKeySpec; 35 import java.util.Collection; 36 37 /** 38 * Contains certificate/key PEM file utility method(s). 39 */ 40 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024") 41 public final class CertificateUtils { 42 /** 43 * Generates X509Certificate array from a PEM file. 44 * The PEM file should contain one or more items in Base64 encoding, each with 45 * plain-text headers and footers 46 * (e.g. -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----). 47 * 48 * @param inputStream is a {@link InputStream} from the certificate files 49 */ getX509Certificates(InputStream inputStream)50 public static X509Certificate[] getX509Certificates(InputStream inputStream) 51 throws CertificateException { 52 CertificateFactory factory = CertificateFactory.getInstance("X.509"); 53 Collection<? extends Certificate> certs = factory.generateCertificates(inputStream); 54 return certs.toArray(new X509Certificate[0]); 55 } 56 57 /** 58 * Generates a {@link PrivateKey} from a PEM file. 59 * The key should be PKCS #8 formatted. The key algorithm should be "RSA" or "EC". 60 * The PEM file should contain one item in Base64 encoding, with plain-text headers and footers 61 * (e.g. -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----). 62 * 63 * @param inputStream is a {@link InputStream} from the private key file 64 */ getPrivateKey(InputStream inputStream)65 public static PrivateKey getPrivateKey(InputStream inputStream) 66 throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException, 67 InvalidKeySpecException { 68 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); 69 String line; 70 while ((line = reader.readLine()) != null) { 71 if ("-----BEGIN PRIVATE KEY-----".equals(line)) { 72 break; 73 } 74 } 75 StringBuilder keyContent = new StringBuilder(); 76 while ((line = reader.readLine()) != null) { 77 if ("-----END PRIVATE KEY-----".equals(line)) { 78 break; 79 } 80 keyContent.append(line); 81 } 82 byte[] decodedKeyBytes = BaseEncoding.base64().decode(keyContent.toString()); 83 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKeyBytes); 84 try { 85 return KeyFactory.getInstance("RSA").generatePrivate(keySpec); 86 } catch (InvalidKeySpecException ignore) { 87 try { 88 return KeyFactory.getInstance("EC").generatePrivate(keySpec); 89 } catch (InvalidKeySpecException e) { 90 throw new InvalidKeySpecException("Neither RSA nor EC worked", e); 91 } 92 } 93 } 94 } 95 96