001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.extensions; 028 029import static org.opends.messages.ExtensionMessages.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import java.io.BufferedReader; 033import java.io.File; 034import java.io.FileInputStream; 035import java.io.FileReader; 036import java.io.IOException; 037import java.security.KeyStore; 038import java.security.KeyStoreException; 039import java.util.Enumeration; 040import java.util.List; 041 042import javax.net.ssl.KeyManager; 043import javax.net.ssl.KeyManagerFactory; 044 045import org.forgerock.i18n.LocalizableMessage; 046import org.forgerock.i18n.slf4j.LocalizedLogger; 047import org.forgerock.opendj.config.server.ConfigChangeResult; 048import org.forgerock.opendj.config.server.ConfigException; 049import org.forgerock.opendj.ldap.ResultCode; 050import org.opends.server.admin.server.ConfigurationChangeListener; 051import org.opends.server.admin.std.server.FileBasedKeyManagerProviderCfg; 052import org.opends.server.api.KeyManagerProvider; 053import org.opends.server.core.DirectoryServer; 054import org.opends.server.types.DN; 055import org.opends.server.types.DirectoryException; 056import org.opends.server.types.InitializationException; 057 058/** 059 * This class defines a key manager provider that will access keys stored in a 060 * file located on the Directory Server filesystem. 061 */ 062public class FileBasedKeyManagerProvider 063 extends KeyManagerProvider<FileBasedKeyManagerProviderCfg> 064 implements ConfigurationChangeListener<FileBasedKeyManagerProviderCfg> 065{ 066 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 067 068 /** The DN of the configuration entry for this key manager provider. */ 069 private DN configEntryDN; 070 /** The configuration for this key manager provider. */ 071 private FileBasedKeyManagerProviderCfg currentConfig; 072 073 /** The PIN needed to access the keystore. */ 074 private char[] keyStorePIN; 075 /** The path to the key store backing file. */ 076 private String keyStoreFile; 077 /** The key store type to use. */ 078 private String keyStoreType; 079 080 /** 081 * Creates a new instance of this file-based key manager provider. The 082 * <CODE>initializeKeyManagerProvider</CODE> method must be called on the 083 * resulting object before it may be used. 084 */ 085 public FileBasedKeyManagerProvider() 086 { 087 // No implementation is required. 088 } 089 090 @Override 091 public void initializeKeyManagerProvider( 092 FileBasedKeyManagerProviderCfg configuration) 093 throws ConfigException, InitializationException { 094 // Store the DN of the configuration entry and register as a change listener 095 currentConfig = configuration; 096 configEntryDN = configuration.dn(); 097 configuration.addFileBasedChangeListener(this); 098 099 final ConfigChangeResult ccr = new ConfigChangeResult(); 100 keyStoreFile = getKeyStoreFile(configuration, configEntryDN, ccr); 101 keyStoreType = getKeyStoreType(configuration, configEntryDN, ccr); 102 keyStorePIN = getKeyStorePIN(configuration, configEntryDN, ccr); 103 if (!ccr.getMessages().isEmpty()) { 104 throw new InitializationException(ccr.getMessages().get(0)); 105 } 106 } 107 108 /** Performs any finalization that may be necessary for this key manager provider. */ 109 @Override 110 public void finalizeKeyManagerProvider() 111 { 112 currentConfig.removeFileBasedChangeListener(this); 113 } 114 115 @Override 116 public boolean containsKeyWithAlias(String alias) { 117 try { 118 KeyStore keyStore = getKeystore(); 119 Enumeration<String> aliases = keyStore.aliases(); 120 while (aliases.hasMoreElements()) { 121 String theAlias = aliases.nextElement(); 122 if (alias.equals(theAlias) && keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { 123 return true; 124 } 125 } 126 } 127 catch (DirectoryException | KeyStoreException e) { 128 } 129 130 return false; 131 } 132 133 private KeyStore getKeystore() throws DirectoryException 134 { 135 try 136 { 137 KeyStore keyStore = KeyStore.getInstance(keyStoreType); 138 139 try (FileInputStream inputStream = new FileInputStream(getFileForPath(keyStoreFile))) 140 { 141 keyStore.load(inputStream, keyStorePIN); 142 } 143 return keyStore; 144 } 145 catch (Exception e) 146 { 147 logger.traceException(e); 148 149 LocalizableMessage message = ERR_FILE_KEYMANAGER_CANNOT_LOAD.get( 150 keyStoreFile, getExceptionMessage(e)); 151 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 152 } 153 } 154 155 @Override 156 public KeyManager[] getKeyManagers() throws DirectoryException 157 { 158 KeyStore keyStore = getKeystore(); 159 160 try 161 { 162 if (! findOneKeyEntry(keyStore)) 163 { 164 // Troubleshooting message to let now of possible config error 165 logger.error(ERR_NO_KEY_ENTRY_IN_KEYSTORE, keyStoreFile); 166 } 167 168 String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); 169 KeyManagerFactory keyManagerFactory = 170 KeyManagerFactory.getInstance(keyManagerAlgorithm); 171 keyManagerFactory.init(keyStore, keyStorePIN); 172 return keyManagerFactory.getKeyManagers(); 173 } 174 catch (Exception e) 175 { 176 logger.traceException(e); 177 178 LocalizableMessage message = ERR_FILE_KEYMANAGER_CANNOT_CREATE_FACTORY.get( 179 keyStoreFile, getExceptionMessage(e)); 180 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 181 } 182 } 183 184 @Override 185 public boolean containsAtLeastOneKey() 186 { 187 try 188 { 189 return findOneKeyEntry(getKeystore()); 190 } 191 catch (Exception e) { 192 logger.traceException(e); 193 return false; 194 } 195 } 196 197 private boolean findOneKeyEntry(KeyStore keyStore) throws KeyStoreException 198 { 199 Enumeration<String> aliases = keyStore.aliases(); 200 while (aliases.hasMoreElements()) 201 { 202 String alias = aliases.nextElement(); 203 if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) 204 { 205 return true; 206 } 207 } 208 return false; 209 } 210 211 @Override 212 public boolean isConfigurationAcceptable( 213 FileBasedKeyManagerProviderCfg configuration, 214 List<LocalizableMessage> unacceptableReasons) 215 { 216 return isConfigurationChangeAcceptable(configuration, unacceptableReasons); 217 } 218 219 @Override 220 public boolean isConfigurationChangeAcceptable( 221 FileBasedKeyManagerProviderCfg configuration, 222 List<LocalizableMessage> unacceptableReasons) 223 { 224 int startSize = unacceptableReasons.size(); 225 DN cfgEntryDN = configuration.dn(); 226 227 final ConfigChangeResult ccr = new ConfigChangeResult(); 228 getKeyStoreFile(configuration, cfgEntryDN, ccr); 229 getKeyStoreType(configuration, cfgEntryDN, ccr); 230 getKeyStorePIN(configuration, cfgEntryDN, ccr); 231 unacceptableReasons.addAll(ccr.getMessages()); 232 233 return startSize == unacceptableReasons.size(); 234 } 235 236 @Override 237 public ConfigChangeResult applyConfigurationChange( 238 FileBasedKeyManagerProviderCfg configuration) 239 { 240 final ConfigChangeResult ccr = new ConfigChangeResult(); 241 String newKeyStoreFile = getKeyStoreFile(configuration, configEntryDN, ccr); 242 String newKeyStoreType = getKeyStoreType(configuration, configEntryDN, ccr); 243 char[] newPIN = getKeyStorePIN(configuration, configEntryDN, ccr); 244 245 if (ccr.getResultCode() == ResultCode.SUCCESS) 246 { 247 currentConfig = configuration; 248 keyStorePIN = newPIN; 249 keyStoreFile = newKeyStoreFile; 250 keyStoreType = newKeyStoreType; 251 } 252 253 return ccr; 254 } 255 256 /** Get the path to the key store file. */ 257 private String getKeyStoreFile(FileBasedKeyManagerProviderCfg configuration, DN cfgEntryDN, 258 final ConfigChangeResult ccr) 259 { 260 String keyStoreFile = configuration.getKeyStoreFile(); 261 try 262 { 263 File f = getFileForPath(keyStoreFile); 264 if (!f.exists() || !f.isFile()) 265 { 266 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 267 ccr.addMessage(ERR_FILE_KEYMANAGER_NO_SUCH_FILE.get(keyStoreFile, cfgEntryDN)); 268 } 269 } 270 catch (Exception e) 271 { 272 logger.traceException(e); 273 274 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 275 ccr.addMessage(ERR_FILE_KEYMANAGER_CANNOT_DETERMINE_FILE.get(cfgEntryDN, getExceptionMessage(e))); 276 } 277 return keyStoreFile; 278 } 279 280 /** Get the keystore type. If none is specified, then use the default type. */ 281 private String getKeyStoreType(FileBasedKeyManagerProviderCfg configuration, DN cfgEntryDN, 282 final ConfigChangeResult ccr) 283 { 284 if (configuration.getKeyStoreType() != null) 285 { 286 try 287 { 288 KeyStore.getInstance(configuration.getKeyStoreType()); 289 return configuration.getKeyStoreType(); 290 } 291 catch (KeyStoreException kse) 292 { 293 logger.traceException(kse); 294 295 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 296 ccr.addMessage(ERR_FILE_KEYMANAGER_INVALID_TYPE.get( 297 configuration.getKeyStoreType(), cfgEntryDN, getExceptionMessage(kse))); 298 } 299 } 300 return KeyStore.getDefaultType(); 301 } 302 303 /** 304 * Get the PIN needed to access the contents of the keystore file. 305 * <p> 306 * We will offer several places to look for the PIN, and we will do so in the following order: 307 * <ol> 308 * <li>In a specified Java property</li> 309 * <li>In a specified environment variable</li> 310 * <li>In a specified file on the server filesystem</li> 311 * <li>As the value of a configuration attribute.</li> 312 * <ol> 313 * In any case, the PIN must be in the clear. 314 * <p> 315 * It is acceptable to have no PIN (OPENDJ-18) 316 */ 317 private char[] getKeyStorePIN(FileBasedKeyManagerProviderCfg configuration, DN cfgEntryDN, 318 final ConfigChangeResult ccr) 319 { 320 if (configuration.getKeyStorePinProperty() != null) 321 { 322 String propertyName = configuration.getKeyStorePinProperty(); 323 String pinStr = System.getProperty(propertyName); 324 325 if (pinStr == null) 326 { 327 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 328 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_PROPERTY_NOT_SET.get(propertyName, cfgEntryDN)); 329 } 330 else 331 { 332 return pinStr.toCharArray(); 333 } 334 } 335 else if (configuration.getKeyStorePinEnvironmentVariable() != null) 336 { 337 String enVarName = configuration.getKeyStorePinEnvironmentVariable(); 338 String pinStr = System.getenv(enVarName); 339 340 if (pinStr == null) 341 { 342 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 343 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_ENVAR_NOT_SET.get(enVarName, cfgEntryDN)); 344 } 345 else 346 { 347 return pinStr.toCharArray(); 348 } 349 } 350 else if (configuration.getKeyStorePinFile() != null) 351 { 352 String fileName = configuration.getKeyStorePinFile(); 353 File pinFile = getFileForPath(fileName); 354 355 if (!pinFile.exists()) 356 { 357 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 358 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_NO_SUCH_FILE.get(fileName, cfgEntryDN)); 359 } 360 else 361 { 362 String pinStr = readPinFromFile(pinFile, fileName, ccr); 363 if (pinStr == null) 364 { 365 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 366 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_FILE_EMPTY.get(fileName, cfgEntryDN)); 367 } 368 else 369 { 370 return pinStr.toCharArray(); 371 } 372 } 373 } 374 else if (configuration.getKeyStorePin() != null) 375 { 376 return configuration.getKeyStorePin().toCharArray(); 377 } 378 return null; 379 } 380 381 private String readPinFromFile(File pinFile, String fileName, ConfigChangeResult ccr) 382 { 383 try (BufferedReader br = new BufferedReader(new FileReader(pinFile))) 384 { 385 return br.readLine(); 386 } 387 catch (IOException ioe) 388 { 389 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 390 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_FILE_CANNOT_READ.get(fileName, configEntryDN, getExceptionMessage(ioe))); 391 return null; 392 } 393 } 394}