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