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