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