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.opends.server.admin.server.ConfigurationChangeListener; 041import org.opends.server.admin.std.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 /** Performs any finalization that may be necessary for this key manager provider. */ 099 @Override 100 public void finalizeKeyManagerProvider() 101 { 102 currentConfig.removeFileBasedChangeListener(this); 103 } 104 105 @Override 106 public boolean containsKeyWithAlias(String alias) { 107 try { 108 KeyStore keyStore = getKeystore(); 109 Enumeration<String> aliases = keyStore.aliases(); 110 while (aliases.hasMoreElements()) { 111 String theAlias = aliases.nextElement(); 112 if (alias.equals(theAlias) && keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { 113 return true; 114 } 115 } 116 } 117 catch (DirectoryException | KeyStoreException e) { 118 } 119 120 return false; 121 } 122 123 private KeyStore getKeystore() throws DirectoryException 124 { 125 try 126 { 127 KeyStore keyStore = KeyStore.getInstance(keyStoreType); 128 129 try (FileInputStream inputStream = new FileInputStream(getFileForPath(keyStoreFile))) 130 { 131 keyStore.load(inputStream, keyStorePIN); 132 } 133 return keyStore; 134 } 135 catch (Exception e) 136 { 137 logger.traceException(e); 138 139 LocalizableMessage message = ERR_FILE_KEYMANAGER_CANNOT_LOAD.get( 140 keyStoreFile, getExceptionMessage(e)); 141 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 142 } 143 } 144 145 @Override 146 public KeyManager[] getKeyManagers() throws DirectoryException 147 { 148 KeyStore keyStore = getKeystore(); 149 150 try 151 { 152 if (! findOneKeyEntry(keyStore)) 153 { 154 // Troubleshooting message to let now of possible config error 155 logger.error(ERR_NO_KEY_ENTRY_IN_KEYSTORE, keyStoreFile); 156 } 157 158 String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); 159 KeyManagerFactory keyManagerFactory = 160 KeyManagerFactory.getInstance(keyManagerAlgorithm); 161 keyManagerFactory.init(keyStore, keyStorePIN); 162 return keyManagerFactory.getKeyManagers(); 163 } 164 catch (Exception e) 165 { 166 logger.traceException(e); 167 168 LocalizableMessage message = ERR_FILE_KEYMANAGER_CANNOT_CREATE_FACTORY.get( 169 keyStoreFile, getExceptionMessage(e)); 170 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 171 } 172 } 173 174 @Override 175 public boolean containsAtLeastOneKey() 176 { 177 try 178 { 179 return findOneKeyEntry(getKeystore()); 180 } 181 catch (Exception e) { 182 logger.traceException(e); 183 return false; 184 } 185 } 186 187 private boolean findOneKeyEntry(KeyStore keyStore) throws KeyStoreException 188 { 189 Enumeration<String> aliases = keyStore.aliases(); 190 while (aliases.hasMoreElements()) 191 { 192 String alias = aliases.nextElement(); 193 if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) 194 { 195 return true; 196 } 197 } 198 return false; 199 } 200 201 @Override 202 public boolean isConfigurationAcceptable( 203 FileBasedKeyManagerProviderCfg configuration, 204 List<LocalizableMessage> unacceptableReasons) 205 { 206 return isConfigurationChangeAcceptable(configuration, unacceptableReasons); 207 } 208 209 @Override 210 public boolean isConfigurationChangeAcceptable( 211 FileBasedKeyManagerProviderCfg configuration, 212 List<LocalizableMessage> unacceptableReasons) 213 { 214 int startSize = unacceptableReasons.size(); 215 DN cfgEntryDN = configuration.dn(); 216 217 final ConfigChangeResult ccr = new ConfigChangeResult(); 218 getKeyStoreFile(configuration, cfgEntryDN, ccr); 219 getKeyStoreType(configuration, cfgEntryDN, ccr); 220 getKeyStorePIN(configuration, cfgEntryDN, ccr); 221 unacceptableReasons.addAll(ccr.getMessages()); 222 223 return startSize == unacceptableReasons.size(); 224 } 225 226 @Override 227 public ConfigChangeResult applyConfigurationChange( 228 FileBasedKeyManagerProviderCfg configuration) 229 { 230 final ConfigChangeResult ccr = new ConfigChangeResult(); 231 String newKeyStoreFile = getKeyStoreFile(configuration, configEntryDN, ccr); 232 String newKeyStoreType = getKeyStoreType(configuration, configEntryDN, ccr); 233 char[] newPIN = getKeyStorePIN(configuration, configEntryDN, ccr); 234 235 if (ccr.getResultCode() == ResultCode.SUCCESS) 236 { 237 currentConfig = configuration; 238 keyStorePIN = newPIN; 239 keyStoreFile = newKeyStoreFile; 240 keyStoreType = newKeyStoreType; 241 } 242 243 return ccr; 244 } 245 246 /** Get the path to the key store file. */ 247 private String getKeyStoreFile(FileBasedKeyManagerProviderCfg configuration, DN cfgEntryDN, 248 final ConfigChangeResult ccr) 249 { 250 String keyStoreFile = configuration.getKeyStoreFile(); 251 try 252 { 253 File f = getFileForPath(keyStoreFile); 254 if (!f.exists() || !f.isFile()) 255 { 256 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 257 ccr.addMessage(ERR_FILE_KEYMANAGER_NO_SUCH_FILE.get(keyStoreFile, cfgEntryDN)); 258 } 259 } 260 catch (Exception e) 261 { 262 logger.traceException(e); 263 264 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 265 ccr.addMessage(ERR_FILE_KEYMANAGER_CANNOT_DETERMINE_FILE.get(cfgEntryDN, getExceptionMessage(e))); 266 } 267 return keyStoreFile; 268 } 269 270 /** Get the keystore type. If none is specified, then use the default type. */ 271 private String getKeyStoreType(FileBasedKeyManagerProviderCfg configuration, DN cfgEntryDN, 272 final ConfigChangeResult ccr) 273 { 274 if (configuration.getKeyStoreType() != null) 275 { 276 try 277 { 278 KeyStore.getInstance(configuration.getKeyStoreType()); 279 return configuration.getKeyStoreType(); 280 } 281 catch (KeyStoreException kse) 282 { 283 logger.traceException(kse); 284 285 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 286 ccr.addMessage(ERR_FILE_KEYMANAGER_INVALID_TYPE.get( 287 configuration.getKeyStoreType(), cfgEntryDN, getExceptionMessage(kse))); 288 } 289 } 290 return KeyStore.getDefaultType(); 291 } 292 293 /** 294 * Get the PIN needed to access the contents of the keystore file. 295 * <p> 296 * We will offer several places to look for the PIN, and we will do so in the following order: 297 * <ol> 298 * <li>In a specified Java property</li> 299 * <li>In a specified environment variable</li> 300 * <li>In a specified file on the server filesystem</li> 301 * <li>As the value of a configuration attribute.</li> 302 * <ol> 303 * In any case, the PIN must be in the clear. 304 * <p> 305 * It is acceptable to have no PIN (OPENDJ-18) 306 */ 307 private char[] getKeyStorePIN(FileBasedKeyManagerProviderCfg configuration, DN cfgEntryDN, 308 final ConfigChangeResult ccr) 309 { 310 if (configuration.getKeyStorePinProperty() != null) 311 { 312 String propertyName = configuration.getKeyStorePinProperty(); 313 String pinStr = System.getProperty(propertyName); 314 315 if (pinStr == null) 316 { 317 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 318 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_PROPERTY_NOT_SET.get(propertyName, cfgEntryDN)); 319 } 320 else 321 { 322 return pinStr.toCharArray(); 323 } 324 } 325 else if (configuration.getKeyStorePinEnvironmentVariable() != null) 326 { 327 String enVarName = configuration.getKeyStorePinEnvironmentVariable(); 328 String pinStr = System.getenv(enVarName); 329 330 if (pinStr == null) 331 { 332 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 333 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_ENVAR_NOT_SET.get(enVarName, cfgEntryDN)); 334 } 335 else 336 { 337 return pinStr.toCharArray(); 338 } 339 } 340 else if (configuration.getKeyStorePinFile() != null) 341 { 342 String fileName = configuration.getKeyStorePinFile(); 343 File pinFile = getFileForPath(fileName); 344 345 if (!pinFile.exists()) 346 { 347 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 348 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_NO_SUCH_FILE.get(fileName, cfgEntryDN)); 349 } 350 else 351 { 352 String pinStr = readPinFromFile(pinFile, fileName, ccr); 353 if (pinStr == null) 354 { 355 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 356 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_FILE_EMPTY.get(fileName, cfgEntryDN)); 357 } 358 else 359 { 360 return pinStr.toCharArray(); 361 } 362 } 363 } 364 else if (configuration.getKeyStorePin() != null) 365 { 366 return configuration.getKeyStorePin().toCharArray(); 367 } 368 return null; 369 } 370 371 private String readPinFromFile(File pinFile, String fileName, ConfigChangeResult ccr) 372 { 373 try (BufferedReader br = new BufferedReader(new FileReader(pinFile))) 374 { 375 return br.readLine(); 376 } 377 catch (IOException ioe) 378 { 379 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 380 ccr.addMessage(ERR_FILE_KEYMANAGER_PIN_FILE_CANNOT_READ.get(fileName, configEntryDN, getExceptionMessage(ioe))); 381 return null; 382 } 383 } 384}