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 2007-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.backends;
018
019import static org.forgerock.util.Reject.*;
020import static org.opends.messages.BackendMessages.*;
021import static org.opends.server.config.ConfigConstants.*;
022import static org.opends.server.util.ServerConstants.*;
023import static org.opends.server.util.StaticUtils.*;
024
025import java.io.BufferedReader;
026import java.io.File;
027import java.io.FileInputStream;
028import java.io.FileOutputStream;
029import java.io.FileReader;
030import java.io.FileWriter;
031import java.io.IOException;
032import java.io.PrintWriter;
033import java.net.UnknownHostException;
034import java.security.Key;
035import java.security.KeyStore;
036import java.security.KeyStoreException;
037import java.security.cert.Certificate;
038import java.util.Collections;
039import java.util.Iterator;
040import java.util.LinkedHashMap;
041import java.util.List;
042import java.util.Random;
043import java.util.Set;
044import java.util.SortedSet;
045
046import javax.naming.ldap.Rdn;
047import javax.net.ssl.KeyManager;
048import javax.net.ssl.KeyManagerFactory;
049import javax.net.ssl.TrustManager;
050import javax.net.ssl.TrustManagerFactory;
051
052import org.forgerock.i18n.LocalizableMessage;
053import org.forgerock.i18n.slf4j.LocalizedLogger;
054import org.forgerock.opendj.config.server.ConfigChangeResult;
055import org.forgerock.opendj.config.server.ConfigException;
056import org.forgerock.opendj.config.server.ConfigurationChangeListener;
057import org.forgerock.opendj.ldap.AVA;
058import org.forgerock.opendj.ldap.ByteString;
059import org.forgerock.opendj.ldap.ConditionResult;
060import org.forgerock.opendj.ldap.DN;
061import org.forgerock.opendj.ldap.RDN;
062import org.forgerock.opendj.ldap.ResultCode;
063import org.forgerock.opendj.ldap.SearchScope;
064import org.forgerock.opendj.ldap.schema.AttributeType;
065import org.forgerock.opendj.ldap.schema.CoreSchema;
066import org.forgerock.opendj.server.config.server.TrustStoreBackendCfg;
067import org.forgerock.util.Reject;
068import org.opends.server.api.Backend;
069import org.opends.server.core.AddOperation;
070import org.opends.server.core.DeleteOperation;
071import org.opends.server.core.DirectoryServer;
072import org.opends.server.core.ModifyDNOperation;
073import org.opends.server.core.ModifyOperation;
074import org.opends.server.core.SearchOperation;
075import org.opends.server.core.ServerContext;
076import org.opends.server.types.Attribute;
077import org.opends.server.types.AttributeBuilder;
078import org.opends.server.types.Attributes;
079import org.opends.server.types.BackupConfig;
080import org.opends.server.types.BackupDirectory;
081import org.opends.server.types.DirectoryException;
082import org.opends.server.types.Entry;
083import org.opends.server.types.FilePermission;
084import org.opends.server.types.IndexType;
085import org.opends.server.types.InitializationException;
086import org.opends.server.types.LDIFExportConfig;
087import org.opends.server.types.LDIFImportConfig;
088import org.opends.server.types.LDIFImportResult;
089import org.forgerock.opendj.ldap.schema.ObjectClass;
090import org.opends.server.types.RestoreConfig;
091import org.opends.server.types.SearchFilter;
092import org.opends.server.util.CertificateManager;
093import org.opends.server.util.Platform.KeyType;
094import org.opends.server.util.SetupUtils;
095
096/**
097 * This class defines a backend used to provide an LDAP view of public keys
098 * stored in a key store.
099 */
100public class TrustStoreBackend extends Backend<TrustStoreBackendCfg>
101       implements ConfigurationChangeListener<TrustStoreBackendCfg>
102{
103  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
104
105  /** The current configuration state. */
106  private TrustStoreBackendCfg configuration;
107  /** The set of base DNs for this backend. */
108  private SortedSet<DN> baseDNs;
109  /** The base entry. */
110  private Entry baseEntry;
111
112  /** The PIN needed to access the trust store backing file. */
113  private char[] trustStorePIN;
114  /** The path to the trust store backing file. */
115  private String trustStoreFile;
116  /** The type of trust store backing file to use. */
117  private String trustStoreType;
118
119  /** The certificate manager for the trust store. */
120  private CertificateManager certificateManager;
121
122  /**
123   * Creates a new backend.  All backend
124   * implementations must implement a default constructor that use
125   * <CODE>super()</CODE> to invoke this constructor.
126   */
127  public TrustStoreBackend()
128  {
129    super();
130
131    // Perform all initialization in initializeBackend.
132  }
133
134  private DN getBaseDN()
135  {
136    return baseDNs.first();
137  }
138
139  @Override
140  public void configureBackend(TrustStoreBackendCfg config, ServerContext serverContext) throws ConfigException
141  {
142    Reject.ifNull(config);
143    configuration = config;
144  }
145
146  @Override
147  public void openBackend() throws ConfigException, InitializationException
148  {
149    DN configEntryDN = configuration.dn();
150
151    // Create the set of base DNs that we will handle.  In this case, it's just
152    // the DN of the base trust store entry.
153    SortedSet<DN> baseDNSet = configuration.getBaseDN();
154    if (baseDNSet.size() != 1)
155    {
156      throw new InitializationException(ERR_TRUSTSTORE_REQUIRES_ONE_BASE_DN.get(configEntryDN));
157    }
158    baseDNs = baseDNSet;
159
160    // Get the path to the trust store file.
161    trustStoreFile = configuration.getTrustStoreFile();
162
163    // Get the trust store type. If none is specified, then use the default type.
164    trustStoreType = configuration.getTrustStoreType();
165    if (trustStoreType == null)
166    {
167      trustStoreType = KeyStore.getDefaultType();
168    }
169
170    try
171    {
172      KeyStore.getInstance(trustStoreType);
173    }
174    catch (KeyStoreException kse)
175    {
176      logger.traceException(kse);
177      throw new InitializationException(ERR_TRUSTSTORE_INVALID_TYPE.get(
178          trustStoreType, configEntryDN, getExceptionMessage(kse)));
179    }
180
181    trustStorePIN = getTrustStorePIN(configEntryDN);
182
183    certificateManager =
184         new CertificateManager(getFileForPath(trustStoreFile).getPath(),
185                                trustStoreType,
186                                new String(trustStorePIN));
187
188    // Generate a self-signed certificate, if there is none.
189    generateInstanceCertificateIfAbsent();
190
191    // Construct the trust store base entry.
192    LinkedHashMap<ObjectClass,String> objectClasses = new LinkedHashMap<>(2);
193    objectClasses.put(CoreSchema.getTopObjectClass(), OC_TOP);
194    objectClasses.put(DirectoryServer.getSchema().getObjectClass("ds-cfg-branch"), "ds-cfg-branch");
195
196    LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(1);
197    for (AVA ava : getBaseDN().rdn())
198    {
199      AttributeType attrType = ava.getAttributeType();
200      userAttrs.put(attrType, Attributes.createAsList(attrType, ava.getAttributeValue()));
201    }
202
203    baseEntry = new Entry(getBaseDN(), objectClasses, userAttrs, null);
204
205    // Register this as a change listener.
206    configuration.addTrustStoreChangeListener(this);
207
208    // Register the trust store base as a private suffix.
209    try
210    {
211      DirectoryServer.registerBaseDN(getBaseDN(), this, true);
212    }
213    catch (Exception e)
214    {
215      logger.traceException(e);
216      throw new InitializationException(ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(getBaseDN(), e), e);
217    }
218  }
219
220  /**
221   * Get the PIN needed to access the contents of the trust store file. We will offer several places
222   * to look for the PIN, and we will do so in the following order:
223   * <ol>
224   * <li>In a specified Java property</li>
225   * <li>In a specified environment variable</li>
226   * <li>In a specified file on the server filesystem</li>
227   * <li>As the value of a configuration attribute</li>
228   * </ol>
229   * In any case, the PIN must be in the clear. If no PIN is provided, then it will be assumed that
230   * none is required to access the information in the trust store.
231   */
232  private char[] getTrustStorePIN(DN configEntryDN) throws InitializationException
233  {
234    final String pinProperty = configuration.getTrustStorePinProperty();
235    if (pinProperty != null)
236    {
237      String pinStr = System.getProperty(pinProperty);
238      if (pinStr == null)
239      {
240        throw new InitializationException(ERR_TRUSTSTORE_PIN_PROPERTY_NOT_SET.get(pinProperty, configEntryDN));
241      }
242      return pinStr.toCharArray();
243    }
244
245    final String pinEnVar = configuration.getTrustStorePinEnvironmentVariable();
246    if (pinEnVar != null)
247    {
248      String pinStr = System.getenv(pinEnVar);
249      if (pinStr == null)
250      {
251        throw new InitializationException(ERR_TRUSTSTORE_PIN_ENVAR_NOT_SET.get(pinProperty, configEntryDN));
252      }
253      return pinStr.toCharArray();
254    }
255
256    final String pinFilePath = configuration.getTrustStorePinFile();
257    if (pinFilePath != null)
258    {
259      File pinFile = getFileForPath(pinFilePath);
260      if (pinFile.exists())
261      {
262        String pinStr = readPinFromFile(pinFile, configEntryDN);
263        if (pinStr == null)
264        {
265          throw new InitializationException(ERR_TRUSTSTORE_PIN_FILE_EMPTY.get(pinFilePath, configEntryDN));
266        }
267        return pinStr.toCharArray();
268      }
269
270      try
271      {
272        // Generate and store the PIN in the pin file.
273        final char[] trustStorePIN = createKeystorePassword();
274        createPINFile(pinFile.getPath(), new String(trustStorePIN));
275        return trustStorePIN;
276      }
277      catch (Exception e)
278      {
279        throw new InitializationException(ERR_TRUSTSTORE_PIN_FILE_CANNOT_CREATE.get(pinFilePath, configEntryDN));
280      }
281    }
282
283    String pinStr = configuration.getTrustStorePin();
284    // else branch should be an Error. Otherwise, programs fails. Is there a Unit Test?
285    return pinStr != null ? pinStr.toCharArray() : null;
286  }
287
288  @Override
289  public void closeBackend()
290  {
291    configuration.addTrustStoreChangeListener(this);
292
293    try
294    {
295      DirectoryServer.deregisterBaseDN(getBaseDN());
296    }
297    catch (Exception e)
298    {
299      logger.traceException(e);
300    }
301  }
302
303  @Override
304  public Set<DN> getBaseDNs()
305  {
306    return baseDNs;
307  }
308
309  @Override
310  public long getEntryCount()
311  {
312    int numEntries = 1;
313
314    try
315    {
316      String[] aliases = certificateManager.getCertificateAliases();
317      if (aliases != null)
318      {
319        numEntries += aliases.length;
320      }
321    }
322    catch (KeyStoreException e)
323    {
324      logger.traceException(e);
325    }
326
327    return numEntries;
328  }
329
330  @Override
331  public boolean isIndexed(AttributeType attributeType, IndexType indexType)
332  {
333    // All searches in this backend will always be considered indexed.
334    return true;
335  }
336
337  @Override
338  public Entry getEntry(DN entryDN) throws DirectoryException
339  {
340    // If the requested entry was null, then throw an exception.
341    if (entryDN == null)
342    {
343      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
344          ERR_BACKEND_GET_ENTRY_NULL.get(getBackendID()));
345    }
346
347    // If the requested entry was the backend base entry, then retrieve it.
348    if (entryDN.equals(getBaseDN()))
349    {
350      return baseEntry.duplicate(true);
351    }
352
353    // See if the requested entry was one level below the backend base entry.
354    // If so, then it must point to a trust store entry.
355    DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
356    if (parentDN != null && parentDN.equals(getBaseDN()))
357    {
358      try
359      {
360        return getCertEntry(entryDN);
361      }
362      catch (DirectoryException e)
363      {
364        logger.traceException(e);
365      }
366    }
367    return null;
368  }
369
370  /**
371   * Generates an entry for a certificate based on the provided DN.  The
372   * DN must contain an RDN component that specifies the alias of the
373   * certificate, and that certificate alias must exist in the key store.
374   *
375   * @param  entryDN  The DN of the certificate to retrieve.
376   *
377   * @return  The requested certificate entry.
378   *
379   * @throws  DirectoryException  If the specified alias does not exist, or if
380   *                              the DN does not specify any alias.
381   */
382  private Entry getCertEntry(DN entryDN)
383         throws DirectoryException
384  {
385    // Make sure that the DN specifies a certificate alias.
386    AttributeType t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID);
387    ByteString v = entryDN.rdn().getAttributeValue(t);
388    if (v == null)
389    {
390      LocalizableMessage message = ERR_TRUSTSTORE_DN_DOES_NOT_SPECIFY_CERTIFICATE.get(entryDN);
391      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, getBaseDN(), null);
392    }
393
394    String certAlias = v.toString();
395    ByteString certValue;
396    try
397    {
398      Certificate cert = certificateManager.getCertificate(certAlias);
399      if (cert == null)
400      {
401        LocalizableMessage message = ERR_TRUSTSTORE_CERTIFICATE_NOT_FOUND.get(entryDN, certAlias);
402        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
403      }
404      certValue = ByteString.wrap(cert.getEncoded());
405    }
406    catch (Exception e)
407    {
408      logger.traceException(e);
409
410      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_RETRIEVE_CERT.get(
411          certAlias, trustStoreFile, e.getMessage());
412      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
413    }
414
415    // Construct the certificate entry to return.
416    LinkedHashMap<ObjectClass,String> ocMap = new LinkedHashMap<>(2);
417    ocMap.put(CoreSchema.getTopObjectClass(), OC_TOP);
418    ocMap.put(DirectoryServer.getSchema().getObjectClass(OC_CRYPTO_INSTANCE_KEY), OC_CRYPTO_INSTANCE_KEY);
419
420    LinkedHashMap<AttributeType,List<Attribute>> opAttrs = new LinkedHashMap<>(0);
421    LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(3);
422
423    userAttrs.put(t, Attributes.createAsList(t, v));
424
425    t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE);
426    AttributeBuilder builder = new AttributeBuilder(t);
427    builder.setOption("binary");
428    builder.add(certValue);
429    userAttrs.put(t, builder.toAttributeList());
430
431    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
432    e.processVirtualAttributes();
433    return e;
434  }
435
436  @Override
437  public void addEntry(Entry entry, AddOperation addOperation)
438         throws DirectoryException
439  {
440    DN entryDN = entry.getName();
441
442    if (entryDN.equals(getBaseDN()))
443    {
444      LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN);
445      throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message);
446    }
447
448    DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
449    if (parentDN == null)
450    {
451      LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN);
452      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
453    }
454
455    if (parentDN.equals(getBaseDN()))
456    {
457      addCertificate(entry);
458    }
459    else
460    {
461      LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN);
462      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
463    }
464  }
465
466  @Override
467  public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
468         throws DirectoryException
469  {
470    if (entryDN.equals(getBaseDN()))
471    {
472      LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN);
473      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
474    }
475
476    DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
477    if (parentDN == null || !parentDN.equals(getBaseDN()))
478    {
479      LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN);
480      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
481    }
482
483    deleteCertificate(entryDN);
484  }
485
486  @Override
487  public void replaceEntry(Entry oldEntry, Entry newEntry,
488      ModifyOperation modifyOperation) throws DirectoryException
489  {
490    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
491        ERR_BACKEND_MODIFY_NOT_SUPPORTED.get(oldEntry.getName(), getBackendID()));
492  }
493
494  @Override
495  public void renameEntry(DN currentDN, Entry entry,
496                          ModifyDNOperation modifyDNOperation)
497         throws DirectoryException
498  {
499    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
500        ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID()));
501  }
502
503  @Override
504  public void search(SearchOperation searchOperation)
505         throws DirectoryException
506  {
507    // Get the base entry for the search, if possible.  If it doesn't exist,
508    // then this will throw an exception.
509    DN    baseDN    = searchOperation.getBaseDN();
510    Entry baseEntry = getEntry(baseDN);
511
512    // Look at the base DN and see if it's the trust store base DN, or a
513    // trust store entry DN.
514    SearchScope  scope  = searchOperation.getScope();
515    SearchFilter filter = searchOperation.getFilter();
516    if (getBaseDN().equals(baseDN))
517    {
518      if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE)
519          && filter.matchesEntry(baseEntry))
520      {
521        searchOperation.returnEntry(baseEntry, null);
522      }
523
524      String[] aliases = null;
525      try
526      {
527        aliases = certificateManager.getCertificateAliases();
528      }
529      catch (KeyStoreException e)
530      {
531        logger.traceException(e);
532      }
533
534      if (aliases == null)
535      {
536        aliases = new String[0];
537      }
538
539      if (scope != SearchScope.BASE_OBJECT && aliases.length != 0)
540      {
541        AttributeType certAliasType = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID);
542        for (String alias : aliases)
543        {
544          DN certDN = makeChildDN(getBaseDN(), certAliasType, alias);
545
546          Entry certEntry;
547          try
548          {
549            certEntry = getCertEntry(certDN);
550          }
551          catch (Exception e)
552          {
553            logger.traceException(e);
554            continue;
555          }
556
557          if (filter.matchesEntry(certEntry))
558          {
559            searchOperation.returnEntry(certEntry, null);
560          }
561        }
562      }
563    }
564    else if (getBaseDN().equals(DirectoryServer.getParentDNInSuffix(baseDN)))
565    {
566      Entry certEntry = getCertEntry(baseDN);
567
568      if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE)
569          && filter.matchesEntry(certEntry))
570      {
571        searchOperation.returnEntry(certEntry, null);
572      }
573    }
574    else
575    {
576      LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(baseDN);
577      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
578    }
579  }
580
581  @Override
582  public Set<String> getSupportedControls()
583  {
584    return Collections.emptySet();
585  }
586
587  @Override
588  public Set<String> getSupportedFeatures()
589  {
590    return Collections.emptySet();
591  }
592
593  @Override
594  public boolean supports(BackendOperation backendOperation)
595  {
596    return false;
597  }
598
599  @Override
600  public void exportLDIF(LDIFExportConfig exportConfig)
601         throws DirectoryException
602  {
603    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
604        ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID()));
605  }
606
607  @Override
608  public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext)
609      throws DirectoryException
610  {
611    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
612        ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID()));
613  }
614
615  @Override
616  public void createBackup(BackupConfig backupConfig)
617       throws DirectoryException
618  {
619    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
620        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
621  }
622
623  @Override
624  public void removeBackup(BackupDirectory backupDirectory,
625                           String backupID)
626         throws DirectoryException
627  {
628    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
629        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
630  }
631
632  @Override
633  public void restoreBackup(RestoreConfig restoreConfig)
634         throws DirectoryException
635  {
636    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
637        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
638  }
639
640  @Override
641  public ConditionResult hasSubordinates(DN entryDN)
642      throws DirectoryException
643  {
644    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
645        ERR_HAS_SUBORDINATES_NOT_SUPPORTED.get());
646  }
647
648  @Override
649  public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException
650  {
651    checkNotNull(baseDN, "baseDN must not be null");
652    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
653  }
654
655  @Override
656  public long getNumberOfChildren(DN parentDN) throws DirectoryException
657  {
658    checkNotNull(parentDN, "parentDN must not be null");
659    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get());
660  }
661
662  @Override
663  public boolean isConfigurationChangeAcceptable(
664       TrustStoreBackendCfg configuration, List<LocalizableMessage> unacceptableReasons)
665  {
666    final ConfigChangeResult ccr = new ConfigChangeResult();
667    final DN cfgEntryDN = configuration.dn();
668
669    // Get the path to the trust store file.
670    String newTrustStoreFile = configuration.getTrustStoreFile();
671    try
672    {
673      File f = getFileForPath(newTrustStoreFile);
674      if (!f.exists() || !f.isFile())
675      {
676        ccr.addMessage(ERR_TRUSTSTORE_NO_SUCH_FILE.get(newTrustStoreFile, cfgEntryDN));
677      }
678    }
679    catch (Exception e)
680    {
681      logger.traceException(e);
682
683      ccr.addMessage(ERR_TRUSTSTORE_CANNOT_DETERMINE_FILE.get(cfgEntryDN, getExceptionMessage(e)));
684    }
685
686    // Check to see if the trust store type is acceptable.
687    String storeType = configuration.getTrustStoreType();
688    if (storeType != null)
689    {
690      try
691      {
692        KeyStore.getInstance(storeType);
693      }
694      catch (KeyStoreException kse)
695      {
696        logger.traceException(kse);
697
698        ccr.addMessage(ERR_TRUSTSTORE_INVALID_TYPE.get(storeType, cfgEntryDN, getExceptionMessage(kse)));
699      }
700    }
701
702    // If there is a PIN property, then make sure the corresponding
703    // property is set.
704    String pinProp = configuration.getTrustStorePinProperty();
705    if (pinProp != null && System.getProperty(pinProp) == null)
706    {
707      ccr.addMessage(ERR_TRUSTSTORE_PIN_PROPERTY_NOT_SET.get(pinProp, cfgEntryDN));
708    }
709
710    // If there is a PIN environment variable, then make sure the corresponding
711    // environment variable is set.
712    String pinEnVar = configuration.getTrustStorePinEnvironmentVariable();
713    if (pinEnVar != null && System.getenv(pinEnVar) == null)
714    {
715      ccr.addMessage(ERR_TRUSTSTORE_PIN_ENVAR_NOT_SET.get(pinEnVar, cfgEntryDN));
716    }
717
718    // If there is a PIN file, then make sure the file is readable if it exists.
719    String pinFile = configuration.getTrustStorePinFile();
720    if (pinFile != null)
721    {
722      File f = new File(pinFile);
723      if (f.exists())
724      {
725        String pinStr = readPinFromFile2(f, cfgEntryDN, ccr);
726        if (pinStr == null)
727        {
728          ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_EMPTY.get(pinFile, cfgEntryDN));
729        }
730      }
731    }
732
733    final List<LocalizableMessage> messages = ccr.getMessages();
734    unacceptableReasons.addAll(messages);
735    return messages.isEmpty();
736  }
737
738  @Override
739  public ConfigChangeResult applyConfigurationChange(TrustStoreBackendCfg cfg)
740  {
741    final ConfigChangeResult ccr = new ConfigChangeResult();
742    DN configEntryDN = cfg.dn();
743
744    // Get the path to the trust store file.
745    String newTrustStoreFile = cfg.getTrustStoreFile();
746    File f = getFileForPath(newTrustStoreFile);
747    if (!f.exists() || !f.isFile())
748    {
749      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
750      ccr.addMessage(ERR_TRUSTSTORE_NO_SUCH_FILE.get(newTrustStoreFile, configEntryDN));
751    }
752
753    // Get the trust store type.  If none is specified, then use the default
754    // type.
755    String newTrustStoreType = cfg.getTrustStoreType();
756    if (newTrustStoreType == null)
757    {
758      newTrustStoreType = KeyStore.getDefaultType();
759    }
760
761    try
762    {
763      KeyStore.getInstance(newTrustStoreType);
764    }
765    catch (KeyStoreException kse)
766    {
767      logger.traceException(kse);
768
769      ccr.addMessage(ERR_TRUSTSTORE_INVALID_TYPE.get(newTrustStoreType, configEntryDN, getExceptionMessage(kse)));
770      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
771    }
772
773    char[] newPIN = getTrustStorePIN2(cfg, ccr);
774
775    if (ccr.getResultCode() == ResultCode.SUCCESS)
776    {
777      trustStoreFile = newTrustStoreFile;
778      trustStoreType = newTrustStoreType;
779      trustStorePIN  = newPIN;
780      configuration  = cfg;
781      certificateManager =
782           new CertificateManager(getFileForPath(trustStoreFile).getPath(),
783                                  trustStoreType,
784                                  new String(trustStorePIN));
785    }
786
787    return ccr;
788  }
789
790  /**
791   * Get the PIN needed to access the contents of the trust store file. We will offer several places
792   * to look for the PIN, and we will do so in the following order:
793   * <ol>
794   * <li>In a specified Java property</li>
795   * <li>In a specified environment variable</li>
796   * <li>In a specified file on the server filesystem.</li>
797   * <li>As the value of a configuration attribute.</li>
798   * </ol>
799   * In any case, the PIN must be in the clear. If no PIN is provided, then it will be assumed that
800   * none is required to access the information in the trust store.
801   */
802  private char[] getTrustStorePIN2(TrustStoreBackendCfg cfg, ConfigChangeResult ccr)
803  {
804    String newPINProperty = cfg.getTrustStorePinProperty();
805    if (newPINProperty == null)
806    {
807      String newPINEnVar = cfg.getTrustStorePinEnvironmentVariable();
808      if (newPINEnVar == null)
809      {
810        String newPINFile = cfg.getTrustStorePinFile();
811        if (newPINFile == null)
812        {
813          String pinStr = cfg.getTrustStorePin();
814          return pinStr != null ? pinStr.toCharArray() : null;
815        }
816        else
817        {
818          File pinFile = getFileForPath(newPINFile);
819          if (! pinFile.exists())
820          {
821            try
822            {
823              // Generate and store a PIN in the pin file.
824              final char[] newPIN = createKeystorePassword();
825              createPINFile(pinFile.getPath(), new String(newPIN));
826              return newPIN;
827            }
828            catch (Exception e)
829            {
830              ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
831              ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_CANNOT_CREATE.get(newPINFile, cfg.dn()));
832            }
833          }
834          else
835          {
836            String pinStr = readPinFromFile2(pinFile, cfg.dn(), ccr);
837            if (pinStr == null)
838            {
839              ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
840              ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_EMPTY.get(newPINFile, cfg.dn()));
841            }
842            else
843            {
844              return pinStr.toCharArray();
845            }
846          }
847        }
848      }
849      else
850      {
851        String pinStr = System.getenv(newPINEnVar);
852        if (pinStr == null)
853        {
854          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
855          ccr.addMessage(ERR_TRUSTSTORE_PIN_ENVAR_NOT_SET.get(newPINEnVar, cfg.dn()));
856        }
857        else
858        {
859          return pinStr.toCharArray();
860        }
861      }
862    }
863    else
864    {
865      String pinStr = System.getProperty(newPINProperty);
866      if (pinStr == null)
867      {
868        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
869        ccr.addMessage(ERR_TRUSTSTORE_PIN_PROPERTY_NOT_SET.get(newPINProperty, cfg.dn()));
870      }
871      else
872      {
873        return pinStr.toCharArray();
874      }
875    }
876    return null;
877  }
878
879  private String readPinFromFile(File pinFile, DN cfgEntryDN) throws InitializationException
880  {
881    try (BufferedReader br = new BufferedReader(new FileReader(pinFile)))
882    {
883      return br.readLine();
884    }
885    catch (IOException ioe)
886    {
887      LocalizableMessage message =
888          ERR_TRUSTSTORE_PIN_FILE_CANNOT_READ.get(pinFile, cfgEntryDN, getExceptionMessage(ioe));
889      throw new InitializationException(message, ioe);
890    }
891  }
892
893  private String readPinFromFile2(File pinFile, DN cfgEntryDN, ConfigChangeResult ccr)
894  {
895    try (BufferedReader br = new BufferedReader(new FileReader(pinFile)))
896    {
897      return br.readLine();
898    }
899    catch (IOException ioe)
900    {
901      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
902      ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_CANNOT_READ.get(pinFile, cfgEntryDN, getExceptionMessage(ioe)));
903      return null;
904    }
905  }
906
907  /**
908   * Create a new child DN from a given parent DN.  The child RDN is formed
909   * from a given attribute type and string value.
910   * @param parentDN The DN of the parent.
911   * @param rdnAttrType The attribute type of the RDN.
912   * @param rdnStringValue The string value of the RDN.
913   * @return A new child DN.
914   */
915  public static DN makeChildDN(DN parentDN, AttributeType rdnAttrType,
916                               String rdnStringValue)
917  {
918    ByteString attrValue = ByteString.valueOfUtf8(rdnStringValue);
919    return parentDN.child(new RDN(rdnAttrType, attrValue));
920  }
921
922  /**
923   * Retrieves a set of <CODE>KeyManager</CODE> objects that may be used for
924   * interactions requiring access to a key manager.
925   *
926   * @return  A set of <CODE>KeyManager</CODE> objects that may be used for
927   *          interactions requiring access to a key manager.
928   *
929   * @throws DirectoryException  If a problem occurs while attempting to obtain
930   *                             the set of key managers.
931   */
932  public KeyManager[] getKeyManagers()
933         throws DirectoryException
934  {
935    final KeyStore keyStore = loadKeyStore();
936
937    try
938    {
939      String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
940      KeyManagerFactory keyManagerFactory =
941           KeyManagerFactory.getInstance(keyManagerAlgorithm);
942      keyManagerFactory.init(keyStore, trustStorePIN);
943      return keyManagerFactory.getKeyManagers();
944    }
945    catch (Exception e)
946    {
947      logger.traceException(e);
948
949      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_CREATE_FACTORY.get(
950          trustStoreFile, getExceptionMessage(e));
951      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
952                                   message, e);
953    }
954  }
955
956  private KeyStore loadKeyStore() throws DirectoryException
957  {
958    try (FileInputStream inputStream = new FileInputStream(getFileForPath(trustStoreFile)))
959    {
960      final KeyStore keyStore = KeyStore.getInstance(trustStoreType);
961      keyStore.load(inputStream, trustStorePIN);
962      return keyStore;
963    }
964    catch (Exception e)
965    {
966      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_LOAD.get(trustStoreFile, getExceptionMessage(e));
967      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
968    }
969  }
970
971  /**
972   * Retrieves a set of {@code TrustManager} objects that may be used
973   * for interactions requiring access to a trust manager.
974   *
975   * @return  A set of {@code TrustManager} objects that may be used
976   *          for interactions requiring access to a trust manager.
977   *
978   * @throws  DirectoryException  If a problem occurs while attempting
979   *                              to obtain the set of trust managers.
980   */
981  public TrustManager[] getTrustManagers()
982         throws DirectoryException
983  {
984    KeyStore trustStore = loadKeyStore();
985
986    try
987    {
988      String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
989      TrustManagerFactory trustManagerFactory =
990           TrustManagerFactory.getInstance(trustManagerAlgorithm);
991      trustManagerFactory.init(trustStore);
992      return trustManagerFactory.getTrustManagers();
993    }
994    catch (Exception e)
995    {
996      logger.traceException(e);
997
998      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_CREATE_FACTORY.get(
999          trustStoreFile, getExceptionMessage(e));
1000      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1001                                   message, e);
1002    }
1003  }
1004
1005  /**
1006   * Returns the key associated with the given alias, using the trust
1007   * store pin to recover it.
1008   *
1009   * @param   alias The alias name.
1010   *
1011   * @return  The requested key, or null if the given alias does not exist
1012   *          or does not identify a key-related entry.
1013   *
1014   * @throws  DirectoryException  If an error occurs while retrieving the key.
1015   */
1016  public Key getKey(String alias)
1017         throws DirectoryException
1018  {
1019    KeyStore trustStore = loadKeyStore();
1020
1021    try
1022    {
1023      return trustStore.getKey(alias, trustStorePIN);
1024    }
1025    catch (Exception e)
1026    {
1027      logger.traceException(e);
1028
1029      LocalizableMessage message = ERR_TRUSTSTORE_ERROR_READING_KEY.get(
1030           alias, trustStoreFile, getExceptionMessage(e));
1031      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1032                                   message, e);
1033    }
1034  }
1035
1036  private void addCertificate(Entry entry)
1037       throws DirectoryException
1038  {
1039    DN entryDN = entry.getName();
1040
1041    // Make sure that the DN specifies a certificate alias.
1042    AttributeType t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID);
1043    ByteString v = entryDN.rdn().getAttributeValue(t);
1044    if (v == null)
1045    {
1046      LocalizableMessage message = ERR_TRUSTSTORE_DN_DOES_NOT_SPECIFY_CERTIFICATE.get(entryDN);
1047      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, getBaseDN(), null);
1048    }
1049
1050    String certAlias = v.toString();
1051    try
1052    {
1053      if (certificateManager.aliasInUse(certAlias))
1054      {
1055        LocalizableMessage message = ERR_TRUSTSTORE_ALIAS_IN_USE.get(entryDN);
1056        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message);
1057      }
1058
1059      if (entry.hasObjectClass(DirectoryServer.getSchema().getObjectClass(OC_SELF_SIGNED_CERT_REQUEST)))
1060      {
1061        try
1062        {
1063          final KeyType keyType = KeyType.getTypeOrDefault(certAlias);
1064          certificateManager.generateSelfSignedCertificate(
1065             keyType,
1066             certAlias,
1067             getADSCertificateSubjectDN(keyType),
1068             getADSCertificateValidity());
1069        }
1070        catch (Exception e)
1071        {
1072          LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_GENERATE_CERT.get(
1073              certAlias, trustStoreFile, getExceptionMessage(e));
1074          throw new DirectoryException(
1075               DirectoryServer.getServerErrorResultCode(), message, e);
1076        }
1077      }
1078      else
1079      {
1080        List<Attribute> certAttrs = entry.getAttribute(
1081             ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE);
1082        if (certAttrs.isEmpty())
1083        {
1084          LocalizableMessage message =
1085               ERR_TRUSTSTORE_ENTRY_MISSING_CERT_ATTR.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE);
1086          throw new DirectoryException(
1087               DirectoryServer.getServerErrorResultCode(), message);
1088        }
1089        if (certAttrs.size() != 1)
1090        {
1091          LocalizableMessage message =
1092               ERR_TRUSTSTORE_ENTRY_HAS_MULTIPLE_CERT_ATTRS.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE);
1093          throw new DirectoryException(
1094               DirectoryServer.getServerErrorResultCode(), message);
1095        }
1096
1097        Attribute certAttr = certAttrs.get(0);
1098        Iterator<ByteString> i = certAttr.iterator();
1099
1100        if (!i.hasNext())
1101        {
1102          LocalizableMessage message =
1103               ERR_TRUSTSTORE_ENTRY_MISSING_CERT_VALUE.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE);
1104          throw new DirectoryException(
1105               DirectoryServer.getServerErrorResultCode(), message);
1106        }
1107
1108        ByteString certBytes = i.next();
1109
1110        if (i.hasNext())
1111        {
1112          LocalizableMessage message =
1113               ERR_TRUSTSTORE_ENTRY_HAS_MULTIPLE_CERT_VALUES.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE);
1114          throw new DirectoryException(
1115               DirectoryServer.getServerErrorResultCode(), message);
1116        }
1117
1118        try
1119        {
1120          File tempDir = getFileForPath("config");
1121          File tempFile = File.createTempFile(configuration.getBackendId(),
1122                                              certAlias, tempDir);
1123          try
1124          {
1125            try (FileOutputStream outputStream = new FileOutputStream(tempFile.getPath(), false))
1126            {
1127              certBytes.copyTo(outputStream);
1128            }
1129
1130            certificateManager.addCertificate(certAlias, tempFile);
1131          }
1132          finally
1133          {
1134            tempFile.delete();
1135          }
1136        }
1137        catch (IOException e)
1138        {
1139          LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_WRITE_CERT.get(
1140              certAlias, getExceptionMessage(e));
1141          throw new DirectoryException(
1142               DirectoryServer.getServerErrorResultCode(), message, e);
1143        }
1144      }
1145    }
1146    catch (Exception e)
1147    {
1148      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_ADD_CERT.get(
1149           certAlias, trustStoreFile, getExceptionMessage(e));
1150      throw new DirectoryException(
1151           DirectoryServer.getServerErrorResultCode(), message, e);
1152    }
1153  }
1154
1155  private void deleteCertificate(DN entryDN)
1156       throws DirectoryException
1157  {
1158    // Make sure that the DN specifies a certificate alias.
1159    AttributeType t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID);
1160    ByteString v = entryDN.rdn().getAttributeValue(t);
1161    if (v == null)
1162    {
1163      LocalizableMessage message = ERR_TRUSTSTORE_DN_DOES_NOT_SPECIFY_CERTIFICATE.get(entryDN);
1164      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, getBaseDN(), null);
1165    }
1166
1167    String certAlias = v.toString();
1168    try
1169    {
1170      if (!certificateManager.aliasInUse(certAlias))
1171      {
1172        LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN);
1173        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
1174      }
1175
1176      certificateManager.removeCertificate(certAlias);
1177    }
1178    catch (Exception e)
1179    {
1180      LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_DELETE_CERT.get(
1181           certAlias, trustStoreFile, getExceptionMessage(e));
1182      throw new DirectoryException(
1183           DirectoryServer.getServerErrorResultCode(), message, e);
1184    }
1185  }
1186
1187  /**
1188   * Returns the validity period to be used to generate the ADS certificate.
1189   * @return The validity period to be used to generate the ADS certificate.
1190   */
1191  private static int getADSCertificateValidity()
1192  {
1193    return 20 * 365;
1194  }
1195
1196  /**
1197   * Returns the Subject DN to be used to generate the ADS certificate.
1198   * @return The Subject DN to be used to generate the ADS certificate.
1199   * @throws java.net.UnknownHostException If the server host name could not be
1200   *                                       determined.
1201   */
1202  private static String getADSCertificateSubjectDN(KeyType keyType) throws UnknownHostException
1203  {
1204    final String hostName = SetupUtils.getHostNameForCertificate(DirectoryServer.getServerRoot());
1205    return "cn=" + Rdn.escapeValue(hostName) + ",O=OpenDJ " + keyType + " Certificate";
1206  }
1207
1208  /**
1209   * Create a randomly generated password for a certificate keystore.
1210   * @return A randomly generated password for a certificate keystore.
1211   */
1212  private static char[] createKeystorePassword() {
1213    int pwdLength = 50;
1214    char[] pwd = new char[pwdLength];
1215    Random random = new Random();
1216    for (int pos=0; pos < pwdLength; pos++) {
1217        int type = getRandomInt(random,3);
1218        char nextChar = getRandomChar(random,type);
1219        pwd[pos] = nextChar;
1220    }
1221    return pwd;
1222  }
1223
1224  private static char getRandomChar(Random random, int type)
1225  {
1226    char generatedChar;
1227    int next = random.nextInt();
1228    int d;
1229
1230    switch (type)
1231    {
1232    case 0:
1233      // Will return a digit
1234      d = next % 10;
1235      if (d < 0)
1236      {
1237        d = d * -1;
1238      }
1239      generatedChar = (char) (d+48);
1240      break;
1241    case 1:
1242      // Will return a lower case letter
1243      d = next % 26;
1244      if (d < 0)
1245      {
1246        d = d * -1;
1247      }
1248      generatedChar =  (char) (d + 97);
1249      break;
1250    default:
1251      // Will return a capital letter
1252      d = next % 26;
1253      if (d < 0)
1254      {
1255        d = d * -1;
1256      }
1257      generatedChar = (char) (d + 65) ;
1258    }
1259
1260    return generatedChar;
1261  }
1262
1263  private static int getRandomInt(Random random,int modulo)
1264  {
1265    return random.nextInt() & modulo;
1266  }
1267
1268  /**
1269   * Creates a PIN file on the specified path.
1270   * @param path the path where the PIN file will be created.
1271   * @param pin The PIN to store in the file.
1272   * @throws IOException if something goes wrong.
1273   */
1274  public static void createPINFile(String path, String pin)
1275       throws IOException
1276  {
1277    try (final FileWriter file = new FileWriter(path);
1278         final PrintWriter out = new PrintWriter(file))
1279    {
1280      out.println(pin);
1281      out.flush();
1282    }
1283
1284    try {
1285      if (!FilePermission.setPermissions(new File(path),
1286          new FilePermission(0600)))
1287      {
1288        // Log a warning that the permissions were not set.
1289        logger.warn(WARN_TRUSTSTORE_SET_PERMISSIONS_FAILED, path);
1290      }
1291    } catch(DirectoryException e) {
1292      // Log a warning that the permissions were not set.
1293      logger.warn(WARN_TRUSTSTORE_SET_PERMISSIONS_FAILED, path);
1294    }
1295  }
1296
1297  /**
1298   * Generates a self-signed certificate with well-known alias if there is none.
1299   * @throws InitializationException If an error occurs while interacting with
1300   *                                 the key store.
1301   */
1302  private void generateInstanceCertificateIfAbsent() throws InitializationException
1303  {
1304    final String certAlias = ADS_CERTIFICATE_ALIAS;
1305    try
1306    {
1307      if (certificateManager.aliasInUse(certAlias))
1308      {
1309        return;
1310      }
1311    }
1312    catch (Exception e)
1313    {
1314      LocalizableMessage message =
1315          ERR_TRUSTSTORE_CANNOT_ADD_CERT.get(certAlias, trustStoreFile, getExceptionMessage(e));
1316      throw new InitializationException(message, e);
1317    }
1318
1319    try
1320    {
1321      final KeyType keyType = KeyType.getTypeOrDefault(certAlias);
1322      certificateManager.generateSelfSignedCertificate(keyType, certAlias, getADSCertificateSubjectDN(keyType),
1323          getADSCertificateValidity());
1324    }
1325    catch (Exception e)
1326    {
1327      LocalizableMessage message =
1328          ERR_TRUSTSTORE_CANNOT_GENERATE_CERT.get(certAlias, trustStoreFile, getExceptionMessage(e));
1329      throw new InitializationException(message, e);
1330    }
1331  }
1332}