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; 044 045import javax.security.auth.x500.X500Principal; 046 047import org.apache.hadoop.conf.Configuration; 048import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory; 049import org.apache.hadoop.security.ssl.SSLFactory; 050import org.bouncycastle.x509.X509V1CertificateGenerator; 051 052public class KeyStoreTestUtil { 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 * 066 * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB" 067 * @param pair the KeyPair 068 * @param days how many days from now the Certificate is valid for 069 * @param algorithm the signing algorithm, eg "SHA1withRSA" 070 * @return the self-signed certificate 071 */ 072 public static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm) 073 throws CertificateEncodingException, InvalidKeyException, IllegalStateException, 074 NoSuchProviderException, NoSuchAlgorithmException, SignatureException { 075 Date from = new Date(); 076 Date to = new Date(from.getTime() + days * 86400000l); 077 BigInteger sn = new BigInteger(64, new SecureRandom()); 078 KeyPair keyPair = pair; 079 X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); 080 X500Principal dnName = new X500Principal(dn); 081 082 certGen.setSerialNumber(sn); 083 certGen.setIssuerDN(dnName); 084 certGen.setNotBefore(from); 085 certGen.setNotAfter(to); 086 certGen.setSubjectDN(dnName); 087 certGen.setPublicKey(keyPair.getPublic()); 088 certGen.setSignatureAlgorithm(algorithm); 089 X509Certificate cert = certGen.generate(pair.getPrivate()); 090 return cert; 091 } 092 093 public static KeyPair generateKeyPair(String algorithm) 094 throws NoSuchAlgorithmException { 095 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); 096 keyGen.initialize(1024); 097 return keyGen.genKeyPair(); 098 } 099 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 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 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 */ 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 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 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 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 */ 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<>(); 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 */ 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 */ 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 */ 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 */ 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}