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