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 package org.apache.hadoop.security.ssl;
19 
20 import com.google.common.annotations.VisibleForTesting;
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.apache.hadoop.classification.InterfaceAudience;
24 import org.apache.hadoop.classification.InterfaceStability;
25 import org.apache.hadoop.conf.Configuration;
26 import org.apache.hadoop.util.StringUtils;
27 
28 import javax.net.ssl.KeyManager;
29 import javax.net.ssl.KeyManagerFactory;
30 import javax.net.ssl.TrustManager;
31 import java.io.FileInputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.security.GeneralSecurityException;
35 import java.security.KeyStore;
36 import java.text.MessageFormat;
37 
38 /**
39  * {@link KeyStoresFactory} implementation that reads the certificates from
40  * keystore files.
41  * <p/>
42  * if the trust certificates keystore file changes, the {@link TrustManager}
43  * is refreshed with the new trust certificate entries (using a
44  * {@link ReloadingX509TrustManager} trustmanager).
45  */
46 @InterfaceAudience.Private
47 @InterfaceStability.Evolving
48 public class FileBasedKeyStoresFactory implements KeyStoresFactory {
49 
50   private static final Log LOG =
51     LogFactory.getLog(FileBasedKeyStoresFactory.class);
52 
53   public static final String SSL_KEYSTORE_LOCATION_TPL_KEY =
54     "ssl.{0}.keystore.location";
55   public static final String SSL_KEYSTORE_PASSWORD_TPL_KEY =
56     "ssl.{0}.keystore.password";
57   public static final String SSL_KEYSTORE_KEYPASSWORD_TPL_KEY =
58     "ssl.{0}.keystore.keypassword";
59   public static final String SSL_KEYSTORE_TYPE_TPL_KEY =
60     "ssl.{0}.keystore.type";
61 
62   public static final String SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY =
63     "ssl.{0}.truststore.reload.interval";
64   public static final String SSL_TRUSTSTORE_LOCATION_TPL_KEY =
65     "ssl.{0}.truststore.location";
66   public static final String SSL_TRUSTSTORE_PASSWORD_TPL_KEY =
67     "ssl.{0}.truststore.password";
68   public static final String SSL_TRUSTSTORE_TYPE_TPL_KEY =
69     "ssl.{0}.truststore.type";
70 
71   /**
72    * Default format of the keystore files.
73    */
74   public static final String DEFAULT_KEYSTORE_TYPE = "jks";
75 
76   /**
77    * Reload interval in milliseconds.
78    */
79   public static final int DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL = 10000;
80 
81   private Configuration conf;
82   private KeyManager[] keyManagers;
83   private TrustManager[] trustManagers;
84   private ReloadingX509TrustManager trustManager;
85 
86   /**
87    * Resolves a property name to its client/server version if applicable.
88    * <p/>
89    * NOTE: This method is public for testing purposes.
90    *
91    * @param mode client/server mode.
92    * @param template property name template.
93    * @return the resolved property name.
94    */
95   @VisibleForTesting
resolvePropertyName(SSLFactory.Mode mode, String template)96   public static String resolvePropertyName(SSLFactory.Mode mode,
97                                            String template) {
98     return MessageFormat.format(
99         template, StringUtils.toLowerCase(mode.toString()));
100   }
101 
102   /**
103    * Sets the configuration for the factory.
104    *
105    * @param conf the configuration for the factory.
106    */
107   @Override
setConf(Configuration conf)108   public void setConf(Configuration conf) {
109     this.conf = conf;
110   }
111 
112   /**
113    * Returns the configuration of the factory.
114    *
115    * @return the configuration of the factory.
116    */
117   @Override
getConf()118   public Configuration getConf() {
119     return conf;
120   }
121 
122   /**
123    * Initializes the keystores of the factory.
124    *
125    * @param mode if the keystores are to be used in client or server mode.
126    * @throws IOException thrown if the keystores could not be initialized due
127    * to an IO error.
128    * @throws GeneralSecurityException thrown if the keystores could not be
129    * initialized due to a security error.
130    */
131   @Override
init(SSLFactory.Mode mode)132   public void init(SSLFactory.Mode mode)
133     throws IOException, GeneralSecurityException {
134 
135     boolean requireClientCert =
136       conf.getBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY,
137           SSLFactory.DEFAULT_SSL_REQUIRE_CLIENT_CERT);
138 
139     // certificate store
140     String keystoreType =
141       conf.get(resolvePropertyName(mode, SSL_KEYSTORE_TYPE_TPL_KEY),
142                DEFAULT_KEYSTORE_TYPE);
143     KeyStore keystore = KeyStore.getInstance(keystoreType);
144     String keystoreKeyPassword = null;
145     if (requireClientCert || mode == SSLFactory.Mode.SERVER) {
146       String locationProperty =
147         resolvePropertyName(mode, SSL_KEYSTORE_LOCATION_TPL_KEY);
148       String keystoreLocation = conf.get(locationProperty, "");
149       if (keystoreLocation.isEmpty()) {
150         throw new GeneralSecurityException("The property '" + locationProperty +
151           "' has not been set in the ssl configuration file.");
152       }
153       String passwordProperty =
154         resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY);
155       String keystorePassword = getPassword(conf, passwordProperty, "");
156       if (keystorePassword.isEmpty()) {
157         throw new GeneralSecurityException("The property '" + passwordProperty +
158           "' has not been set in the ssl configuration file.");
159       }
160       String keyPasswordProperty =
161         resolvePropertyName(mode, SSL_KEYSTORE_KEYPASSWORD_TPL_KEY);
162       // Key password defaults to the same value as store password for
163       // compatibility with legacy configurations that did not use a separate
164       // configuration property for key password.
165       keystoreKeyPassword = getPassword(
166           conf, keyPasswordProperty, keystorePassword);
167       LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation);
168 
169       InputStream is = new FileInputStream(keystoreLocation);
170       try {
171         keystore.load(is, keystorePassword.toCharArray());
172       } finally {
173         is.close();
174       }
175       LOG.debug(mode.toString() + " Loaded KeyStore: " + keystoreLocation);
176     } else {
177       keystore.load(null, null);
178     }
179     KeyManagerFactory keyMgrFactory = KeyManagerFactory
180         .getInstance(SSLFactory.SSLCERTIFICATE);
181 
182     keyMgrFactory.init(keystore, (keystoreKeyPassword != null) ?
183                                  keystoreKeyPassword.toCharArray() : null);
184     keyManagers = keyMgrFactory.getKeyManagers();
185 
186     //trust store
187     String truststoreType =
188       conf.get(resolvePropertyName(mode, SSL_TRUSTSTORE_TYPE_TPL_KEY),
189                DEFAULT_KEYSTORE_TYPE);
190 
191     String locationProperty =
192       resolvePropertyName(mode, SSL_TRUSTSTORE_LOCATION_TPL_KEY);
193     String truststoreLocation = conf.get(locationProperty, "");
194     if (!truststoreLocation.isEmpty()) {
195       String passwordProperty = resolvePropertyName(mode,
196           SSL_TRUSTSTORE_PASSWORD_TPL_KEY);
197       String truststorePassword = getPassword(conf, passwordProperty, "");
198       if (truststorePassword.isEmpty()) {
199         throw new GeneralSecurityException("The property '" + passwordProperty +
200             "' has not been set in the ssl configuration file.");
201       }
202       long truststoreReloadInterval =
203           conf.getLong(
204               resolvePropertyName(mode, SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY),
205               DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL);
206 
207       LOG.debug(mode.toString() + " TrustStore: " + truststoreLocation);
208 
209       trustManager = new ReloadingX509TrustManager(truststoreType,
210           truststoreLocation,
211           truststorePassword,
212           truststoreReloadInterval);
213       trustManager.init();
214       LOG.debug(mode.toString() + " Loaded TrustStore: " + truststoreLocation);
215       trustManagers = new TrustManager[]{trustManager};
216     } else {
217       LOG.debug("The property '" + locationProperty + "' has not been set, " +
218           "no TrustStore will be loaded");
219       trustManagers = null;
220     }
221   }
222 
getPassword(Configuration conf, String alias, String defaultPass)223   String getPassword(Configuration conf, String alias, String defaultPass) {
224     String password = defaultPass;
225     try {
226       char[] passchars = conf.getPassword(alias);
227       if (passchars != null) {
228         password = new String(passchars);
229       }
230     }
231     catch (IOException ioe) {
232       LOG.warn("Exception while trying to get password for alias " + alias +
233           ": " + ioe.getMessage());
234     }
235     return password;
236   }
237 
238   /**
239    * Releases any resources being used.
240    */
241   @Override
destroy()242   public synchronized void destroy() {
243     if (trustManager != null) {
244       trustManager.destroy();
245       trustManager = null;
246       keyManagers = null;
247       trustManagers = null;
248     }
249   }
250 
251   /**
252    * Returns the keymanagers for owned certificates.
253    *
254    * @return the keymanagers for owned certificates.
255    */
256   @Override
getKeyManagers()257   public KeyManager[] getKeyManagers() {
258     return keyManagers;
259   }
260 
261   /**
262    * Returns the trustmanagers for trusted certificates.
263    *
264    * @return the trustmanagers for trusted certificates.
265    */
266   @Override
getTrustManagers()267   public TrustManager[] getTrustManagers() {
268     return trustManagers;
269   }
270 
271 }
272