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