001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.io.crypto.tls; 019 020import static java.nio.charset.StandardCharsets.US_ASCII; 021import static java.util.Base64.getMimeDecoder; 022import static java.util.regex.Pattern.CASE_INSENSITIVE; 023import static javax.crypto.Cipher.DECRYPT_MODE; 024 025import java.io.ByteArrayInputStream; 026import java.io.File; 027import java.io.IOException; 028import java.nio.file.Files; 029import java.security.GeneralSecurityException; 030import java.security.KeyFactory; 031import java.security.KeyStore; 032import java.security.KeyStoreException; 033import java.security.PrivateKey; 034import java.security.PublicKey; 035import java.security.cert.Certificate; 036import java.security.cert.CertificateException; 037import java.security.cert.CertificateFactory; 038import java.security.cert.X509Certificate; 039import java.security.spec.InvalidKeySpecException; 040import java.security.spec.PKCS8EncodedKeySpec; 041import java.security.spec.X509EncodedKeySpec; 042import java.util.ArrayList; 043import java.util.List; 044import java.util.regex.Matcher; 045import java.util.regex.Pattern; 046import javax.crypto.Cipher; 047import javax.crypto.EncryptedPrivateKeyInfo; 048import javax.crypto.SecretKey; 049import javax.crypto.SecretKeyFactory; 050import javax.crypto.spec.PBEKeySpec; 051import javax.security.auth.x500.X500Principal; 052 053/** 054 * This file has been copied from the Apache ZooKeeper project. 055 * @see <a href= 056 * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/util/PemReader.java">Base 057 * revision</a> 058 */ 059final class PemReader { 060 private static final Pattern CERT_PATTERN = 061 Pattern.compile("-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header 062 "([a-z0-9+/=\\r\\n]+)" + // Base64 text 063 "-+END\\s+.*CERTIFICATE[^-]*-+", // Footer 064 CASE_INSENSITIVE); 065 066 private static final Pattern PRIVATE_KEY_PATTERN = 067 Pattern.compile("-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header 068 "([a-z0-9+/=\\r\\n]+)" + // Base64 text 069 "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer 070 CASE_INSENSITIVE); 071 072 private static final Pattern PUBLIC_KEY_PATTERN = 073 Pattern.compile("-+BEGIN\\s+.*PUBLIC\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header 074 "([a-z0-9+/=\\r\\n]+)" + // Base64 text 075 "-+END\\s+.*PUBLIC\\s+KEY[^-]*-+", // Footer 076 CASE_INSENSITIVE); 077 078 private PemReader() { 079 } 080 081 public static KeyStore loadTrustStore(File certificateChainFile) 082 throws IOException, GeneralSecurityException { 083 KeyStore keyStore = KeyStore.getInstance("JKS"); 084 keyStore.load(null, null); 085 086 List<X509Certificate> certificateChain = readCertificateChain(certificateChainFile); 087 for (X509Certificate certificate : certificateChain) { 088 X500Principal principal = certificate.getSubjectX500Principal(); 089 keyStore.setCertificateEntry(principal.getName("RFC2253"), certificate); 090 } 091 return keyStore; 092 } 093 094 public static KeyStore loadKeyStore(File certificateChainFile, File privateKeyFile, 095 char[] keyPassword) throws IOException, GeneralSecurityException { 096 PrivateKey key = loadPrivateKey(privateKeyFile, keyPassword); 097 098 List<X509Certificate> certificateChain = readCertificateChain(certificateChainFile); 099 if (certificateChain.isEmpty()) { 100 throw new CertificateException( 101 "Certificate file does not contain any certificates: " + certificateChainFile); 102 } 103 104 KeyStore keyStore = KeyStore.getInstance("JKS"); 105 keyStore.load(null, null); 106 keyStore.setKeyEntry("key", key, keyPassword, certificateChain.toArray(new Certificate[0])); 107 return keyStore; 108 } 109 110 public static List<X509Certificate> readCertificateChain(File certificateChainFile) 111 throws IOException, GeneralSecurityException { 112 String contents = new String(Files.readAllBytes(certificateChainFile.toPath()), US_ASCII); 113 return readCertificateChain(contents); 114 } 115 116 public static List<X509Certificate> readCertificateChain(String certificateChain) 117 throws CertificateException { 118 Matcher matcher = CERT_PATTERN.matcher(certificateChain); 119 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 120 List<X509Certificate> certificates = new ArrayList<>(); 121 122 int start = 0; 123 while (matcher.find(start)) { 124 byte[] buffer = base64Decode(matcher.group(1)); 125 certificates.add( 126 (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(buffer))); 127 start = matcher.end(); 128 } 129 130 return certificates; 131 } 132 133 public static PrivateKey loadPrivateKey(File privateKeyFile, char[] keyPassword) 134 throws IOException, GeneralSecurityException { 135 String privateKey = new String(Files.readAllBytes(privateKeyFile.toPath()), US_ASCII); 136 return loadPrivateKey(privateKey, keyPassword); 137 } 138 139 public static PrivateKey loadPrivateKey(String privateKey, char[] keyPassword) 140 throws IOException, GeneralSecurityException { 141 Matcher matcher = PRIVATE_KEY_PATTERN.matcher(privateKey); 142 if (!matcher.find()) { 143 throw new KeyStoreException("did not find a private key"); 144 } 145 byte[] encodedKey = base64Decode(matcher.group(1)); 146 147 PKCS8EncodedKeySpec encodedKeySpec; 148 if (keyPassword != null && keyPassword.length > 0) { 149 EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey); 150 SecretKeyFactory keyFactory = 151 SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); 152 SecretKey secretKey = keyFactory.generateSecret(new PBEKeySpec(keyPassword)); 153 154 Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); 155 cipher.init(DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters()); 156 157 encodedKeySpec = encryptedPrivateKeyInfo.getKeySpec(cipher); 158 } else { 159 encodedKeySpec = new PKCS8EncodedKeySpec(encodedKey); 160 } 161 162 // this code requires a key in PKCS8 format which is not the default openssl format 163 // to convert to the PKCS8 format you use : openssl pkcs8 -topk8 ... 164 try { 165 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 166 return keyFactory.generatePrivate(encodedKeySpec); 167 } catch (InvalidKeySpecException ignore) { 168 // ignore 169 } 170 171 try { 172 KeyFactory keyFactory = KeyFactory.getInstance("EC"); 173 return keyFactory.generatePrivate(encodedKeySpec); 174 } catch (InvalidKeySpecException ignore) { 175 // ignore 176 } 177 178 KeyFactory keyFactory = KeyFactory.getInstance("DSA"); 179 return keyFactory.generatePrivate(encodedKeySpec); 180 } 181 182 public static PublicKey loadPublicKey(File publicKeyFile) 183 throws IOException, GeneralSecurityException { 184 String publicKey = new String(Files.readAllBytes(publicKeyFile.toPath()), US_ASCII); 185 return loadPublicKey(publicKey); 186 } 187 188 public static PublicKey loadPublicKey(String publicKey) throws GeneralSecurityException { 189 Matcher matcher = PUBLIC_KEY_PATTERN.matcher(publicKey); 190 if (!matcher.find()) { 191 throw new KeyStoreException("did not find a public key"); 192 } 193 String data = matcher.group(1); 194 byte[] encodedKey = base64Decode(data); 195 196 X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(encodedKey); 197 try { 198 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 199 return keyFactory.generatePublic(encodedKeySpec); 200 } catch (InvalidKeySpecException ignore) { 201 // ignore 202 } 203 204 try { 205 KeyFactory keyFactory = KeyFactory.getInstance("EC"); 206 return keyFactory.generatePublic(encodedKeySpec); 207 } catch (InvalidKeySpecException ignore) { 208 // ignore 209 } 210 211 KeyFactory keyFactory = KeyFactory.getInstance("DSA"); 212 return keyFactory.generatePublic(encodedKeySpec); 213 } 214 215 private static byte[] base64Decode(String base64) { 216 return getMimeDecoder().decode(base64.getBytes(US_ASCII)); 217 } 218}