View Javadoc

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  
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     */
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  
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  
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<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    */
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 }