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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import org.forgerock.i18n.LocalizableMessage;
020import java.io.BufferedReader;
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.FileReader;
024import java.io.IOException;
025import java.security.*;
026import java.util.List;
027import javax.net.ssl.TrustManager;
028import javax.net.ssl.TrustManagerFactory;
029import javax.net.ssl.X509TrustManager;
030
031import org.opends.server.admin.server.ConfigurationChangeListener;
032import org.opends.server.admin.std.server.TrustManagerProviderCfg;
033import org.opends.server.admin.std.server.FileBasedTrustManagerProviderCfg;
034import org.opends.server.api.TrustManagerProvider;
035import org.forgerock.opendj.config.server.ConfigException;
036import org.opends.server.core.DirectoryServer;
037import org.forgerock.opendj.config.server.ConfigChangeResult;
038import org.opends.server.types.DirectoryException;
039import org.forgerock.opendj.ldap.DN;
040import org.opends.server.types.InitializationException;
041import org.forgerock.opendj.ldap.ResultCode;
042import org.opends.server.util.ExpirationCheckTrustManager;
043
044import org.forgerock.i18n.slf4j.LocalizedLogger;
045import static org.opends.messages.ExtensionMessages.*;
046import static org.opends.server.util.StaticUtils.*;
047
048/**
049 * This class defines a trust manager provider that will reference certificates
050 * stored in a file located on the Directory Server filesystem.
051 */
052public class FileBasedTrustManagerProvider
053       extends TrustManagerProvider<FileBasedTrustManagerProviderCfg>
054       implements ConfigurationChangeListener<FileBasedTrustManagerProviderCfg>
055{
056  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
057
058
059
060
061  /** The DN of the configuration entry for this trust manager provider. */
062  private DN configEntryDN;
063
064  /** The PIN needed to access the trust store. */
065  private char[] trustStorePIN;
066
067  /** The handle to the configuration for this trust manager. */
068  private FileBasedTrustManagerProviderCfg currentConfig;
069
070  /** The path to the trust store backing file. */
071  private String trustStoreFile;
072
073  /** The trust store type to use. */
074  private String trustStoreType;
075
076
077
078  /**
079   * Creates a new instance of this file-based trust manager provider.  The
080   * <CODE>initializeTrustManagerProvider</CODE> method must be called on the
081   * resulting object before it may be used.
082   */
083  public FileBasedTrustManagerProvider()
084  {
085    // No implementation is required.
086  }
087
088
089
090  /** {@inheritDoc} */
091  @Override
092  public void initializeTrustManagerProvider(
093                   FileBasedTrustManagerProviderCfg configuration)
094         throws ConfigException, InitializationException
095  {
096    // Store the DN of the configuration entry and register to listen for any
097    // changes to the configuration entry.
098    currentConfig = configuration;
099    configEntryDN = configuration.dn();
100    configuration.addFileBasedChangeListener(this);
101
102
103    // Get the path to the trust store file.
104    trustStoreFile = configuration.getTrustStoreFile();
105    File f = getFileForPath(trustStoreFile);
106    if (!f.exists() || !f.isFile())
107    {
108      LocalizableMessage message = ERR_FILE_TRUSTMANAGER_NO_SUCH_FILE.get(trustStoreFile, configEntryDN);
109      throw new InitializationException(message);
110    }
111
112
113    // Get the trust store type.  If none is specified, then use the default
114    // type.
115    trustStoreType = configuration.getTrustStoreType();
116    if (trustStoreType == null)
117    {
118      trustStoreType = KeyStore.getDefaultType();
119    }
120
121    try
122    {
123      KeyStore.getInstance(trustStoreType);
124    }
125    catch (KeyStoreException kse)
126    {
127      logger.traceException(kse);
128
129      LocalizableMessage message = ERR_FILE_TRUSTMANAGER_INVALID_TYPE.
130          get(trustStoreType, configEntryDN, getExceptionMessage(kse));
131      throw new InitializationException(message);
132    }
133
134
135    // Get the PIN needed to access the contents of the trust store file.  We
136    // will offer several places to look for the PIN, and we will do so in the
137    // following order:
138    // - In a specified Java property
139    // - In a specified environment variable
140    // - In a specified file on the server filesystem.
141    // - As the value of a configuration attribute.
142    // In any case, the PIN must be in the clear.  If no PIN is provided, then
143    // it will be assumed that none is required to access the information in the
144    // trust store.
145    String pinProperty = configuration.getTrustStorePinProperty();
146    if (pinProperty == null)
147    {
148      String pinEnVar = configuration.getTrustStorePinEnvironmentVariable();
149      if (pinEnVar == null)
150      {
151        String pinFilePath = configuration.getTrustStorePinFile();
152        if (pinFilePath == null)
153        {
154          String pinStr = configuration.getTrustStorePin();
155          if (pinStr == null)
156          {
157            trustStorePIN = null;
158          }
159          else
160          {
161            trustStorePIN = pinStr.toCharArray();
162          }
163        }
164        else
165        {
166          File pinFile = getFileForPath(pinFilePath);
167          if (! pinFile.exists())
168          {
169            LocalizableMessage message = ERR_FILE_TRUSTMANAGER_PIN_NO_SUCH_FILE.get(pinFilePath, configEntryDN);
170            throw new InitializationException(message);
171          }
172          else
173          {
174            String pinStr;
175
176            BufferedReader br = null;
177            try
178            {
179              br = new BufferedReader(new FileReader(pinFile));
180              pinStr = br.readLine();
181            }
182            catch (IOException ioe)
183            {
184              LocalizableMessage message = ERR_FILE_TRUSTMANAGER_PIN_FILE_CANNOT_READ.
185                  get(pinFilePath, configEntryDN, getExceptionMessage(ioe));
186              throw new InitializationException(message, ioe);
187            }
188            finally
189            {
190              close(br);
191            }
192
193            if (pinStr == null)
194            {
195              LocalizableMessage message = ERR_FILE_TRUSTMANAGER_PIN_FILE_EMPTY.get(pinFilePath, configEntryDN);
196              throw new InitializationException(message);
197            }
198            else
199            {
200              trustStorePIN     = pinStr.toCharArray();
201            }
202          }
203        }
204      }
205      else
206      {
207        String pinStr = System.getenv(pinEnVar);
208        if (pinStr == null)
209        {
210          LocalizableMessage message = ERR_FILE_TRUSTMANAGER_PIN_ENVAR_NOT_SET.get(pinProperty, configEntryDN);
211          throw new InitializationException(message);
212        }
213        else
214        {
215          trustStorePIN = pinStr.toCharArray();
216        }
217      }
218    }
219    else
220    {
221      String pinStr = System.getProperty(pinProperty);
222      if (pinStr == null)
223      {
224        LocalizableMessage message = ERR_FILE_TRUSTMANAGER_PIN_PROPERTY_NOT_SET.get(pinProperty, configEntryDN);
225        throw new InitializationException(message);
226      }
227      else
228      {
229        trustStorePIN = pinStr.toCharArray();
230      }
231    }
232  }
233
234
235
236  /** {@inheritDoc} */
237  @Override
238  public void finalizeTrustManagerProvider()
239  {
240    currentConfig.removeFileBasedChangeListener(this);
241  }
242
243
244
245  /** {@inheritDoc} */
246  @Override
247  public TrustManager[] getTrustManagers()
248         throws DirectoryException
249  {
250    KeyStore trustStore;
251    try
252    {
253      trustStore = KeyStore.getInstance(trustStoreType);
254
255      FileInputStream inputStream =
256           new FileInputStream(getFileForPath(trustStoreFile));
257      trustStore.load(inputStream, trustStorePIN);
258      inputStream.close();
259    }
260    catch (Exception e)
261    {
262      logger.traceException(e);
263
264      LocalizableMessage message = ERR_FILE_TRUSTMANAGER_CANNOT_LOAD.get(
265          trustStoreFile, getExceptionMessage(e));
266      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
267                                   message, e);
268    }
269
270
271    try
272    {
273      String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
274      TrustManagerFactory trustManagerFactory =
275           TrustManagerFactory.getInstance(trustManagerAlgorithm);
276      trustManagerFactory.init(trustStore);
277      TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
278      TrustManager[] newTrustManagers = new TrustManager[trustManagers.length];
279      for (int i=0; i < trustManagers.length; i++)
280      {
281        newTrustManagers[i] = new ExpirationCheckTrustManager(
282                                       (X509TrustManager) trustManagers[i]);
283      }
284      return newTrustManagers;
285    }
286    catch (Exception e)
287    {
288      logger.traceException(e);
289
290      LocalizableMessage message = ERR_FILE_TRUSTMANAGER_CANNOT_CREATE_FACTORY.get(
291          trustStoreFile, getExceptionMessage(e));
292      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
293                                   message, e);
294    }
295  }
296
297
298
299  /** {@inheritDoc} */
300  @Override
301  public boolean isConfigurationAcceptable(
302                         TrustManagerProviderCfg configuration,
303                         List<LocalizableMessage> unacceptableReasons)
304  {
305    FileBasedTrustManagerProviderCfg config =
306            (FileBasedTrustManagerProviderCfg) configuration;
307    return isConfigurationChangeAcceptable(config, unacceptableReasons);
308  }
309
310
311
312  /** {@inheritDoc} */
313  public boolean isConfigurationChangeAcceptable(
314                      FileBasedTrustManagerProviderCfg configuration,
315                      List<LocalizableMessage> unacceptableReasons)
316  {
317    boolean configAcceptable = true;
318    DN cfgEntryDN = configuration.dn();
319
320
321    // Get the path to the trust store file.
322    String newTrustStoreFile = configuration.getTrustStoreFile();
323    try
324    {
325      File f = getFileForPath(newTrustStoreFile);
326      if (!f.exists() || !f.isFile())
327      {
328        unacceptableReasons.add(ERR_FILE_TRUSTMANAGER_NO_SUCH_FILE.get(newTrustStoreFile, cfgEntryDN));
329        configAcceptable = false;
330      }
331    }
332    catch (Exception e)
333    {
334      logger.traceException(e);
335
336      unacceptableReasons.add(ERR_FILE_TRUSTMANAGER_CANNOT_DETERMINE_FILE.get(cfgEntryDN, getExceptionMessage(e)));
337      configAcceptable = false;
338    }
339
340
341    // Check to see if the trust store type is acceptable.
342    String storeType = configuration.getTrustStoreType();
343    if (storeType != null)
344    {
345      try
346      {
347        KeyStore.getInstance(storeType);
348      }
349      catch (KeyStoreException kse)
350      {
351        logger.traceException(kse);
352
353        unacceptableReasons.add(ERR_FILE_TRUSTMANAGER_INVALID_TYPE.get(
354            storeType, cfgEntryDN, getExceptionMessage(kse)));
355        configAcceptable = false;
356      }
357    }
358
359
360    // If there is a PIN property, then make sure the corresponding
361    // property is set.
362    String pinProp = configuration.getTrustStorePinProperty();
363    if (pinProp != null && System.getProperty(pinProp) == null)
364    {
365      unacceptableReasons.add(ERR_FILE_TRUSTMANAGER_PIN_PROPERTY_NOT_SET.get(pinProp, cfgEntryDN));
366      configAcceptable = false;
367    }
368
369
370    // If there is a PIN environment variable, then make sure the corresponding
371    // environment variable is set.
372    String pinEnVar = configuration.getTrustStorePinEnvironmentVariable();
373    if (pinEnVar != null && System.getenv(pinEnVar) == null)
374    {
375      unacceptableReasons.add(ERR_FILE_TRUSTMANAGER_PIN_ENVAR_NOT_SET.get(pinEnVar, cfgEntryDN));
376      configAcceptable = false;
377    }
378
379
380    // If there is a PIN file, then make sure the file exists and is readable.
381    String pinFile = configuration.getTrustStorePinFile();
382    if (pinFile != null)
383    {
384      File f = getFileForPath(pinFile);
385      if (f.exists())
386      {
387        String pinStr = null;
388
389        BufferedReader br = null;
390        try
391        {
392          br = new BufferedReader(new FileReader(f));
393          pinStr = br.readLine();
394        }
395        catch (IOException ioe)
396        {
397          unacceptableReasons.add(ERR_FILE_TRUSTMANAGER_PIN_FILE_CANNOT_READ.get(
398              pinFile, cfgEntryDN, getExceptionMessage(ioe)));
399          configAcceptable = false;
400        }
401        finally
402        {
403          close(br);
404        }
405
406        if (pinStr == null)
407        {
408          LocalizableMessage message = ERR_FILE_TRUSTMANAGER_PIN_FILE_EMPTY.get(pinFile, cfgEntryDN);
409          unacceptableReasons.add(message);
410          configAcceptable = false;
411        }
412      }
413      else
414      {
415        LocalizableMessage message = ERR_FILE_TRUSTMANAGER_PIN_NO_SUCH_FILE.get(pinFile, cfgEntryDN);
416        unacceptableReasons.add(message);
417        configAcceptable = false;
418      }
419    }
420
421
422    return configAcceptable;
423  }
424
425  /** {@inheritDoc} */
426  public ConfigChangeResult applyConfigurationChange(
427                                 FileBasedTrustManagerProviderCfg configuration)
428  {
429    final ConfigChangeResult ccr = new ConfigChangeResult();
430
431
432    // Get the path to the trust store file.
433    String newTrustStoreFile = configuration.getTrustStoreFile();
434    File f = getFileForPath(newTrustStoreFile);
435    if (!f.exists() || !f.isFile())
436    {
437      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
438      ccr.addMessage(ERR_FILE_TRUSTMANAGER_NO_SUCH_FILE.get(newTrustStoreFile, configEntryDN));
439    }
440
441    // Get the trust store type.  If none is specified, then use the default type.
442    String newTrustStoreType = configuration.getTrustStoreType();
443    if (newTrustStoreType == null)
444    {
445      newTrustStoreType = KeyStore.getDefaultType();
446    }
447
448    try
449    {
450      KeyStore.getInstance(newTrustStoreType);
451    }
452    catch (KeyStoreException kse)
453    {
454      logger.traceException(kse);
455
456      ccr.addMessage(ERR_FILE_TRUSTMANAGER_INVALID_TYPE.get(
457          newTrustStoreType, configEntryDN, getExceptionMessage(kse)));
458      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
459    }
460
461
462    // Get the PIN needed to access the contents of the trust store file.  We
463    // will offer several places to look for the PIN, and we will do so in the
464    // following order:
465    // - In a specified Java property
466    // - In a specified environment variable
467    // - In a specified file on the server filesystem.
468    // - As the value of a configuration attribute.
469    // In any case, the PIN must be in the clear.  If no PIN is provided, then
470    // it will be assumed that none is required to access the information in the
471    // trust store.
472    char[] newPIN = null;
473    String newPINProperty = configuration.getTrustStorePinProperty();
474    if (newPINProperty == null)
475    {
476      String newPINEnVar = configuration.getTrustStorePinEnvironmentVariable();
477      if (newPINEnVar == null)
478      {
479        String newPINFile = configuration.getTrustStorePinFile();
480        if (newPINFile == null)
481        {
482          String pinStr = configuration.getTrustStorePin();
483          if (pinStr == null)
484          {
485            newPIN = null;
486          }
487          else
488          {
489            newPIN = pinStr.toCharArray();
490          }
491        }
492        else
493        {
494          File pinFile = getFileForPath(newPINFile);
495          if (! pinFile.exists())
496          {
497            ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
498            ccr.addMessage(ERR_FILE_TRUSTMANAGER_PIN_NO_SUCH_FILE.get(newPINFile, configEntryDN));
499          }
500          else
501          {
502            String pinStr = null;
503
504            BufferedReader br = null;
505            try
506            {
507              br = new BufferedReader(new FileReader(pinFile));
508              pinStr = br.readLine();
509            }
510            catch (IOException ioe)
511            {
512              ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
513              ccr.addMessage(ERR_FILE_TRUSTMANAGER_PIN_FILE_CANNOT_READ.get(
514                  newPINFile, configEntryDN, getExceptionMessage(ioe)));
515            }
516            finally
517            {
518              close(br);
519            }
520
521            if (pinStr == null)
522            {
523              ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
524              ccr.addMessage(ERR_FILE_TRUSTMANAGER_PIN_FILE_EMPTY.get(newPINFile, configEntryDN));
525            }
526            else
527            {
528              newPIN = pinStr.toCharArray();
529            }
530          }
531        }
532      }
533      else
534      {
535        String pinStr = System.getenv(newPINEnVar);
536        if (pinStr == null)
537        {
538          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
539          ccr.addMessage(ERR_FILE_TRUSTMANAGER_PIN_ENVAR_NOT_SET.get(newPINEnVar, configEntryDN));
540        }
541        else
542        {
543          newPIN = pinStr.toCharArray();
544        }
545      }
546    }
547    else
548    {
549      String pinStr = System.getProperty(newPINProperty);
550      if (pinStr == null)
551      {
552        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
553        ccr.addMessage(ERR_FILE_TRUSTMANAGER_PIN_PROPERTY_NOT_SET.get(newPINProperty, configEntryDN));
554      }
555      else
556      {
557        newPIN = pinStr.toCharArray();
558      }
559    }
560
561
562    if (ccr.getResultCode() == ResultCode.SUCCESS)
563    {
564      trustStoreFile = newTrustStoreFile;
565      trustStoreType = newTrustStoreType;
566      trustStorePIN  = newPIN;
567      currentConfig  = configuration;
568    }
569
570    return ccr;
571  }
572}