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