| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.hbase.http.ssl; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.math.BigInteger; |
| import java.net.URL; |
| import java.nio.charset.StandardCharsets; |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidKeyException; |
| import java.security.Key; |
| import java.security.KeyPair; |
| import java.security.KeyPairGenerator; |
| import java.security.KeyStore; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.SecureRandom; |
| import java.security.SignatureException; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.X509Certificate; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Map; |
| import javax.security.auth.x500.X500Principal; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.hbase.HBaseCommonTestingUtil; |
| import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory; |
| import org.apache.hadoop.security.ssl.SSLFactory; |
| import org.bouncycastle.x509.X509V1CertificateGenerator; |
| |
| import org.apache.hbase.thirdparty.com.google.common.io.Files; |
| |
| public final class KeyStoreTestUtil { |
| private KeyStoreTestUtil() { |
| } |
| |
| public static String getClasspathDir(Class<?> klass) throws Exception { |
| String file = klass.getName(); |
| file = file.replace('.', '/') + ".class"; |
| URL url = Thread.currentThread().getContextClassLoader().getResource(file); |
| String baseDir = url.toURI().getPath(); |
| baseDir = baseDir.substring(0, baseDir.length() - file.length() - 1); |
| return baseDir; |
| } |
| |
| /** |
| * Create a self-signed X.509 Certificate. |
| * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB" |
| * @param pair the KeyPair |
| * @param days how many days from now the Certificate is valid for |
| * @param algorithm the signing algorithm, eg "SHA1withRSA" |
| * @return the self-signed certificate |
| */ |
| @SuppressWarnings("JavaUtilDate") |
| public static X509Certificate generateCertificate(String dn, KeyPair pair, int days, |
| String algorithm) throws CertificateEncodingException, InvalidKeyException, |
| IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException { |
| Date from = new Date(); |
| Date to = new Date(from.getTime() + days * 86400000L); |
| BigInteger sn = new BigInteger(64, new SecureRandom()); |
| KeyPair keyPair = pair; |
| X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); |
| X500Principal dnName = new X500Principal(dn); |
| |
| certGen.setSerialNumber(sn); |
| certGen.setIssuerDN(dnName); |
| certGen.setNotBefore(from); |
| certGen.setNotAfter(to); |
| certGen.setSubjectDN(dnName); |
| certGen.setPublicKey(keyPair.getPublic()); |
| certGen.setSignatureAlgorithm(algorithm); |
| X509Certificate cert = certGen.generate(pair.getPrivate()); |
| return cert; |
| } |
| |
| public static KeyPair generateKeyPair(String algorithm) throws NoSuchAlgorithmException { |
| KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); |
| keyGen.initialize(1024); |
| return keyGen.genKeyPair(); |
| } |
| |
| private static KeyStore createEmptyKeyStore() throws GeneralSecurityException, IOException { |
| return createEmptyKeyStore("jks"); |
| } |
| |
| private static KeyStore createEmptyKeyStore(String keyStoreType) |
| throws GeneralSecurityException, IOException { |
| KeyStore ks = KeyStore.getInstance(keyStoreType); |
| ks.load(null, null); // initialize |
| return ks; |
| } |
| |
| private static void saveKeyStore(KeyStore ks, String filename, String password) |
| throws GeneralSecurityException, IOException { |
| FileOutputStream out = new FileOutputStream(filename); |
| try { |
| ks.store(out, password.toCharArray()); |
| } finally { |
| out.close(); |
| } |
| } |
| |
| /** |
| * Creates a keystore with a single key and saves it to a file. This method will use the same |
| * password for the keystore and for the key. This method will always generate a keystore file in |
| * JKS format. |
| * @param filename String file to save |
| * @param password String store password to set on keystore |
| * @param alias String alias to use for the key |
| * @param privateKey Key to save in keystore |
| * @param cert Certificate to use as certificate chain associated to key |
| * @throws GeneralSecurityException for any error with the security APIs |
| * @throws IOException if there is an I/O error saving the file |
| */ |
| public static void createKeyStore(String filename, String password, String alias, Key privateKey, |
| Certificate cert) throws GeneralSecurityException, IOException { |
| createKeyStore(filename, password, password, alias, privateKey, cert); |
| } |
| |
| /** |
| * Creates a keystore with a single key and saves it to a file. This method will always generate a |
| * keystore file in JKS format. |
| * @param filename String file to save |
| * @param password String store password to set on keystore |
| * @param keyPassword String key password to set on key |
| * @param alias String alias to use for the key |
| * @param privateKey Key to save in keystore |
| * @param cert Certificate to use as certificate chain associated to key |
| * @throws GeneralSecurityException for any error with the security APIs |
| * @throws IOException if there is an I/O error saving the file |
| */ |
| public static void createKeyStore(String filename, String password, String keyPassword, |
| String alias, Key privateKey, Certificate cert) throws GeneralSecurityException, IOException { |
| createKeyStore(filename, password, keyPassword, alias, privateKey, cert, "JKS"); |
| } |
| |
| /** |
| * Creates a keystore with a single key and saves it to a file. |
| * @param filename String file to save |
| * @param password String store password to set on keystore |
| * @param keyPassword String key password to set on key |
| * @param alias String alias to use for the key |
| * @param privateKey Key to save in keystore |
| * @param cert Certificate to use as certificate chain associated to key |
| * @param keystoreType String keystore file type (e.g. "JKS") |
| * @throws GeneralSecurityException for any error with the security APIs |
| * @throws IOException if there is an I/O error saving the file |
| */ |
| public static void createKeyStore(String filename, String password, String keyPassword, |
| String alias, Key privateKey, Certificate cert, String keystoreType) |
| throws GeneralSecurityException, IOException { |
| KeyStore ks = createEmptyKeyStore(keystoreType); |
| ks.setKeyEntry(alias, privateKey, keyPassword.toCharArray(), new Certificate[] { cert }); |
| saveKeyStore(ks, filename, password); |
| } |
| |
| /** |
| * Creates a truststore with a single certificate and saves it to a file. This method uses the |
| * default JKS truststore type. |
| * @param filename String file to save |
| * @param password String store password to set on truststore |
| * @param alias String alias to use for the certificate |
| * @param cert Certificate to add |
| * @throws GeneralSecurityException for any error with the security APIs |
| * @throws IOException if there is an I/O error saving the file |
| */ |
| public static void createTrustStore(String filename, String password, String alias, |
| Certificate cert) throws GeneralSecurityException, IOException { |
| createTrustStore(filename, password, alias, cert, "JKS"); |
| } |
| |
| /** |
| * Creates a truststore with a single certificate and saves it to a file. |
| * @param filename String file to save |
| * @param password String store password to set on truststore |
| * @param alias String alias to use for the certificate |
| * @param cert Certificate to add |
| * @param trustStoreType String keystore file type (e.g. "JKS") |
| * @throws GeneralSecurityException for any error with the security APIs |
| * @throws IOException if there is an I/O error saving the file |
| */ |
| public static void createTrustStore(String filename, String password, String alias, |
| Certificate cert, String trustStoreType) throws GeneralSecurityException, IOException { |
| KeyStore ks = createEmptyKeyStore(trustStoreType); |
| ks.setCertificateEntry(alias, cert); |
| saveKeyStore(ks, filename, password); |
| } |
| |
| public static <T extends Certificate> void createTrustStore(String filename, String password, |
| Map<String, T> certs) throws GeneralSecurityException, IOException { |
| KeyStore ks = createEmptyKeyStore(); |
| for (Map.Entry<String, T> cert : certs.entrySet()) { |
| ks.setCertificateEntry(cert.getKey(), cert.getValue()); |
| } |
| saveKeyStore(ks, filename, password); |
| } |
| |
| public static void cleanupSSLConfig(Configuration conf) throws Exception { |
| File f = new File(conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, |
| FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY))); |
| f.delete(); |
| f = new File(conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.SERVER, |
| FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY))); |
| f.delete(); |
| |
| String clientKeyStore = |
| conf.get(FileBasedKeyStoresFactory.resolvePropertyName(SSLFactory.Mode.CLIENT, |
| FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY)); |
| if (clientKeyStore != null) { |
| f = new File(clientKeyStore); |
| f.delete(); |
| } |
| f = new File(KeyStoreTestUtil.getClasspathDir(KeyStoreTestUtil.class) + "/" |
| + conf.get(SSLFactory.SSL_CLIENT_CONF_KEY)); |
| f.delete(); |
| f = new File(KeyStoreTestUtil.getClasspathDir(KeyStoreTestUtil.class) + "/" |
| + conf.get(SSLFactory.SSL_SERVER_CONF_KEY)); |
| f.delete(); |
| } |
| |
| /** |
| * Performs complete setup of SSL configuration in preparation for testing an SSLFactory. This |
| * includes keys, certs, keystores, truststores, the server SSL configuration file, the client SSL |
| * configuration file, and the master configuration file read by the SSLFactory. |
| * @param keystoresDir String directory to save keystores |
| * @param sslConfDir String directory to save SSL configuration files |
| * @param conf Configuration master configuration to be used by an SSLFactory, which will |
| * be mutated by this method |
| * @param useClientCert boolean true to make the client present a cert in the SSL handshake |
| */ |
| public static void setupSSLConfig(String keystoresDir, String sslConfDir, Configuration conf, |
| boolean useClientCert) throws Exception { |
| String clientKS = keystoresDir + "/clientKS.jks"; |
| String clientPassword = "clientP"; |
| String serverKS = keystoresDir + "/serverKS.jks"; |
| String serverPassword = "serverP"; |
| String trustKS = keystoresDir + "/trustKS.jks"; |
| String trustPassword = "trustP"; |
| |
| File sslClientConfFile = new File(sslConfDir + "/ssl-client-" + System.nanoTime() + "-" |
| + HBaseCommonTestingUtil.getRandomUUID() + ".xml"); |
| File sslServerConfFile = new File(sslConfDir + "/ssl-server-" + System.nanoTime() + "-" |
| + HBaseCommonTestingUtil.getRandomUUID() + ".xml"); |
| |
| Map<String, X509Certificate> certs = new HashMap<>(); |
| |
| if (useClientCert) { |
| KeyPair cKP = KeyStoreTestUtil.generateKeyPair("RSA"); |
| X509Certificate cCert = |
| KeyStoreTestUtil.generateCertificate("CN=localhost, O=client", cKP, 30, "SHA1withRSA"); |
| KeyStoreTestUtil.createKeyStore(clientKS, clientPassword, "client", cKP.getPrivate(), cCert); |
| certs.put("client", cCert); |
| } |
| |
| KeyPair sKP = KeyStoreTestUtil.generateKeyPair("RSA"); |
| X509Certificate sCert = |
| KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", sKP, 30, "SHA1withRSA"); |
| KeyStoreTestUtil.createKeyStore(serverKS, serverPassword, "server", sKP.getPrivate(), sCert); |
| certs.put("server", sCert); |
| |
| KeyStoreTestUtil.createTrustStore(trustKS, trustPassword, certs); |
| |
| Configuration clientSSLConf = |
| createClientSSLConfig(clientKS, clientPassword, clientPassword, trustKS); |
| Configuration serverSSLConf = |
| createServerSSLConfig(serverKS, serverPassword, serverPassword, trustKS); |
| |
| saveConfig(sslClientConfFile, clientSSLConf); |
| saveConfig(sslServerConfFile, serverSSLConf); |
| |
| conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL"); |
| conf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getName()); |
| conf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getName()); |
| conf.set("dfs.https.server.keystore.resource", sslServerConfFile.getName()); |
| |
| conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert); |
| } |
| |
| /** |
| * Creates SSL configuration for a client. |
| * @param clientKS String client keystore file |
| * @param password String store password, or null to avoid setting store password |
| * @param keyPassword String key password, or null to avoid setting key password |
| * @param trustKS String truststore file |
| * @return Configuration for client SSL |
| */ |
| public static Configuration createClientSSLConfig(String clientKS, String password, |
| String keyPassword, String trustKS) { |
| Configuration clientSSLConf = |
| createSSLConfig(SSLFactory.Mode.CLIENT, clientKS, password, keyPassword, trustKS); |
| return clientSSLConf; |
| } |
| |
| /** |
| * Creates SSL configuration for a server. |
| * @param serverKS String server keystore file |
| * @param password String store password, or null to avoid setting store password |
| * @param keyPassword String key password, or null to avoid setting key password |
| * @param trustKS String truststore file |
| * @return Configuration for server SSL |
| */ |
| public static Configuration createServerSSLConfig(String serverKS, String password, |
| String keyPassword, String trustKS) throws IOException { |
| Configuration serverSSLConf = |
| createSSLConfig(SSLFactory.Mode.SERVER, serverKS, password, keyPassword, trustKS); |
| return serverSSLConf; |
| } |
| |
| /** |
| * Creates SSL configuration. |
| * @param mode SSLFactory.Mode mode to configure |
| * @param keystore String keystore file |
| * @param password String store password, or null to avoid setting store password |
| * @param keyPassword String key password, or null to avoid setting key password |
| * @param trustKS String truststore file |
| * @return Configuration for SSL |
| */ |
| private static Configuration createSSLConfig(SSLFactory.Mode mode, String keystore, |
| String password, String keyPassword, String trustKS) { |
| String trustPassword = "trustP"; |
| |
| Configuration sslConf = new Configuration(false); |
| if (keystore != null) { |
| sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, |
| FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), keystore); |
| } |
| if (password != null) { |
| sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, |
| FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), password); |
| } |
| if (keyPassword != null) { |
| sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, |
| FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY), keyPassword); |
| } |
| if (trustKS != null) { |
| sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, |
| FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS); |
| } |
| if (trustPassword != null) { |
| sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, |
| FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY), trustPassword); |
| } |
| sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode, |
| FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000"); |
| |
| return sslConf; |
| } |
| |
| /** |
| * Saves configuration to a file. |
| * @param file File to save |
| * @param conf Configuration contents to write to file |
| * @throws IOException if there is an I/O error saving the file |
| */ |
| public static void saveConfig(File file, Configuration conf) throws IOException { |
| try (Writer writer = Files.newWriter(file, StandardCharsets.UTF_8)) { |
| conf.writeXml(writer); |
| } |
| } |
| } |