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.Key;
30  import java.security.KeyPair;
31  import java.security.KeyPairGenerator;
32  import java.security.KeyStore;
33  import java.security.NoSuchAlgorithmException;
34  import java.security.PrivateKey;
35  import java.security.SecureRandom;
36  import java.security.cert.Certificate;
37  import java.security.cert.X509Certificate;
38  import java.util.Date;
39  import java.util.HashMap;
40  import java.util.Map;
41  
42  import org.apache.hadoop.conf.Configuration;
43  import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory;
44  import org.apache.hadoop.security.ssl.SSLFactory;
45  
46  import sun.security.x509.AlgorithmId;
47  import sun.security.x509.CertificateAlgorithmId;
48  import sun.security.x509.CertificateIssuerName;
49  import sun.security.x509.CertificateSerialNumber;
50  import sun.security.x509.CertificateSubjectName;
51  import sun.security.x509.CertificateValidity;
52  import sun.security.x509.CertificateVersion;
53  import sun.security.x509.CertificateX509Key;
54  import sun.security.x509.X500Name;
55  import sun.security.x509.X509CertImpl;
56  import sun.security.x509.X509CertInfo;
57  
58  public class KeyStoreTestUtil {
59  
60    public static String getClasspathDir(Class klass) throws Exception {
61      String file = klass.getName();
62      file = file.replace('.', '/') + ".class";
63      URL url = Thread.currentThread().getContextClassLoader().getResource(file);
64      String baseDir = url.toURI().getPath();
65      baseDir = baseDir.substring(0, baseDir.length() - file.length() - 1);
66      return baseDir;
67    }
68  
69    /**
70     * Create a self-signed X.509 Certificate.
71     * From http://bfo.com/blog/2011/03/08/odds_and_ends_creating_a_new_x_509_certificate.html.
72     *
73     * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
74     * @param pair the KeyPair
75     * @param days how many days from now the Certificate is valid for
76     * @param algorithm the signing algorithm, eg "SHA1withRSA"
77     * @return the self-signed certificate
78     * @throws IOException thrown if an IO error ocurred.
79     * @throws GeneralSecurityException thrown if an Security error ocurred.
80     */
81    public static X509Certificate generateCertificate(String dn, KeyPair pair,
82                                                      int days, String algorithm)
83      throws GeneralSecurityException, IOException {
84      PrivateKey privkey = pair.getPrivate();
85      X509CertInfo info = new X509CertInfo();
86      Date from = new Date();
87      Date to = new Date(from.getTime() + days * 86400000l);
88      CertificateValidity interval = new CertificateValidity(from, to);
89      BigInteger sn = new BigInteger(64, new SecureRandom());
90      X500Name owner = new X500Name(dn);
91  
92      info.set(X509CertInfo.VALIDITY, interval);
93      info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
94      info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
95      info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
96      info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
97      info
98        .set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
99      AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
100     info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
101 
102     // Sign the cert to identify the algorithm that's used.
103     X509CertImpl cert = new X509CertImpl(info);
104     cert.sign(privkey, algorithm);
105 
106     // Update the algorith, and resign.
107     algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
108     info
109       .set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM,
110            algo);
111     cert = new X509CertImpl(info);
112     cert.sign(privkey, algorithm);
113     return cert;
114   }
115 
116   public static KeyPair generateKeyPair(String algorithm)
117     throws NoSuchAlgorithmException {
118     KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
119     keyGen.initialize(1024);
120     return keyGen.genKeyPair();
121   }
122 
123   private static KeyStore createEmptyKeyStore()
124     throws GeneralSecurityException, IOException {
125     KeyStore ks = KeyStore.getInstance("JKS");
126     ks.load(null, null); // initialize
127     return ks;
128   }
129 
130   private static void saveKeyStore(KeyStore ks, String filename,
131                                    String password)
132     throws GeneralSecurityException, IOException {
133     FileOutputStream out = new FileOutputStream(filename);
134     try {
135       ks.store(out, password.toCharArray());
136     } finally {
137       out.close();
138     }
139   }
140 
141   public static void createKeyStore(String filename,
142                                     String password, String alias,
143                                     Key privateKey, Certificate cert)
144     throws GeneralSecurityException, IOException {
145     KeyStore ks = createEmptyKeyStore();
146     ks.setKeyEntry(alias, privateKey, password.toCharArray(),
147                    new Certificate[]{cert});
148     saveKeyStore(ks, filename, password);
149   }
150 
151   /**
152    * Creates a keystore with a single key and saves it to a file.
153    * 
154    * @param filename String file to save
155    * @param password String store password to set on keystore
156    * @param keyPassword String key password to set on key
157    * @param alias String alias to use for the key
158    * @param privateKey Key to save in keystore
159    * @param cert Certificate to use as certificate chain associated to key
160    * @throws GeneralSecurityException for any error with the security APIs
161    * @throws IOException if there is an I/O error saving the file
162    */
163   public static void createKeyStore(String filename,
164                                     String password, String keyPassword, String alias,
165                                     Key privateKey, Certificate cert)
166     throws GeneralSecurityException, IOException {
167     KeyStore ks = createEmptyKeyStore();
168     ks.setKeyEntry(alias, privateKey, keyPassword.toCharArray(),
169                    new Certificate[]{cert});
170     saveKeyStore(ks, filename, password);
171   }
172 
173   public static void createTrustStore(String filename,
174                                       String password, String alias,
175                                       Certificate cert)
176     throws GeneralSecurityException, IOException {
177     KeyStore ks = createEmptyKeyStore();
178     ks.setCertificateEntry(alias, cert);
179     saveKeyStore(ks, filename, password);
180   }
181 
182   public static <T extends Certificate> void createTrustStore(
183     String filename, String password, Map<String, T> certs)
184     throws GeneralSecurityException, IOException {
185     KeyStore ks = createEmptyKeyStore();
186     for (Map.Entry<String, T> cert : certs.entrySet()) {
187       ks.setCertificateEntry(cert.getKey(), cert.getValue());
188     }
189     saveKeyStore(ks, filename, password);
190   }
191 
192   public static void cleanupSSLConfig(String keystoresDir, String sslConfDir)
193     throws Exception {
194     File f = new File(keystoresDir + "/clientKS.jks");
195     f.delete();
196     f = new File(keystoresDir + "/serverKS.jks");
197     f.delete();
198     f = new File(keystoresDir + "/trustKS.jks");
199     f.delete();
200     f = new File(sslConfDir + "/ssl-client.xml");
201     f.delete();
202     f = new File(sslConfDir +  "/ssl-server.xml");
203     f.delete();
204   }
205 
206   /**
207    * Performs complete setup of SSL configuration in preparation for testing an
208    * SSLFactory.  This includes keys, certs, keystores, truststores, the server
209    * SSL configuration file, the client SSL configuration file, and the master
210    * configuration file read by the SSLFactory.
211    * 
212    * @param keystoresDir String directory to save keystores
213    * @param sslConfDir String directory to save SSL configuration files
214    * @param conf Configuration master configuration to be used by an SSLFactory,
215    *   which will be mutated by this method
216    * @param useClientCert boolean true to make the client present a cert in the
217    *   SSL handshake
218    */
219   public static void setupSSLConfig(String keystoresDir, String sslConfDir,
220                                     Configuration conf, boolean useClientCert)
221     throws Exception {
222     String clientKS = keystoresDir + "/clientKS.jks";
223     String clientPassword = "clientP";
224     String serverKS = keystoresDir + "/serverKS.jks";
225     String serverPassword = "serverP";
226     String trustKS = keystoresDir + "/trustKS.jks";
227     String trustPassword = "trustP";
228 
229     File sslClientConfFile = new File(sslConfDir + "/ssl-client.xml");
230     File sslServerConfFile = new File(sslConfDir + "/ssl-server.xml");
231 
232     Map<String, X509Certificate> certs = new HashMap<String, X509Certificate>();
233 
234     if (useClientCert) {
235       KeyPair cKP = KeyStoreTestUtil.generateKeyPair("RSA");
236       X509Certificate cCert =
237         KeyStoreTestUtil.generateCertificate("CN=localhost, O=client", cKP, 30,
238                                              "SHA1withRSA");
239       KeyStoreTestUtil.createKeyStore(clientKS, clientPassword, "client",
240                                       cKP.getPrivate(), cCert);
241       certs.put("client", cCert);
242     }
243 
244     KeyPair sKP = KeyStoreTestUtil.generateKeyPair("RSA");
245     X509Certificate sCert =
246       KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", sKP, 30,
247                                            "SHA1withRSA");
248     KeyStoreTestUtil.createKeyStore(serverKS, serverPassword, "server",
249                                     sKP.getPrivate(), sCert);
250     certs.put("server", sCert);
251 
252     KeyStoreTestUtil.createTrustStore(trustKS, trustPassword, certs);
253 
254     Configuration clientSSLConf = createClientSSLConfig(clientKS, clientPassword,
255       clientPassword, trustKS);
256     Configuration serverSSLConf = createServerSSLConfig(serverKS, serverPassword,
257       serverPassword, trustKS);
258 
259     saveConfig(sslClientConfFile, clientSSLConf);
260     saveConfig(sslServerConfFile, serverSSLConf);
261 
262     conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL");
263     conf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getName());
264     conf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getName());
265     conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert);
266   }
267 
268   /**
269    * Creates SSL configuration for a client.
270    * 
271    * @param clientKS String client keystore file
272    * @param password String store password, or null to avoid setting store
273    *   password
274    * @param keyPassword String key password, or null to avoid setting key
275    *   password
276    * @param trustKS String truststore file
277    * @return Configuration for client SSL
278    */
279   public static Configuration createClientSSLConfig(String clientKS,
280       String password, String keyPassword, String trustKS) {
281     Configuration clientSSLConf = createSSLConfig(SSLFactory.Mode.CLIENT,
282       clientKS, password, keyPassword, trustKS);
283     return clientSSLConf;
284   }
285 
286   /**
287    * Creates SSL configuration for a server.
288    * 
289    * @param serverKS String server keystore file
290    * @param password String store password, or null to avoid setting store
291    *   password
292    * @param keyPassword String key password, or null to avoid setting key
293    *   password
294    * @param trustKS String truststore file
295    * @return Configuration for server SSL
296    */
297   public static Configuration createServerSSLConfig(String serverKS,
298       String password, String keyPassword, String trustKS) throws IOException {
299     Configuration serverSSLConf = createSSLConfig(SSLFactory.Mode.SERVER,
300       serverKS, password, keyPassword, trustKS);
301     return serverSSLConf;
302   }
303 
304   /**
305    * Creates SSL configuration.
306    * 
307    * @param mode SSLFactory.Mode mode to configure
308    * @param keystore String keystore file
309    * @param password String store password, or null to avoid setting store
310    *   password
311    * @param keyPassword String key password, or null to avoid setting key
312    *   password
313    * @param trustKS String truststore file
314    * @return Configuration for SSL
315    */
316   private static Configuration createSSLConfig(SSLFactory.Mode mode,
317       String keystore, String password, String keyPassword, String trustKS) {
318     String trustPassword = "trustP";
319 
320     Configuration sslConf = new Configuration(false);
321     if (keystore != null) {
322       sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
323         FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), keystore);
324     }
325     if (password != null) {
326       sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
327         FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), password);
328     }
329     if (keyPassword != null) {
330       sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
331         FileBasedKeyStoresFactory.SSL_KEYSTORE_KEYPASSWORD_TPL_KEY),
332         keyPassword);
333     }
334     if (trustKS != null) {
335       sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
336         FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS);
337     }
338     if (trustPassword != null) {
339       sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
340         FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY),
341         trustPassword);
342     }
343     sslConf.set(FileBasedKeyStoresFactory.resolvePropertyName(mode,
344       FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000");
345 
346     return sslConf;
347   }
348 
349   /**
350    * Saves configuration to a file.
351    * 
352    * @param file File to save
353    * @param conf Configuration contents to write to file
354    * @throws IOException if there is an I/O error saving the file
355    */
356   public static void saveConfig(File file, Configuration conf)
357       throws IOException {
358     Writer writer = new FileWriter(file);
359     try {
360       conf.writeXml(writer);
361     } finally {
362       writer.close();
363     }
364   }
365 }