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 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017
018package org.opends.quicksetup.util;
019
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.FileOutputStream;
023import java.io.IOException;
024import java.security.KeyStore;
025import java.security.KeyStoreException;
026import java.security.NoSuchAlgorithmException;
027import java.security.cert.Certificate;
028import java.security.cert.CertificateException;
029import java.security.cert.X509Certificate;
030import java.util.Enumeration;
031
032import org.forgerock.i18n.LocalizableMessage;
033import org.forgerock.i18n.slf4j.LocalizedLogger;
034
035/** Class used to get the KeyStore that the graphical utilities use. */
036public class UIKeyStore extends KeyStore
037{
038  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
039  private static KeyStore keyStore;
040
041  /** This should never be called. */
042  private UIKeyStore()
043  {
044    super(null, null, null);
045  }
046  /**
047   * Returns the KeyStore to be used by graphical applications.
048   * @return the KeyStore to be used by graphical applications.
049   * @throws IOException if there was a file system access error.
050   * @throws KeyStoreException if there was a problem while reading the key
051   * store.
052   * @throws CertificateException if an error with a certificate occurred.
053   * @throws NoSuchAlgorithmException if the used algorithm is not supported
054   * by the system.
055   */
056  public static KeyStore getInstance() throws IOException, KeyStoreException,
057      CertificateException, NoSuchAlgorithmException
058  {
059    if (keyStore == null)
060    {
061      keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
062      String keyStorePath = getKeyStorePath();
063
064      File f = new File(keyStorePath);
065      if (!f.exists())
066      {
067        logger.info(LocalizableMessage.raw("Path "+keyStorePath+ " does not exist"));
068        keyStorePath = null;
069      }
070      else if (f.isDirectory())
071      {
072        logger.error(LocalizableMessage.raw("Path "+keyStorePath+ " is a directory"));
073        keyStorePath = null;
074      }
075      else if (!f.canRead())
076      {
077        logger.error(LocalizableMessage.raw("Path "+keyStorePath+ " is not readable"));
078        keyStorePath = null;
079      }
080      else if (!f.canWrite())
081      {
082        logger.error(LocalizableMessage.raw("Path "+keyStorePath+ " is not writable"));
083        keyStorePath = null;
084      }
085
086
087      if (keyStorePath != null)
088      {
089        FileInputStream fos = new FileInputStream(keyStorePath);
090        try
091        {
092          keyStore.load(fos, null);
093        }
094        catch (Throwable t)
095        {
096          logger.error(LocalizableMessage.raw("Error reading key store on "+keyStorePath, t));
097          keyStore.load(null, null);
098        }
099        fos.close();
100      }
101      else
102      {
103        keyStore.load(null, null);
104      }
105      loadLocalAdminTrustStore(keyStore);
106    }
107    return keyStore;
108  }
109
110  /**
111   * Updates the Key Store with the provided certificate chain.
112   * @param chain the certificate chain to be accepted.
113   * @throws IOException if there was a file system access error.
114   * @throws KeyStoreException if there was a problem while reading or writing
115   * to the key store.
116   * @throws CertificateException if an error with a certificate occurred.
117   * @throws NoSuchAlgorithmException if the used algorithm is not supported
118   * by the system.
119   */
120  public static void acceptCertificate(X509Certificate[] chain)
121      throws IOException,KeyStoreException, CertificateException,
122      NoSuchAlgorithmException
123  {
124    logger.info(LocalizableMessage.raw("Accepting certificate chain."));
125    KeyStore k = getInstance();
126    for (X509Certificate aChain : chain) {
127      if (!containsCertificate(aChain, k)) {
128        String alias = aChain.getSubjectDN().getName();
129        int j = 1;
130        while (k.containsAlias(alias)) {
131          alias = aChain.getSubjectDN().getName() + "-" + j;
132          j++;
133        }
134        k.setCertificateEntry(alias, aChain);
135      }
136    }
137    String keyStorePath = getKeyStorePath();
138    File f = new File(keyStorePath);
139    if (!f.exists())
140    {
141      Utils.createFile(f);
142    }
143    FileOutputStream fos = new FileOutputStream(getKeyStorePath(), false);
144    k.store(fos, new char[]{});
145    fos.close();
146  }
147
148  /**
149   * Returns the path where we store the keystore for the graphical
150   * applications.
151   * @return the path where we store the keystore for the graphical
152   * applications.
153   */
154  private static String getKeyStorePath()
155  {
156    return System.getProperty("user.home") + File.separator +
157    ".opendj" + File.separator + "gui-keystore";
158  }
159
160  /**
161   * Loads the local admin truststore.
162   * @param keyStore the keystore where the admin truststore will be loaded.
163   */
164  private static void loadLocalAdminTrustStore(KeyStore keyStore)
165  {
166    String adminTrustStorePath = getLocalAdminTrustStorePath();
167    File f = new File(adminTrustStorePath);
168    if (!f.exists())
169    {
170      logger.info(LocalizableMessage.raw("Path "+adminTrustStorePath+ " does not exist"));
171      adminTrustStorePath = null;
172    }
173    else if (f.isDirectory())
174    {
175      logger.error(LocalizableMessage.raw("Path "+adminTrustStorePath+ " is a directory"));
176      adminTrustStorePath = null;
177    }
178    else if (!f.canRead())
179    {
180      logger.error(LocalizableMessage.raw("Path "+adminTrustStorePath+ " is not readable"));
181      adminTrustStorePath = null;
182    }
183
184    if (adminTrustStorePath != null)
185    {
186      FileInputStream fos = null;
187      try
188      {
189        fos = new FileInputStream(adminTrustStorePath);
190        KeyStore adminKeyStore =
191          KeyStore.getInstance(KeyStore.getDefaultType());
192        adminKeyStore.load(fos, null);
193        Enumeration<String> aliases = adminKeyStore.aliases();
194        while (aliases.hasMoreElements())
195        {
196          String alias = aliases.nextElement();
197          if (adminKeyStore.isCertificateEntry(alias))
198          {
199            keyStore.setCertificateEntry(alias,
200                adminKeyStore.getCertificate(alias));
201          }
202          else
203          {
204            keyStore.setEntry(alias, adminKeyStore.getEntry(alias, null), null);
205          }
206        }
207      }
208      catch (Throwable t)
209      {
210        logger.error(LocalizableMessage.raw("Error reading admin key store on "+
211            adminTrustStorePath, t));
212      }
213      finally
214      {
215        try
216        {
217          if (fos != null)
218          {
219            fos.close();
220          }
221        }
222        catch (Throwable t)
223        {
224          logger.error(LocalizableMessage.raw("Error closing admin key store on "+
225              adminTrustStorePath, t));
226        }
227      }
228    }
229  }
230
231  /**
232   * Returns the path where the local admin trust store is.
233   * @return the path where the local admin trust store is.
234   */
235  private static String getLocalAdminTrustStorePath()
236  {
237    String instancePath =
238      Utils.getInstancePathFromInstallPath(Utils.getInstallPathFromClasspath());
239    return  instancePath + File.separator + "config" +
240    File.separator + "admin-truststore";
241  }
242
243  /**
244   * Returns whether the key store contains the provided certificate or not.
245   * @param cert the certificate.
246   * @param keyStore the key store.
247   * @return whether the key store contains the provided certificate or not.
248   * @throws KeyStoreException if an error occurs reading the contents of the
249   * key store.
250   */
251  private static boolean containsCertificate(X509Certificate cert,
252      KeyStore keyStore) throws KeyStoreException
253  {
254    boolean found = false;
255    Enumeration<String> aliases = keyStore.aliases();
256    while (aliases.hasMoreElements() && !found)
257    {
258      String alias = aliases.nextElement();
259      if (keyStore.isCertificateEntry(alias))
260      {
261        Certificate c = keyStore.getCertificate(alias);
262        found = c.equals(cert);
263      }
264    }
265    return found;
266  }
267}