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}