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