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