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}