1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package org.apache.hadoop.hbase.http.ssl; 20 21 import java.io.File; 22 import java.io.FileOutputStream; 23 import java.io.FileWriter; 24 import java.io.IOException; 25 import java.io.Writer; 26 import java.math.BigInteger; 27 import java.net.URL; 28 import java.security.GeneralSecurityException; 29 import java.security.InvalidKeyException; 30 import java.security.Key; 31 import java.security.KeyPair; 32 import java.security.KeyPairGenerator; 33 import java.security.KeyStore; 34 import java.security.NoSuchAlgorithmException; 35 import java.security.NoSuchProviderException; 36 import java.security.SecureRandom; 37 import java.security.SignatureException; 38 import java.security.cert.Certificate; 39 import java.security.cert.CertificateEncodingException; 40 import java.security.cert.X509Certificate; 41 import java.util.Date; 42 import java.util.HashMap; 43 import java.util.Map; 44 45 import javax.security.auth.x500.X500Principal; 46 47 import org.apache.hadoop.conf.Configuration; 48 import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory; 49 import org.apache.hadoop.security.ssl.SSLFactory; 50 import org.bouncycastle.x509.X509V1CertificateGenerator; 51 52 public class KeyStoreTestUtil { 53 getClasspathDir(Class<?> klass)54 public static String getClasspathDir(Class<?> klass) throws Exception { 55 String file = klass.getName(); 56 file = file.replace('.', '/') + ".class"; 57 URL url = Thread.currentThread().getContextClassLoader().getResource(file); 58 String baseDir = url.toURI().getPath(); 59 baseDir = baseDir.substring(0, baseDir.length() - file.length() - 1); 60 return baseDir; 61 } 62 63 /** 64 * Create a self-signed X.509 Certificate. 65 * 66 * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB" 67 * @param pair the KeyPair 68 * @param days how many days from now the Certificate is valid for 69 * @param algorithm the signing algorithm, eg "SHA1withRSA" 70 * @return the self-signed certificate 71 */ generateCertificate(String dn, KeyPair pair, int days, String algorithm)72 public static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm) 73 throws CertificateEncodingException, InvalidKeyException, IllegalStateException, 74 NoSuchProviderException, NoSuchAlgorithmException, SignatureException { 75 Date from = new Date(); 76 Date to = new Date(from.getTime() + days * 86400000l); 77 BigInteger sn = new BigInteger(64, new SecureRandom()); 78 KeyPair keyPair = pair; 79 X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); 80 X500Principal dnName = new X500Principal(dn); 81 82 certGen.setSerialNumber(sn); 83 certGen.setIssuerDN(dnName); 84 certGen.setNotBefore(from); 85 certGen.setNotAfter(to); 86 certGen.setSubjectDN(dnName); 87 certGen.setPublicKey(keyPair.getPublic()); 88 certGen.setSignatureAlgorithm(algorithm); 89 X509Certificate cert = certGen.generate(pair.getPrivate()); 90 return cert; 91 } 92 generateKeyPair(String algorithm)93 public static KeyPair generateKeyPair(String algorithm) 94 throws NoSuchAlgorithmException { 95 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); 96 keyGen.initialize(1024); 97 return keyGen.genKeyPair(); 98 } 99 createEmptyKeyStore()100 private static KeyStore createEmptyKeyStore() 101 throws GeneralSecurityException, IOException { 102 KeyStore ks = KeyStore.getInstance("JKS"); 103 ks.load(null, null); // initialize 104 return ks; 105 } 106 saveKeyStore(KeyStore ks, String filename, String password)107 private static void saveKeyStore(KeyStore ks, String filename, 108 String password) 109 throws GeneralSecurityException, IOException { 110 FileOutputStream out = new FileOutputStream(filename); 111 try { 112 ks.store(out, password.toCharArray()); 113 } finally { 114 out.close(); 115 } 116 } 117 createKeyStore(String filename, String password, String alias, Key privateKey, Certificate cert)118 public static void createKeyStore(String filename, 119 String password, String alias, 120 Key privateKey, Certificate cert) 121 throws GeneralSecurityException, IOException { 122 KeyStore ks = createEmptyKeyStore(); 123 ks.setKeyEntry(alias, privateKey, password.toCharArray(), 124 new Certificate[]{cert}); 125 saveKeyStore(ks, filename, password); 126 } 127 128 /** 129 * Creates a keystore with a single key and saves it to a file. 130 * 131 * @param filename String file to save 132 * @param password String store password to set on keystore 133 * @param keyPassword String key password to set on key 134 * @param alias String alias to use for the key 135 * @param privateKey Key to save in keystore 136 * @param cert Certificate to use as certificate chain associated to key 137 * @throws GeneralSecurityException for any error with the security APIs 138 * @throws IOException if there is an I/O error saving the file 139 */ createKeyStore(String filename, String password, String keyPassword, String alias, Key privateKey, Certificate cert)140 public static void createKeyStore(String filename, 141 String password, String keyPassword, String alias, 142 Key privateKey, Certificate cert) 143 throws GeneralSecurityException, IOException { 144 KeyStore ks = createEmptyKeyStore(); 145 ks.setKeyEntry(alias, privateKey, keyPassword.toCharArray(), 146 new Certificate[]{cert}); 147 saveKeyStore(ks, filename, password); 148 } 149 createTrustStore(String filename, String password, String alias, Certificate cert)150 public static void createTrustStore(String filename, 151 String password, String alias, 152 Certificate cert) 153 throws GeneralSecurityException, IOException { 154 KeyStore ks = createEmptyKeyStore(); 155 ks.setCertificateEntry(alias, cert); 156 saveKeyStore(ks, filename, password); 157 } 158 createTrustStore( String filename, String password, Map<String, T> certs)159 public static <T extends Certificate> void createTrustStore( 160 String filename, String password, Map<String, T> certs) 161 throws GeneralSecurityException, IOException { 162 KeyStore ks = createEmptyKeyStore(); 163 for (Map.Entry<String, T> cert : certs.entrySet()) { 164 ks.setCertificateEntry(cert.getKey(), cert.getValue()); 165 } 166 saveKeyStore(ks, filename, password); 167 } 168 cleanupSSLConfig(String keystoresDir, String sslConfDir)169 public static void cleanupSSLConfig(String keystoresDir, String sslConfDir) 170 throws Exception { 171 File f = new File(keystoresDir + "/clientKS.jks"); 172 f.delete(); 173 f = new File(keystoresDir + "/serverKS.jks"); 174 f.delete(); 175 f = new File(keystoresDir + "/trustKS.jks"); 176 f.delete(); 177 f = new File(sslConfDir + "/ssl-client.xml"); 178 f.delete(); 179 f = new File(sslConfDir + "/ssl-server.xml"); 180 f.delete(); 181 } 182 183 /** 184 * Performs complete setup of SSL configuration in preparation for testing an 185 * SSLFactory. This includes keys, certs, keystores, truststores, the server 186 * SSL configuration file, the client SSL configuration file, and the master 187 * configuration file read by the SSLFactory. 188 * 189 * @param keystoresDir String directory to save keystores 190 * @param sslConfDir String directory to save SSL configuration files 191 * @param conf Configuration master configuration to be used by an SSLFactory, 192 * which will be mutated by this method 193 * @param useClientCert boolean true to make the client present a cert in the 194 * SSL handshake 195 */ setupSSLConfig(String keystoresDir, String sslConfDir, Configuration conf, boolean useClientCert)196 public static void setupSSLConfig(String keystoresDir, String sslConfDir, 197 Configuration conf, boolean useClientCert) 198 throws Exception { 199 String clientKS = keystoresDir + "/clientKS.jks"; 200 String clientPassword = "clientP"; 201 String serverKS = keystoresDir + "/serverKS.jks"; 202 String serverPassword = "serverP"; 203 String trustKS = keystoresDir + "/trustKS.jks"; 204 String trustPassword = "trustP"; 205 206 File sslClientConfFile = new File(sslConfDir + "/ssl-client.xml"); 207 File sslServerConfFile = new File(sslConfDir + "/ssl-server.xml"); 208 209 Map<String, X509Certificate> certs = new HashMap<String, X509Certificate>(); 210 211 if (useClientCert) { 212 KeyPair cKP = KeyStoreTestUtil.generateKeyPair("RSA"); 213 X509Certificate cCert = 214 KeyStoreTestUtil.generateCertificate("CN=localhost, O=client", cKP, 30, 215 "SHA1withRSA"); 216 KeyStoreTestUtil.createKeyStore(clientKS, clientPassword, "client", 217 cKP.getPrivate(), cCert); 218 certs.put("client", cCert); 219 } 220 221 KeyPair sKP = KeyStoreTestUtil.generateKeyPair("RSA"); 222 X509Certificate sCert = 223 KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", sKP, 30, 224 "SHA1withRSA"); 225 KeyStoreTestUtil.createKeyStore(serverKS, serverPassword, "server", 226 sKP.getPrivate(), sCert); 227 certs.put("server", sCert); 228 229 KeyStoreTestUtil.createTrustStore(trustKS, trustPassword, certs); 230 231 Configuration clientSSLConf = createClientSSLConfig(clientKS, clientPassword, 232 clientPassword, trustKS); 233 Configuration serverSSLConf = createServerSSLConfig(serverKS, serverPassword, 234 serverPassword, trustKS); 235 236 saveConfig(sslClientConfFile, clientSSLConf); 237 saveConfig(sslServerConfFile, serverSSLConf); 238 239 conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL"); 240 conf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getName()); 241 conf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getName()); 242 conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert); 243 } 244 245 /** 246 * Creates SSL configuration for a client. 247 * 248 * @param clientKS String client keystore file 249 * @param password String store password, or null to avoid setting store 250 * password 251 * @param keyPassword String key password, or null to avoid setting key 252 * password 253 * @param trustKS String truststore file 254 * @return Configuration for client SSL 255 */ createClientSSLConfig(String clientKS, String password, String keyPassword, String trustKS)256 public static Configuration createClientSSLConfig(String clientKS, 257 String password, String keyPassword, String trustKS) { 258 Configuration clientSSLConf = createSSLConfig(SSLFactory.Mode.CLIENT, 259 clientKS, password, keyPassword, trustKS); 260 return clientSSLConf; 261 } 262 263 /** 264 * Creates SSL configuration for a server. 265 * 266 * @param serverKS String server keystore file 267 * @param password String store password, or null to avoid setting store 268 * password 269 * @param keyPassword String key password, or null to avoid setting key 270 * password 271 * @param trustKS String truststore file 272 * @return Configuration for server SSL 273 */ createServerSSLConfig(String serverKS, String password, String keyPassword, String trustKS)274 public static Configuration createServerSSLConfig(String serverKS, 275 String password, String keyPassword, String trustKS) throws IOException { 276 Configuration serverSSLConf = createSSLConfig(SSLFactory.Mode.SERVER, 277 serverKS, password, keyPassword, trustKS); 278 return serverSSLConf; 279 } 280 281 /** 282 * Creates SSL configuration. 283 * 284 * @param mode SSLFactory.Mode mode to configure 285 * @param keystore String 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 SSL 292 */ createSSLConfig(SSLFactory.Mode mode, String keystore, String password, String keyPassword, String trustKS)293 private static Configuration createSSLConfig(SSLFactory.Mode mode, 294 String keystore, String password, String keyPassword, String trustKS) { 295 String trustPassword = "trustP"; 296 297 Configuration sslConf = new Configuration(false); 298 if (keystore != null) { 299 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 300 FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), keystore); 301 } 302 if (password != null) { 303 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 304 FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), password); 305 } 306 if (keyPassword != null) { 307 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 308 FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY), 309 keyPassword); 310 } 311 if (trustKS != null) { 312 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 313 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS); 314 } 315 if (trustPassword != null) { 316 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 317 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY), 318 trustPassword); 319 } 320 sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, 321 FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000"); 322 323 return sslConf; 324 } 325 326 /** 327 * Saves configuration to a file. 328 * 329 * @param file File to save 330 * @param conf Configuration contents to write to file 331 * @throws IOException if there is an I/O error saving the file 332 */ saveConfig(File file, Configuration conf)333 public static void saveConfig(File file, Configuration conf) 334 throws IOException { 335 Writer writer = new FileWriter(file); 336 try { 337 conf.writeXml(writer); 338 } finally { 339 writer.close(); 340 } 341 } 342 } 343