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