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;
028import org.forgerock.i18n.LocalizableMessage;
029
030
031
032import java.io.BufferedReader;
033import java.io.File;
034import java.io.FileReader;
035import java.io.IOException;
036import java.security.KeyStore;
037import java.util.List;
038import javax.net.ssl.KeyManager;
039import javax.net.ssl.KeyManagerFactory;
040
041import org.opends.server.admin.server.ConfigurationChangeListener;
042import org.opends.server.admin.std.server.PKCS11KeyManagerProviderCfg;
043import org.opends.server.api.KeyManagerProvider;
044import org.forgerock.opendj.config.server.ConfigException;
045import org.opends.server.core.DirectoryServer;
046import org.forgerock.opendj.config.server.ConfigChangeResult;
047import org.opends.server.types.DirectoryException;
048import org.opends.server.types.DN;
049import org.opends.server.types.InitializationException;
050import org.forgerock.opendj.ldap.ResultCode;
051import org.opends.server.util.StaticUtils;
052
053import org.forgerock.i18n.slf4j.LocalizedLogger;
054import static org.opends.messages.ExtensionMessages.*;
055import static org.opends.server.util.StaticUtils.*;
056
057/**
058 * This class defines a key manager provider that will access keys stored on a
059 * PKCS#11 device.  It will use the Java PKCS#11 interface, which may need to be
060 * configured on the underlying system.
061 */
062public class PKCS11KeyManagerProvider
063    extends KeyManagerProvider<PKCS11KeyManagerProviderCfg>
064    implements ConfigurationChangeListener<PKCS11KeyManagerProviderCfg>
065{
066  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
067
068
069
070  /**
071   * The keystore type to use when accessing the PKCS#11 keystore.
072   */
073  public static final String PKCS11_KEYSTORE_TYPE = "PKCS11";
074
075
076
077  /** The DN of the configuration entry for this key manager provider. */
078  private DN configEntryDN;
079
080  /** The PIN needed to access the keystore. */
081  private char[] keyStorePIN;
082
083  /** The current configuration for this key manager provider. */
084  private PKCS11KeyManagerProviderCfg currentConfig;
085
086
087
088  /**
089   * Creates a new instance of this PKCS#11 key manager provider.  The
090   * <CODE>initializeKeyManagerProvider</CODE> method must be called on the
091   * resulting object before it may be used.
092   */
093  public PKCS11KeyManagerProvider()
094  {
095    // No implementation is required.
096  }
097
098
099
100  /** {@inheritDoc} */
101  @Override
102  public void initializeKeyManagerProvider(
103                    PKCS11KeyManagerProviderCfg configuration)
104         throws ConfigException, InitializationException
105  {
106    // Store the DN of the configuration entry and register to be notified of
107    // configuration changes.
108    currentConfig = configuration;
109    configEntryDN = configuration.dn();
110    configuration.addPKCS11ChangeListener(this);
111
112    // Get the PIN needed to access the contents of the PKCS#11
113    // keystore. We will offer several places to look for the PIN, and
114    // we will do so in the following order:
115    //
116    // - In a specified Java property
117    // - In a specified environment variable
118    // - In a specified file on the server filesystem.
119    // - As the value of a configuration attribute.
120    //
121    // In any case, the PIN must be in the clear.
122    keyStorePIN = null;
123
124    if (configuration.getKeyStorePinProperty() != null) {
125      String propertyName = configuration.getKeyStorePinProperty();
126      String pinStr = System.getProperty(propertyName);
127
128      if (pinStr == null) {
129        LocalizableMessage message = ERR_PKCS11_KEYMANAGER_PIN_PROPERTY_NOT_SET.get(
130            propertyName, configEntryDN);
131        throw new InitializationException(message);
132      }
133
134      keyStorePIN = pinStr.toCharArray();
135    } else if (configuration.getKeyStorePinEnvironmentVariable() != null) {
136      String enVarName = configuration
137          .getKeyStorePinEnvironmentVariable();
138      String pinStr = System.getenv(enVarName);
139
140      if (pinStr == null) {
141        LocalizableMessage message = ERR_PKCS11_KEYMANAGER_PIN_ENVAR_NOT_SET.get(
142            enVarName, configEntryDN);
143        throw new InitializationException(message);
144      }
145
146      keyStorePIN = pinStr.toCharArray();
147    } else if (configuration.getKeyStorePinFile() != null) {
148      String fileName = configuration.getKeyStorePinFile();
149      File pinFile = getFileForPath(fileName);
150
151      if (!pinFile.exists()) {
152        LocalizableMessage message = ERR_PKCS11_KEYMANAGER_PIN_NO_SUCH_FILE.get(fileName, configEntryDN);
153        throw new InitializationException(message);
154      }
155
156      String pinStr;
157      try {
158        BufferedReader br = new BufferedReader(
159            new FileReader(pinFile));
160        pinStr = br.readLine();
161        br.close();
162      } catch (IOException ioe) {
163        logger.traceException(ioe);
164
165        LocalizableMessage message = ERR_PKCS11_KEYMANAGER_PIN_FILE_CANNOT_READ.
166            get(fileName, configEntryDN, getExceptionMessage(ioe));
167        throw new InitializationException(message, ioe);
168      }
169
170      if (pinStr == null) {
171        LocalizableMessage message = ERR_PKCS11_KEYMANAGER_PIN_FILE_EMPTY.get(fileName, configEntryDN);
172        throw new InitializationException(message);
173      }
174
175      keyStorePIN = pinStr.toCharArray();
176    } else if (configuration.getKeyStorePin() != null) {
177      keyStorePIN = configuration.getKeyStorePin().toCharArray();
178    }
179  }
180
181
182
183  /**
184   * Performs any finalization that may be necessary for this key
185   * manager provider.
186   */
187  public void finalizeKeyManagerProvider()
188  {
189    currentConfig.removePKCS11ChangeListener(this);
190  }
191
192
193
194  /**
195   * Retrieves a set of <CODE>KeyManager</CODE> objects that may be used for
196   * interactions requiring access to a key manager.
197   *
198   * @return  A set of <CODE>KeyManager</CODE> objects that may be used for
199   *          interactions requiring access to a key manager.
200   *
201   * @throws  DirectoryException  If a problem occurs while attempting to obtain
202   *                              the set of key managers.
203   */
204  public KeyManager[] getKeyManagers()
205         throws DirectoryException
206  {
207    KeyStore keyStore;
208    try
209    {
210      keyStore = KeyStore.getInstance(PKCS11_KEYSTORE_TYPE);
211      keyStore.load(null, keyStorePIN);
212    }
213    catch (Exception e)
214    {
215      logger.traceException(e);
216
217      LocalizableMessage message =
218          ERR_PKCS11_KEYMANAGER_CANNOT_LOAD.get(getExceptionMessage(e));
219      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
220                                   message, e);
221    }
222
223
224    try
225    {
226      String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
227      KeyManagerFactory keyManagerFactory =
228           KeyManagerFactory.getInstance(keyManagerAlgorithm);
229      keyManagerFactory.init(keyStore, keyStorePIN);
230      return keyManagerFactory.getKeyManagers();
231    }
232    catch (Exception e)
233    {
234      logger.traceException(e);
235
236      LocalizableMessage message = ERR_PKCS11_KEYMANAGER_CANNOT_CREATE_FACTORY.get(
237          getExceptionMessage(e));
238      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
239                                   message, e);
240    }
241  }
242
243
244
245  /** {@inheritDoc} */
246  @Override
247  public boolean isConfigurationAcceptable(
248                        PKCS11KeyManagerProviderCfg configuration,
249                          List<LocalizableMessage> unacceptableReasons)
250  {
251    return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
252  }
253
254
255
256  /** {@inheritDoc} */
257  public boolean isConfigurationChangeAcceptable(
258                      PKCS11KeyManagerProviderCfg configuration,
259                      List<LocalizableMessage> unacceptableReasons)
260  {
261    boolean configAcceptable = true;
262    DN cfgEntryDN = configuration.dn();
263
264
265    // Get the PIN needed to access the contents of the keystore file.
266    //
267    // We will offer several places to look for the PIN, and we will
268    // do so in the following order:
269    //
270    // - In a specified Java property
271    // - In a specified environment variable
272    // - In a specified file on the server filesystem.
273    // - As the value of a configuration attribute.
274    //
275    // In any case, the PIN must be in the clear.
276    //
277    // It is acceptable to have no PIN (OPENDJ-18)
278    if (configuration.getKeyStorePinProperty() != null)
279    {
280      String propertyName = configuration.getKeyStorePinProperty();
281      String pinStr = System.getProperty(propertyName);
282
283      if (pinStr == null)
284      {
285        unacceptableReasons.add(ERR_PKCS11_KEYMANAGER_PIN_PROPERTY_NOT_SET.get(propertyName, cfgEntryDN));
286        configAcceptable = false;
287      }
288    }
289    else if (configuration.getKeyStorePinEnvironmentVariable() != null)
290    {
291      String enVarName = configuration.getKeyStorePinEnvironmentVariable();
292      String pinStr    = System.getenv(enVarName);
293
294      if (pinStr == null)
295      {
296        unacceptableReasons.add(ERR_PKCS11_KEYMANAGER_PIN_ENVAR_NOT_SET.get(enVarName, configEntryDN));
297        configAcceptable = false;
298      }
299    }
300    else if (configuration.getKeyStorePinFile() != null)
301    {
302      String fileName = configuration.getKeyStorePinFile();
303      File   pinFile  = getFileForPath(fileName);
304
305      if (!pinFile.exists())
306      {
307        unacceptableReasons.add(ERR_PKCS11_KEYMANAGER_PIN_NO_SUCH_FILE.get(fileName, configEntryDN));
308        configAcceptable = false;
309      }
310      else
311      {
312        String pinStr = null;
313        BufferedReader br = null;
314        try {
315          br = new BufferedReader(new FileReader(pinFile));
316          pinStr = br.readLine();
317        }
318        catch (IOException ioe)
319        {
320          unacceptableReasons.add(
321                  ERR_PKCS11_KEYMANAGER_PIN_FILE_CANNOT_READ.get(
322                      fileName, cfgEntryDN, getExceptionMessage(ioe)));
323          configAcceptable = false;
324        }
325        finally
326        {
327          StaticUtils.close(br);
328        }
329
330        if (pinStr == null)
331        {
332          unacceptableReasons.add(ERR_PKCS11_KEYMANAGER_PIN_FILE_EMPTY.get(fileName, configEntryDN));
333          configAcceptable = false;
334        }
335      }
336    }
337    else if (configuration.getKeyStorePin() != null)
338    {
339      String pinStr = configuration.getKeyStorePin();
340      if (pinStr == null)
341      {
342        // We should have a pin from the configuration, but no.
343        unacceptableReasons.add(
344            ERR_PKCS11_KEYMANAGER_CANNOT_DETERMINE_PIN_FROM_ATTR.get(cfgEntryDN, null));
345        configAcceptable = false;
346      }
347    }
348
349    return configAcceptable;
350  }
351
352
353
354  /** {@inheritDoc} */
355  public ConfigChangeResult applyConfigurationChange(
356                                 PKCS11KeyManagerProviderCfg configuration)
357  {
358    final ConfigChangeResult ccr = new ConfigChangeResult();
359
360    // Get the PIN needed to access the contents of the keystore file.
361    //
362    // We will offer several places to look for the PIN, and we will
363    // do so in the following order:
364    //
365    // - In a specified Java property
366    // - In a specified environment variable
367    // - In a specified file on the server filesystem.
368    // - As the value of a configuration attribute.
369    //
370    // In any case, the PIN must be in the clear.
371    char[] newPIN = null;
372
373    if (configuration.getKeyStorePinProperty() != null)
374    {
375      String propertyName = configuration.getKeyStorePinProperty();
376      String pinStr = System.getProperty(propertyName);
377
378      if (pinStr == null)
379      {
380        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
381        ccr.addMessage(ERR_PKCS11_KEYMANAGER_PIN_PROPERTY_NOT_SET.get(propertyName, configEntryDN));
382      }
383      else
384      {
385        newPIN = pinStr.toCharArray();
386      }
387    }
388    else if (configuration.getKeyStorePinEnvironmentVariable() != null)
389    {
390      String enVarName = configuration.getKeyStorePinEnvironmentVariable();
391      String pinStr    = System.getenv(enVarName);
392
393      if (pinStr == null)
394      {
395        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
396        ccr.addMessage(ERR_PKCS11_KEYMANAGER_PIN_ENVAR_NOT_SET.get(enVarName, configEntryDN));
397      }
398      else
399      {
400        newPIN = pinStr.toCharArray();
401      }
402    }
403    else if (configuration.getKeyStorePinFile() != null)
404    {
405      String fileName = configuration.getKeyStorePinFile();
406      File   pinFile  = getFileForPath(fileName);
407
408      if (!pinFile.exists())
409      {
410        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
411        ccr.addMessage(ERR_PKCS11_KEYMANAGER_PIN_NO_SUCH_FILE.get(fileName, configEntryDN));
412      }
413      else
414      {
415        String pinStr = null;
416        BufferedReader br = null;
417        try {
418          br = new BufferedReader(new FileReader(pinFile));
419          pinStr = br.readLine();
420        }
421        catch (IOException ioe)
422        {
423          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
424          ccr.addMessage(ERR_PKCS11_KEYMANAGER_PIN_FILE_CANNOT_READ.get(
425              fileName, configEntryDN, getExceptionMessage(ioe)));
426        }
427        finally
428        {
429          StaticUtils.close(br);
430        }
431
432        if (pinStr == null)
433        {
434          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
435          ccr.addMessage(ERR_PKCS11_KEYMANAGER_PIN_FILE_EMPTY.get(fileName, configEntryDN));
436        }
437        else
438        {
439          newPIN = pinStr.toCharArray();
440        }
441      }
442    }
443    else if (configuration.getKeyStorePin() != null)
444    {
445      newPIN = configuration.getKeyStorePin().toCharArray();
446    }
447
448    if (ccr.getResultCode() == ResultCode.SUCCESS)
449    {
450      currentConfig = configuration;
451      keyStorePIN   = newPIN;
452    }
453
454    return ccr;
455  }
456}