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.http.ssl; 019 020import java.io.File; 021import java.io.FileOutputStream; 022import java.io.FileWriter; 023import java.io.IOException; 024import java.io.Writer; 025import java.math.BigInteger; 026import java.net.URL; 027import java.security.GeneralSecurityException; 028import java.security.InvalidKeyException; 029import java.security.Key; 030import java.security.KeyPair; 031import java.security.KeyPairGenerator; 032import java.security.KeyStore; 033import java.security.NoSuchAlgorithmException; 034import java.security.NoSuchProviderException; 035import java.security.SecureRandom; 036import java.security.SignatureException; 037import java.security.cert.Certificate; 038import java.security.cert.CertificateEncodingException; 039import java.security.cert.X509Certificate; 040import java.util.Date; 041import java.util.HashMap; 042import java.util.Map; 043import javax.security.auth.x500.X500Principal; 044import org.apache.hadoop.conf.Configuration; 045import org.apache.hadoop.hbase.HBaseCommonTestingUtil; 046import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory; 047import org.apache.hadoop.security.ssl.SSLFactory; 048import org.bouncycastle.x509.X509V1CertificateGenerator; 049 050public final class KeyStoreTestUtil { 051 private KeyStoreTestUtil() { 052 } 053 054 public static String getClasspathDir(Class<?> klass) throws Exception { 055 String file = klass.getName(); 056 file = file.replace('.', '/') + ".class"; 057 URL url = Thread.currentThread().getContextClassLoader().getResource(file); 058 String baseDir = url.toURI().getPath(); 059 baseDir = baseDir.substring(0, baseDir.length() - file.length() - 1); 060 return baseDir; 061 } 062 063 /** 064 * Create a self-signed X.509 Certificate. 065 * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB" 066 * @param pair the KeyPair 067 * @param days how many days from now the Certificate is valid for 068 * @param algorithm the signing algorithm, eg "SHA1withRSA" 069 * @return the self-signed certificate 070 */ 071 public static X509Certificate generateCertificate(String dn, KeyPair pair, int days, 072 String algorithm) throws CertificateEncodingException, InvalidKeyException, 073 IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException { 074 Date from = new Date(); 075 Date to = new Date(from.getTime() + days * 86400000L); 076 BigInteger sn = new BigInteger(64, new SecureRandom()); 077 KeyPair keyPair = pair; 078 X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); 079 X500Principal dnName = new X500Principal(dn); 080 081 certGen.setSerialNumber(sn); 082 certGen.setIssuerDN(dnName); 083 certGen.setNotBefore(from); 084 certGen.setNotAfter(to); 085 certGen.setSubjectDN(dnName); 086 certGen.setPublicKey(keyPair.getPublic()); 087 certGen.setSignatureAlgorithm(algorithm); 088 X509Certificate cert = certGen.generate(pair.getPrivate()); 089 return cert; 090 } 091 092 public static KeyPair generateKeyPair(String algorithm) throws NoSuchAlgorithmException { 093 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); 094 keyGen.initialize(1024); 095 return keyGen.genKeyPair(); 096 } 097 098 private static KeyStore createEmptyKeyStore() throws GeneralSecurityException, IOException { 099 return createEmptyKeyStore("jks"); 100 } 101 102 private static KeyStore createEmptyKeyStore(String keyStoreType) 103 throws GeneralSecurityException, IOException { 104 KeyStore ks = KeyStore.getInstance(keyStoreType); 105 ks.load(null, null); // initialize 106 return ks; 107 } 108 109 private static void saveKeyStore(KeyStore ks, String filename, String password) 110 throws GeneralSecurityException, IOException { 111 FileOutputStream out = new FileOutputStream(filename); 112 try { 113 ks.store(out, password.toCharArray()); 114 } finally { 115 out.close(); 116 } 117 } 118 119 /** 120 * Creates a keystore with a single key and saves it to a file. This method will use the same 121 * password for the keystore and for the key. This method will always generate a keystore file in 122 * JKS format. 123 * @param filename String file to save 124 * @param password String store password to set on keystore 125 * @param alias String alias to use for the key 126 * @param privateKey Key to save in keystore 127 * @param cert Certificate to use as certificate chain associated to key 128 * @throws GeneralSecurityException for any error with the security APIs 129 * @throws IOException if there is an I/O error saving the file 130 */ 131 public static void createKeyStore(String filename, String password, String alias, Key privateKey, 132 Certificate cert) throws GeneralSecurityException, IOException { 133 createKeyStore(filename, password, password, alias, privateKey, cert); 134 } 135 136 /** 137 * Creates a keystore with a single key and saves it to a file. This method will always generate a 138 * keystore file in JKS format. 139 * @param filename String file to save 140 * @param password String store password to set on keystore 141 * @param keyPassword String key password to set on key 142 * @param alias String alias to use for the key 143 * @param privateKey Key to save in keystore 144 * @param cert Certificate to use as certificate chain associated to key 145 * @throws GeneralSecurityException for any error with the security APIs 146 * @throws IOException if there is an I/O error saving the file 147 */ 148 public static void createKeyStore(String filename, String password, String keyPassword, 149 String alias, Key privateKey, Certificate cert) throws GeneralSecurityException, IOException { 150 createKeyStore(filename, password, keyPassword, alias, privateKey, cert, "JKS"); 151 } 152 153 /** 154 * Creates a keystore with a single key and saves it to a file. 155 * @param filename String file to save 156 * @param password String store password to set on keystore 157 * @param keyPassword String key password to set on key 158 * @param alias String alias to use for the key 159 * @param privateKey Key to save in keystore 160 * @param cert Certificate to use as certificate chain associated to key 161 * @param keystoreType String keystore file type (e.g. "JKS") 162 * @throws GeneralSecurityException for any error with the security APIs 163 * @throws IOException if there is an I/O error saving the file 164 */ 165 public static void createKeyStore(String filename, String password, String keyPassword, 166 String alias, Key privateKey, Certificate cert, String keystoreType) 167 throws GeneralSecurityException, IOException { 168 KeyStore ks = createEmptyKeyStore(keystoreType); 169 ks.setKeyEntry(alias, privateKey, keyPassword.toCharArray(), new Certificate[] { cert }); 170 saveKeyStore(ks, filename, password); 171 } 172 173 /** 174 * Creates a truststore with a single certificate and saves it to a file. This method uses the 175 * default JKS truststore type. 176 * @param filename String file to save 177 * @param password String store password to set on truststore 178 * @param alias String alias to use for the certificate 179 * @param cert Certificate to add 180 * @throws GeneralSecurityException for any error with the security APIs 181 * @throws IOException if there is an I/O error saving the file 182 */ 183 public static void createTrustStore(String filename, String password, String alias, 184 Certificate cert) throws GeneralSecurityException, IOException { 185 createTrustStore(filename, password, alias, cert, "JKS"); 186 } 187 188 /** 189 * Creates a truststore with a single certificate and saves it to a file. 190 * @param filename String file to save 191 * @param password String store password to set on truststore 192 * @param alias String alias to use for the certificate 193 * @param cert Certificate to add 194 * @param trustStoreType String keystore file type (e.g. "JKS") 195 * @throws GeneralSecurityException for any error with the security APIs 196 * @throws IOException if there is an I/O error saving the file 197 */ 198 public static void createTrustStore(String filename, String password, String alias, 199 Certificate cert, String trustStoreType) throws GeneralSecurityException, IOException { 200 KeyStore ks = createEmptyKeyStore(trustStoreType); 201 ks.setCertificateEntry(alias, cert); 202 saveKeyStore(ks, filename, password); 203 } 204 205 public static <T extends Certificate> void createTrustStore(String filename, String password, 206 Map<String, T> certs) throws GeneralSecurityException, IOException { 207 KeyStore ks = createEmptyKeyStore(); 208 for (Map.Entry<String, T> cert : certs.entrySet()) { 209 ks.setCertificateEntry(cert.getKey(), cert.getValue()); 210 } 211 saveKeyStore(ks, filename, password); 212 } 213 214 public static void cleanupSSLConfig(Configuration conf) throws Exception { 215 File f = new File(conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, 216 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY))); 217 f.delete(); 218 f = new File(conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, 219 FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY))); 220 f.delete(); 221 222 String clientKeyStore = 223 conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.CLIENT, 224 FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY)); 225 if (clientKeyStore != null) { 226 f = new File(clientKeyStore); 227 f.delete(); 228 } 229 f = new File(KeyStoreTestUtil.getClasspathDir(KeyStoreTestUtil.class) + "/" 230 + conf.get(SSLFactory.SSL_CLIENT_CONF_KEY)); 231 f.delete(); 232 f = new File(KeyStoreTestUtil.getClasspathDir(KeyStoreTestUtil.class) + "/" 233 + conf.get(SSLFactory.SSL_SERVER_CONF_KEY)); 234 f.delete(); 235 } 236 237 /** 238 * Performs complete setup of SSL configuration in preparation for testing an SSLFactory. This 239 * includes keys, certs, keystores, truststores, the server SSL configuration file, the client SSL 240 * configuration file, and the master configuration file read by the SSLFactory. 241 * @param keystoresDir String directory to save keystores 242 * @param sslConfDir String directory to save SSL configuration files 243 * @param conf Configuration master configuration to be used by an SSLFactory, which will 244 * be mutated by this method 245 * @param useClientCert boolean true to make the client present a cert in the SSL handshake 246 */ 247 public static void setupSSLConfig(String keystoresDir, String sslConfDir, Configuration conf, 248 boolean useClientCert) throws Exception { 249 String clientKS = keystoresDir + "/clientKS.jks"; 250 String clientPassword = "clientP"; 251 String serverKS = keystoresDir + "/serverKS.jks"; 252 String serverPassword = "serverP"; 253 String trustKS = keystoresDir + "/trustKS.jks"; 254 String trustPassword = "trustP"; 255 256 File sslClientConfFile = new File(sslConfDir + "/ssl-client-" + System.nanoTime() + "-" 257 + HBaseCommonTestingUtil.getRandomUUID() + ".xml"); 258 File sslServerConfFile = new File(sslConfDir + "/ssl-server-" + System.nanoTime() + "-" 259 + HBaseCommonTestingUtil.getRandomUUID() + ".xml"); 260 261 Map<String, X509Certificate> certs = new HashMap<>(); 262 263 if (useClientCert) { 264 KeyPair cKP = KeyStoreTestUtil.generateKeyPair("RSA"); 265 X509Certificate cCert = 266 KeyStoreTestUtil.generateCertificate("CN=localhost, O=client", cKP, 30, "SHA1withRSA"); 267 KeyStoreTestUtil.createKeyStore(clientKS, clientPassword, "client", cKP.getPrivate(), cCert); 268 certs.put("client", cCert); 269 } 270 271 KeyPair sKP = KeyStoreTestUtil.generateKeyPair("RSA"); 272 X509Certificate sCert = 273 KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", sKP, 30, "SHA1withRSA"); 274 KeyStoreTestUtil.createKeyStore(serverKS, serverPassword, "server", sKP.getPrivate(), sCert); 275 certs.put("server", sCert); 276 277 KeyStoreTestUtil.createTrustStore(trustKS, trustPassword, certs); 278 279 Configuration clientSSLConf = 280 createClientSSLConfig(clientKS, clientPassword, clientPassword, trustKS); 281 Configuration serverSSLConf = 282 createServerSSLConfig(serverKS, serverPassword, serverPassword, trustKS); 283 284 saveConfig(sslClientConfFile, clientSSLConf); 285 saveConfig(sslServerConfFile, serverSSLConf); 286 287 conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL"); 288 conf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getName()); 289 conf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getName()); 290 conf.set("dfs.https.server.keystore.resource", sslServerConfFile.getName()); 291 292 conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert); 293 } 294 295 /** 296 * Creates SSL configuration for a client. 297 * @param clientKS String client keystore file 298 * @param password String store password, or null to avoid setting store password 299 * @param keyPassword String key password, or null to avoid setting key password 300 * @param trustKS String truststore file 301 * @return Configuration for client SSL 302 */ 303 public static Configuration createClientSSLConfig(String clientKS, String password, 304 String keyPassword, String trustKS) { 305 Configuration clientSSLConf = 306 createSSLConfig(SSLFactory.Mode.CLIENT, clientKS, password, keyPassword, trustKS); 307 return clientSSLConf; 308 } 309 310 /** 311 * Creates SSL configuration for a server. 312 * @param serverKS String server keystore file 313 * @param password String store password, or null to avoid setting store password 314 * @param keyPassword String key password, or null to avoid setting key password 315 * @param trustKS String truststore file 316 * @return Configuration for server SSL 317 */ 318 public static Configuration createServerSSLConfig(String serverKS, String password, 319 String keyPassword, String trustKS) throws IOException { 320 Configuration serverSSLConf = 321 createSSLConfig(SSLFactory.Mode.SERVER, serverKS, password, keyPassword, trustKS); 322 return serverSSLConf; 323 } 324 325 /** 326 * Creates SSL configuration. 327 * @param mode SSLFactory.Mode mode to configure 328 * @param keystore String keystore file 329 * @param password String store password, or null to avoid setting store password 330 * @param keyPassword String key password, or null to avoid setting key password 331 * @param trustKS String truststore file 332 * @return Configuration for SSL 333 */ 334 private static Configuration createSSLConfig(SSLFactory.Mode mode, String keystore, 335 String password, String keyPassword, String trustKS) { 336 String trustPassword = "trustP"; 337 338 Configuration sslConf = new Configuration(false); 339 if (keystore != null) { 340 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 341 FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), keystore); 342 } 343 if (password != null) { 344 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 345 FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), password); 346 } 347 if (keyPassword != null) { 348 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 349 FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY), keyPassword); 350 } 351 if (trustKS != null) { 352 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 353 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS); 354 } 355 if (trustPassword != null) { 356 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 357 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY), trustPassword); 358 } 359 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 360 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000"); 361 362 return sslConf; 363 } 364 365 /** 366 * Saves configuration to a file. 367 * @param file File to save 368 * @param conf Configuration contents to write to file 369 * @throws IOException if there is an I/O error saving the file 370 */ 371 public static void saveConfig(File file, Configuration conf) throws IOException { 372 Writer writer = new FileWriter(file); 373 try { 374 conf.writeXml(writer); 375 } finally { 376 writer.close(); 377 } 378 } 379}