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 KeyStore ks = KeyStore.getInstance("JKS"); 105 ks.load(null, null); // initialize 106 return ks; 107 } 108 109 private static void saveKeyStore(KeyStore ks, String filename, 110 String password) 111 throws GeneralSecurityException, IOException { 112 FileOutputStream out = new FileOutputStream(filename); 113 try { 114 ks.store(out, password.toCharArray()); 115 } finally { 116 out.close(); 117 } 118 } 119 120 public static void createKeyStore(String filename, 121 String password, String alias, 122 Key privateKey, Certificate cert) 123 throws GeneralSecurityException, IOException { 124 KeyStore ks = createEmptyKeyStore(); 125 ks.setKeyEntry(alias, privateKey, password.toCharArray(), 126 new Certificate[]{cert}); 127 saveKeyStore(ks, filename, password); 128 } 129 130 /** 131 * Creates a keystore with a single key and saves it to a file. 132 * 133 * @param filename String file to save 134 * @param password String store password to set on keystore 135 * @param keyPassword String key password to set on key 136 * @param alias String alias to use for the key 137 * @param privateKey Key to save in keystore 138 * @param cert Certificate to use as certificate chain associated to key 139 * @throws GeneralSecurityException for any error with the security APIs 140 * @throws IOException if there is an I/O error saving the file 141 */ 142 public static void createKeyStore(String filename, 143 String password, String keyPassword, String alias, 144 Key privateKey, Certificate cert) 145 throws GeneralSecurityException, IOException { 146 KeyStore ks = createEmptyKeyStore(); 147 ks.setKeyEntry(alias, privateKey, keyPassword.toCharArray(), 148 new Certificate[]{cert}); 149 saveKeyStore(ks, filename, password); 150 } 151 152 public static void createTrustStore(String filename, 153 String password, String alias, 154 Certificate cert) 155 throws GeneralSecurityException, IOException { 156 KeyStore ks = createEmptyKeyStore(); 157 ks.setCertificateEntry(alias, cert); 158 saveKeyStore(ks, filename, password); 159 } 160 161 public static <T extends Certificate> void createTrustStore( 162 String filename, String password, Map<String, T> certs) 163 throws GeneralSecurityException, IOException { 164 KeyStore ks = createEmptyKeyStore(); 165 for (Map.Entry<String, T> cert : certs.entrySet()) { 166 ks.setCertificateEntry(cert.getKey(), cert.getValue()); 167 } 168 saveKeyStore(ks, filename, password); 169 } 170 171 public static void cleanupSSLConfig(Configuration conf) 172 throws Exception { 173 File f = new File(conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, 174 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY))); 175 f.delete(); 176 f = new File(conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, 177 FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY))); 178 f.delete(); 179 180 String clientKeyStore = conf.get(FileBasedKeyStoresFactory 181 .resolvePropertyName(SSLFactory.Mode.CLIENT, 182 FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY)); 183 if (clientKeyStore != null) { 184 f = new File(clientKeyStore); 185 f.delete(); 186 } 187 f = new File(KeyStoreTestUtil.getClasspathDir(KeyStoreTestUtil.class) + "/" + conf 188 .get(SSLFactory.SSL_CLIENT_CONF_KEY)); 189 f.delete(); 190 f = new File(KeyStoreTestUtil.getClasspathDir(KeyStoreTestUtil.class) + "/" + conf 191 .get(SSLFactory.SSL_SERVER_CONF_KEY)); 192 f.delete(); 193 } 194 195 /** 196 * Performs complete setup of SSL configuration in preparation for testing an 197 * SSLFactory. This includes keys, certs, keystores, truststores, the server 198 * SSL configuration file, the client SSL configuration file, and the master 199 * configuration file read by the SSLFactory. 200 * 201 * @param keystoresDir String directory to save keystores 202 * @param sslConfDir String directory to save SSL configuration files 203 * @param conf Configuration master configuration to be used by an SSLFactory, 204 * which will be mutated by this method 205 * @param useClientCert boolean true to make the client present a cert in the 206 * SSL handshake 207 */ 208 public static void setupSSLConfig(String keystoresDir, String sslConfDir, 209 Configuration conf, boolean useClientCert) 210 throws Exception { 211 String clientKS = keystoresDir + "/clientKS.jks"; 212 String clientPassword = "clientP"; 213 String serverKS = keystoresDir + "/serverKS.jks"; 214 String serverPassword = "serverP"; 215 String trustKS = keystoresDir + "/trustKS.jks"; 216 String trustPassword = "trustP"; 217 218 File sslClientConfFile = new File( 219 sslConfDir + "/ssl-client-" + System.nanoTime() + "-" + HBaseCommonTestingUtility 220 .getRandomUUID() + ".xml"); 221 File sslServerConfFile = new File( 222 sslConfDir + "/ssl-server-" + System.nanoTime() + "-" + HBaseCommonTestingUtility 223 .getRandomUUID() + ".xml"); 224 225 Map<String, X509Certificate> certs = new HashMap<>(); 226 227 if (useClientCert) { 228 KeyPair cKP = KeyStoreTestUtil.generateKeyPair("RSA"); 229 X509Certificate cCert = 230 KeyStoreTestUtil.generateCertificate("CN=localhost, O=client", cKP, 30, 231 "SHA1withRSA"); 232 KeyStoreTestUtil.createKeyStore(clientKS, clientPassword, "client", 233 cKP.getPrivate(), cCert); 234 certs.put("client", cCert); 235 } 236 237 KeyPair sKP = KeyStoreTestUtil.generateKeyPair("RSA"); 238 X509Certificate sCert = 239 KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", sKP, 30, 240 "SHA1withRSA"); 241 KeyStoreTestUtil.createKeyStore(serverKS, serverPassword, "server", 242 sKP.getPrivate(), sCert); 243 certs.put("server", sCert); 244 245 KeyStoreTestUtil.createTrustStore(trustKS, trustPassword, certs); 246 247 Configuration clientSSLConf = createClientSSLConfig(clientKS, clientPassword, 248 clientPassword, trustKS); 249 Configuration serverSSLConf = createServerSSLConfig(serverKS, serverPassword, 250 serverPassword, trustKS); 251 252 saveConfig(sslClientConfFile, clientSSLConf); 253 saveConfig(sslServerConfFile, serverSSLConf); 254 255 conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL"); 256 conf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getName()); 257 conf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getName()); 258 conf.set("dfs.https.server.keystore.resource", sslServerConfFile.getName()); 259 260 261 conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert); 262 } 263 264 /** 265 * Creates SSL configuration for a client. 266 * 267 * @param clientKS String client keystore file 268 * @param password String store password, or null to avoid setting store 269 * password 270 * @param keyPassword String key password, or null to avoid setting key 271 * password 272 * @param trustKS String truststore file 273 * @return Configuration for client SSL 274 */ 275 public static Configuration createClientSSLConfig(String clientKS, 276 String password, String keyPassword, String trustKS) { 277 Configuration clientSSLConf = createSSLConfig(SSLFactory.Mode.CLIENT, 278 clientKS, password, keyPassword, trustKS); 279 return clientSSLConf; 280 } 281 282 /** 283 * Creates SSL configuration for a server. 284 * 285 * @param serverKS String server keystore file 286 * @param password String store password, or null to avoid setting store 287 * password 288 * @param keyPassword String key password, or null to avoid setting key 289 * password 290 * @param trustKS String truststore file 291 * @return Configuration for server SSL 292 */ 293 public static Configuration createServerSSLConfig(String serverKS, 294 String password, String keyPassword, String trustKS) throws IOException { 295 Configuration serverSSLConf = createSSLConfig(SSLFactory.Mode.SERVER, 296 serverKS, password, keyPassword, trustKS); 297 return serverSSLConf; 298 } 299 300 /** 301 * Creates SSL configuration. 302 * 303 * @param mode SSLFactory.Mode mode to configure 304 * @param keystore String keystore file 305 * @param password String store password, or null to avoid setting store 306 * password 307 * @param keyPassword String key password, or null to avoid setting key 308 * password 309 * @param trustKS String truststore file 310 * @return Configuration for SSL 311 */ 312 private static Configuration createSSLConfig(SSLFactory.Mode mode, 313 String keystore, String password, String keyPassword, String trustKS) { 314 String trustPassword = "trustP"; 315 316 Configuration sslConf = new Configuration(false); 317 if (keystore != null) { 318 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 319 FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), keystore); 320 } 321 if (password != null) { 322 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 323 FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), password); 324 } 325 if (keyPassword != null) { 326 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 327 FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY), 328 keyPassword); 329 } 330 if (trustKS != null) { 331 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 332 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS); 333 } 334 if (trustPassword != null) { 335 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 336 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY), 337 trustPassword); 338 } 339 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 340 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000"); 341 342 return sslConf; 343 } 344 345 /** 346 * Saves configuration to a file. 347 * 348 * @param file File to save 349 * @param conf Configuration contents to write to file 350 * @throws IOException if there is an I/O error saving the file 351 */ 352 public static void saveConfig(File file, Configuration conf) 353 throws IOException { 354 Writer writer = new FileWriter(file); 355 try { 356 conf.writeXml(writer); 357 } finally { 358 writer.close(); 359 } 360 } 361}