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