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 2013-2015 ForgeRock AS
026 */
027package org.opends.server.util;
028
029import java.io.*;
030import java.security.*;
031import java.security.cert.Certificate;
032import java.util.ArrayList;
033import java.util.Enumeration;
034import org.forgerock.i18n.LocalizableMessage;
035import static org.opends.messages.UtilityMessages.*;
036
037/**
038 * This class provides an interface for generating self-signed certificates and
039 * certificate signing requests, and for importing, exporting, and deleting
040 * certificates from a key store.  It supports JKS, PKCS11, and PKCS12 key store
041 * types.
042 * <BR><BR>
043   This code uses the Platform class to perform all of the certificate
044    management.
045 */
046@org.opends.server.types.PublicAPI(
047     stability=org.opends.server.types.StabilityLevel.VOLATILE,
048     mayInstantiate=true,
049     mayExtend=false,
050     mayInvoke=true)
051public final class CertificateManager {
052
053  /**
054   * The key store type value that should be used for the "JKS" key store.
055   */
056  public static final String KEY_STORE_TYPE_JKS = "JKS";
057
058  /**
059   * The key store type value that should be used for the "JCEKS" key store.
060   */
061  public static final String KEY_STORE_TYPE_JCEKS = "JCEKS";
062
063  /**
064   * The key store type value that should be used for the "PKCS11" key store.
065   */
066  public static final String KEY_STORE_TYPE_PKCS11 = "PKCS11";
067
068  /**
069   * The key store type value that should be used for the "PKCS12" key store.
070   */
071  public static final String KEY_STORE_TYPE_PKCS12 = "PKCS12";
072
073  /**
074   * The key store path value that must be used in conjunction with the PKCS11
075   * key store type.
076   */
077  public static final String KEY_STORE_PATH_PKCS11 = "NONE";
078
079  /** Error message strings. */
080  private static final String KEYSTORE_PATH_MSG = "key store path";
081  private static final String KEYSTORE_TYPE_MSG = "key store type";
082  private static final String SUBJECT_DN_MSG = "subject DN";
083  private static final String CERT_ALIAS_MSG = "certificate alias";
084  private static final String CERT_REQUEST_FILE_MSG =
085                                                    "certificate request file";
086  /** The parsed key store backing this certificate manager. */
087  private KeyStore keyStore;
088
089  /** The path to the key store that we should be using. */
090  private final String keyStorePath;
091  /** The name of the key store type we are using. */
092  private final String keyStoreType;
093
094  private final char[] password;
095
096  private Boolean realAliases;
097
098  /**
099   * Always return true.
100   *
101   * @return  This always returns true;
102   */
103  public static boolean mayUseCertificateManager() {
104    return true;
105  }
106
107
108
109  /**
110   * Creates a new certificate manager instance with the provided information.
111   *
112   * @param  keyStorePath  The path to the key store file, or "NONE" if the key
113   *                       store type is "PKCS11".  For the other key store
114   *                       types, the file does not need to exist if a new
115   *                       self-signed certificate or certificate signing
116   *                       request is to be generated, although the directory
117   *                       containing the file must exist.  The key store file
118   *                       must exist if import or export operations are to be
119   *                       performed.
120   * @param  keyStoreType  The key store type to use.  It should be one of
121   *                       {@code KEY_STORE_TYPE_JKS},
122   *                       {@code KEY_STORE_TYPE_JCEKS},
123   *                       {@code KEY_STORE_TYPE_PKCS11}, or
124   *                       {@code KEY_STORE_TYPE_PKCS12}.
125   * @param  keyStorePassword   The password required to access the key store.
126   *                         It must not be {@code null}.
127   * @throws IllegalArgumentException If an argument is invalid or {@code null}.
128   *
129   */
130  public CertificateManager(String keyStorePath, String keyStoreType,
131                            String keyStorePassword)
132  throws IllegalArgumentException {
133    ensureValid(keyStorePath, KEYSTORE_PATH_MSG);
134    ensureValid(keyStoreType, KEYSTORE_TYPE_MSG);
135    if (keyStoreType.equals(KEY_STORE_TYPE_PKCS11)) {
136      if (! keyStorePath.equals(KEY_STORE_PATH_PKCS11)) {
137        LocalizableMessage msg =
138          ERR_CERTMGR_INVALID_PKCS11_PATH.get(KEY_STORE_PATH_PKCS11);
139        throw new IllegalArgumentException(msg.toString());
140      }
141    } else if (keyStoreType.equals(KEY_STORE_TYPE_JKS) ||
142        keyStoreType.equals(KEY_STORE_TYPE_JCEKS) ||
143        keyStoreType.equals(KEY_STORE_TYPE_PKCS12)) {
144      File keyStoreFile = new File(keyStorePath);
145      if (keyStoreFile.exists()) {
146        if (! keyStoreFile.isFile()) {
147          LocalizableMessage msg = ERR_CERTMGR_INVALID_KEYSTORE_PATH.get(keyStorePath);
148          throw new IllegalArgumentException(msg.toString());
149        }
150      } else {
151        final File keyStoreDirectory = keyStoreFile.getParentFile();
152        if (keyStoreDirectory == null || !keyStoreDirectory.exists() || !keyStoreDirectory.isDirectory()) {
153          LocalizableMessage msg = ERR_CERTMGR_INVALID_PARENT.get(keyStorePath);
154          throw new IllegalArgumentException(msg.toString());
155        }
156      }
157    } else {
158      LocalizableMessage msg =  ERR_CERTMGR_INVALID_STORETYPE.get(
159          KEY_STORE_TYPE_JKS, KEY_STORE_TYPE_JCEKS,
160          KEY_STORE_TYPE_PKCS11, KEY_STORE_TYPE_PKCS12);
161      throw new IllegalArgumentException(msg.toString());
162    }
163    this.keyStorePath = keyStorePath;
164    this.keyStoreType = keyStoreType;
165    this.password =
166        keyStorePassword == null ? null : keyStorePassword.toCharArray();
167    keyStore = null;
168  }
169
170
171
172  /**
173   * Indicates whether the provided alias is in use in the key store.
174   *
175   * @param  alias  The alias for which to make the determination.  It must not
176   *                be {@code null} or empty.
177   *
178   * @return  {@code true} if the key store exist and already contains a
179   *          certificate with the given alias, or {@code false} if not.
180   *
181   * @throws  KeyStoreException  If a problem occurs while attempting to
182   *                             interact with the key store.
183   */
184  public boolean aliasInUse(final String alias)
185  throws KeyStoreException {
186    ensureValid(alias, CERT_ALIAS_MSG);
187    KeyStore keyStore = getKeyStore();
188    return keyStore != null && keyStore.containsAlias(alias);
189  }
190
191
192
193  /**
194   * Retrieves the aliases of the certificates in the specified key store.
195   *
196   * @return  The aliases of the certificates in the specified key store, or
197   *          {@code null} if the key store does not exist.
198   *
199   * @throws  KeyStoreException  If a problem occurs while attempting to
200   *                             interact with the key store.
201   */
202  public String[] getCertificateAliases() throws KeyStoreException {
203    Enumeration<String> aliasEnumeration = null;
204    KeyStore keyStore = getKeyStore();
205    if (keyStore == null)
206    {
207      return null;
208    }
209    aliasEnumeration = keyStore.aliases();
210    if (aliasEnumeration == null)
211    {
212      return new String[0];
213    }
214    ArrayList<String> aliasList = new ArrayList<>();
215    while (aliasEnumeration.hasMoreElements())
216    {
217      aliasList.add(aliasEnumeration.nextElement());
218    }
219    String[] aliases = new String[aliasList.size()];
220    return aliasList.toArray(aliases);
221  }
222
223
224
225  /**
226   * Retrieves the certificate with the specified alias from the key store.
227   *
228   * @param  alias  The alias of the certificate to retrieve.  It must not be
229   *                {@code null} or empty.
230   *
231   * @return  The requested certificate, or {@code null} if the specified
232   *          certificate does not exist.
233   *
234   * @throws  KeyStoreException  If a problem occurs while interacting with the
235   *                             key store, or the key store does not exist..
236   */
237  public Certificate  getCertificate(String alias)
238  throws KeyStoreException {
239    ensureValid(alias, CERT_ALIAS_MSG);
240    KeyStore ks = getKeyStore();
241    if (ks == null) {
242      LocalizableMessage msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get();
243      throw new KeyStoreException(msg.toString());
244    }
245    return ks.getCertificate(alias);
246  }
247
248
249  /**
250   * Generates a self-signed certificate using the provided information.
251   *
252   * @param  alias      The nickname to use for the certificate in the key
253   *                    store.  For the server certificate, it should generally
254   *                    be "server-cert".  It must not be {@code null} or empty.
255   * @param  subjectDN  The subject DN to use for the certificate.  It must not
256   *                    be {@code null} or empty.
257   * @param  validity   The length of time in days that the certificate should
258   *                    be valid, starting from the time the certificate is
259   *                    generated.  It must be a positive integer value.
260   * @throws  KeyStoreException  If a problem occurs while actually attempting
261   *                             to generate the certificate in the key store.
262   *@throws IllegalArgumentException If the validity parameter is not a
263   *                                 positive integer, or the alias is already
264   *                                 in the keystore.
265   */
266  public void generateSelfSignedCertificate(String alias, String subjectDN,
267                                            int validity)
268  throws KeyStoreException, IllegalArgumentException {
269    ensureValid(alias, CERT_ALIAS_MSG);
270    ensureValid(subjectDN, SUBJECT_DN_MSG);
271    if (validity <= 0) {
272      LocalizableMessage msg = ERR_CERTMGR_VALIDITY.get(validity);
273      throw new IllegalArgumentException(msg.toString());
274    }
275    if (aliasInUse(alias)) {
276      LocalizableMessage msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
277      throw new IllegalArgumentException(msg.toString());
278    }
279    keyStore = null;
280    Platform.generateSelfSignedCertificate(getKeyStore(), keyStoreType,
281        keyStorePath, alias, password, subjectDN, validity);
282  }
283
284
285
286  /**
287   * Adds the provided certificate to the key store.  This may be used to
288   * associate an externally-signed certificate with an existing private key
289   * with the given alias.
290   *
291   * @param  alias            The alias to use for the certificate.  It must not
292   *                          be {@code null} or empty.
293   * @param  certificateFile  The file containing the encoded certificate.  It
294   *                          must not be {@code null}, and the file must exist.
295
296   * @throws  KeyStoreException  If a problem occurs while interacting with the
297   *                             key store.
298   *
299   *@throws IllegalArgumentException If the certificate file is not valid.
300   */
301  public void addCertificate(String alias, File certificateFile)
302  throws  KeyStoreException, IllegalArgumentException {
303    ensureValid(alias, CERT_ALIAS_MSG);
304    ensureFileValid(certificateFile, CERT_REQUEST_FILE_MSG);
305    if (!certificateFile.exists() || !certificateFile.isFile()) {
306      LocalizableMessage msg = ERR_CERTMGR_INVALID_CERT_FILE.get(
307          certificateFile.getAbsolutePath());
308      throw new IllegalArgumentException(msg.toString());
309    }
310    keyStore = null;
311    Platform.addCertificate(getKeyStore(), keyStoreType, keyStorePath, alias,
312        password, certificateFile.getAbsolutePath());
313  }
314
315
316  /**
317   * Removes the specified certificate from the key store.
318   *
319   * @param  alias  The alias to use for the certificate to remove.  It must not
320   *                be {@code null} or an empty string, and it must exist in
321   *                the key store.
322   *
323   * @throws  KeyStoreException  If a problem occurs while interacting with the
324   *                             key store.
325   *@throws IllegalArgumentException If the alias is in use and cannot be
326   *                                 deleted.
327   */
328  public void removeCertificate(String alias)
329  throws KeyStoreException, IllegalArgumentException {
330    ensureValid(alias, CERT_ALIAS_MSG);
331    if (!aliasInUse(alias)) {
332      LocalizableMessage msg = ERR_CERTMGR_ALIAS_CAN_NOT_DELETE.get(alias);
333      throw new IllegalArgumentException(msg.toString());
334    }
335    keyStore = null;
336    Platform.deleteAlias(getKeyStore(), keyStorePath, alias, password);
337  }
338
339
340  /**
341   * Retrieves a handle to the key store.
342   *
343   * @return  The handle to the key store, or {@code null} if the key store
344   *          doesn't exist.
345   *
346   * @throws  KeyStoreException  If a problem occurs while trying to open the
347   *                             key store.
348   */
349  private KeyStore getKeyStore()
350  throws KeyStoreException
351  {
352      if (keyStore != null)
353      {
354          return keyStore;
355      }
356
357      // For JKS and PKCS12 key stores, we should make sure the file exists, and
358      // we'll need an input stream that we can use to read it.  For PKCS11 key
359      // stores there won't be a file and the input stream should be null.
360      FileInputStream keyStoreInputStream = null;
361      if (keyStoreType.equals(KEY_STORE_TYPE_JKS) ||
362          keyStoreType.equals(KEY_STORE_TYPE_JCEKS) ||
363          keyStoreType.equals(KEY_STORE_TYPE_PKCS12))
364      {
365          final File keyStoreFile = new File(keyStorePath);
366          if (! keyStoreFile.exists())
367          {
368              return null;
369          }
370
371          try
372          {
373              keyStoreInputStream = new FileInputStream(keyStoreFile);
374          }
375          catch (final Exception e)
376          {
377              throw new KeyStoreException(String.valueOf(e), e);
378          }
379      }
380
381
382      final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
383      try
384      {
385          keyStore.load(keyStoreInputStream, password);
386          return this.keyStore = keyStore;
387      }
388      catch (final Exception e)
389      {
390          throw new KeyStoreException(String.valueOf(e), e);
391      }
392      finally
393      {
394          if (keyStoreInputStream != null)
395          {
396              try
397              {
398                  keyStoreInputStream.close();
399              }
400              catch (final Throwable t)
401              {
402              }
403          }
404      }
405  }
406
407  /**
408   * Returns whether this certificate manager contains 'real' aliases or not.
409   * For instance, the certificate manager can contain a PKCS12 certificate
410   * with no alias.
411   * @return whether this certificate manager contains 'real' aliases or not.
412   * @throws KeyStoreException if there is a problem accessing the key store.
413   */
414  public boolean hasRealAliases() throws KeyStoreException
415  {
416    if (realAliases == null)
417    {
418      String[] aliases = getCertificateAliases();
419      if (aliases == null || aliases.length == 0)
420      {
421        realAliases = Boolean.FALSE;
422      }
423      else if (aliases.length > 1)
424      {
425        realAliases = Boolean.TRUE;
426      }
427      else
428      {
429        CertificateManager certManager2 = new CertificateManager(keyStorePath,
430            keyStoreType, new String(password));
431        String[] aliases2 = certManager2.getCertificateAliases();
432        if (aliases2 != null && aliases2.length == 1)
433        {
434          realAliases = aliases[0].equalsIgnoreCase(aliases2[0]);
435        }
436        else
437        {
438          realAliases = Boolean.FALSE;
439        }
440      }
441    }
442    return realAliases;
443  }
444
445  private static void ensureFileValid(File arg, String msgStr) {
446    if(arg == null) {
447      LocalizableMessage msg = ERR_CERTMGR_FILE_NAME_INVALID.get(msgStr);
448      throw new NullPointerException(msg.toString());
449    }
450  }
451
452  private static void ensureValid(String arg, String msgStr) {
453    if(arg == null || arg.length() == 0) {
454     LocalizableMessage msg = ERR_CERTMGR_VALUE_INVALID.get(msgStr);
455      throw new NullPointerException(msg.toString());
456    }
457  }
458}