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}