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 2009-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027
028package org.opends.server.util;
029
030
031
032import java.security.KeyStoreException;
033import java.security.KeyStore;
034import java.security.PrivateKey;
035import java.security.cert.Certificate;
036import java.security.cert.CertificateFactory;
037import java.security.cert.X509Certificate;
038import java.util.List;
039import java.io.FileInputStream;
040import java.io.FileOutputStream;
041import java.io.InputStream;
042import java.lang.management.ManagementFactory;
043import java.lang.management.MemoryPoolMXBean;
044import java.lang.management.MemoryUsage;
045import java.lang.reflect.Constructor;
046import java.lang.reflect.Method;
047
048import org.forgerock.i18n.LocalizableMessage;
049
050import static org.opends.messages.UtilityMessages.*;
051
052
053
054/**
055 * Provides a wrapper class that collects all of the JVM vendor and JDK version
056 * specific code in a single place.
057 */
058public final class Platform
059{
060
061  /** Prefix that determines which security package to use. */
062  private static final String pkgPrefix;
063
064  /** The two security package prefixes (IBM and SUN). */
065  private static final String IBM_SEC = "com.ibm.security";
066  private static final String SUN_SEC = "sun.security";
067
068  private static final PlatformIMPL IMPL;
069
070  /** The minimum java supported version. */
071  public static final String JAVA_MINIMUM_VERSION_NUMBER = "7.0";
072
073  static
074  {
075    String vendor = System.getProperty("java.vendor");
076
077    if (vendor.startsWith("IBM"))
078    {
079      pkgPrefix = IBM_SEC;
080    }
081    else
082    {
083      pkgPrefix = SUN_SEC;
084    }
085    IMPL = new DefaultPlatformIMPL();
086  }
087
088
089
090  /**
091   * Platform base class. Performs all of the certificate management functions.
092   */
093  private static abstract class PlatformIMPL
094  {
095
096    /** Key size, key algorithm and signature algorithms used. */
097    private static final int KEY_SIZE = 1024;
098    private static final String KEY_ALGORITHM = "rsa";
099    private static final String SIG_ALGORITHM = "SHA1WithRSA";
100
101    /** Time values used in validity calculations. */
102    private static final int SEC_IN_DAY = 24 * 60 * 60;
103
104    /** Methods pulled from the classes. */
105    private static final String GENERATE_METHOD = "generate";
106    private static final String GET_PRIVATE_KEY_METHOD = "getPrivateKey";
107    private static final String GET_SELFSIGNED_CERT_METHOD =
108      "getSelfCertificate";
109
110    /** Classes needed to manage certificates. */
111    private static final Class<?> certKeyGenClass, X500NameClass;
112
113    /** Constructors for each of the above classes. */
114    private static Constructor<?> certKeyGenCons, X500NameCons;
115
116    /** Filesystem APIs */
117
118    static
119    {
120
121      String x509pkg = pkgPrefix + ".x509";
122      String certAndKeyGen;
123      if (pkgPrefix.equals(IBM_SEC)
124          || System.getProperty("java.version").matches("^1\\.[67]\\..*"))
125      {
126        certAndKeyGen = x509pkg + ".CertAndKeyGen";
127      }
128      else
129      { // Java 8 moved the CertAndKeyGen class to sun.security.tools.keytool
130        certAndKeyGen = pkgPrefix + ".tools.keytool" + ".CertAndKeyGen";
131      }
132      String X500Name = x509pkg + ".X500Name";
133      try
134      {
135        certKeyGenClass = Class.forName(certAndKeyGen);
136        X500NameClass = Class.forName(X500Name);
137        certKeyGenCons = certKeyGenClass.getConstructor(String.class,
138            String.class);
139        X500NameCons = X500NameClass.getConstructor(String.class);
140      }
141      catch (ClassNotFoundException e)
142      {
143        LocalizableMessage msg = ERR_CERTMGR_CLASS_NOT_FOUND.get(e.getMessage());
144        throw new ExceptionInInitializerError(msg.toString());
145      }
146      catch (SecurityException e)
147      {
148        LocalizableMessage msg = ERR_CERTMGR_SECURITY.get(e.getMessage());
149        throw new ExceptionInInitializerError(msg.toString());
150      }
151      catch (NoSuchMethodException e)
152      {
153        LocalizableMessage msg = ERR_CERTMGR_NO_METHOD.get(e.getMessage());
154        throw new ExceptionInInitializerError(msg.toString());
155      }
156    }
157
158
159
160    protected PlatformIMPL()
161    {
162    }
163
164
165
166    private final void deleteAlias(KeyStore ks, String ksPath, String alias,
167        char[] pwd) throws KeyStoreException
168    {
169      try
170      {
171        if (ks == null)
172        {
173          LocalizableMessage msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get();
174          throw new KeyStoreException(msg.toString());
175        }
176        ks.deleteEntry(alias);
177        FileOutputStream fs = new FileOutputStream(ksPath);
178        ks.store(fs, pwd);
179        fs.close();
180      }
181      catch (Exception e)
182      {
183        LocalizableMessage msg = ERR_CERTMGR_DELETE_ALIAS.get(alias, e.getMessage());
184        throw new KeyStoreException(msg.toString());
185      }
186    }
187
188
189
190    private final void addCertificate(KeyStore ks, String ksType, String ksPath,
191        String alias, char[] pwd, String certPath) throws KeyStoreException
192    {
193      try
194      {
195        CertificateFactory cf = CertificateFactory.getInstance("X509");
196        InputStream inStream = new FileInputStream(certPath);
197        if (ks == null)
198        {
199          ks = KeyStore.getInstance(ksType);
200          ks.load(null, pwd);
201        }
202        // Do not support certificate replies.
203        if (ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class))
204        {
205          LocalizableMessage msg = ERR_CERTMGR_CERT_REPLIES_INVALID.get(alias);
206          throw new KeyStoreException(msg.toString());
207        }
208        else if (!ks.containsAlias(alias)
209            || ks
210                .entryInstanceOf(alias, KeyStore.TrustedCertificateEntry.class))
211        {
212          trustedCert(alias, cf, ks, inStream);
213        }
214        else
215        {
216          LocalizableMessage msg = ERR_CERTMGR_ALIAS_INVALID.get(alias);
217          throw new KeyStoreException(msg.toString());
218        }
219        FileOutputStream fileOutStream = new FileOutputStream(ksPath);
220        ks.store(fileOutStream, pwd);
221        fileOutStream.close();
222        inStream.close();
223      }
224      catch (Exception e)
225      {
226        LocalizableMessage msg = ERR_CERTMGR_ADD_CERT.get(alias, e.getMessage());
227        throw new KeyStoreException(msg.toString());
228      }
229    }
230
231
232
233    private final KeyStore generateSelfSignedCertificate(KeyStore ks,
234        String ksType, String ksPath, String alias, char[] pwd, String dn,
235        int validity) throws KeyStoreException
236    {
237      try
238      {
239        if (ks == null)
240        {
241          ks = KeyStore.getInstance(ksType);
242          ks.load(null, pwd);
243        }
244        else if (ks.containsAlias(alias))
245        {
246          LocalizableMessage msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
247          throw new KeyStoreException(msg.toString());
248        }
249        Object keypair = certKeyGenCons.newInstance(KEY_ALGORITHM,
250            SIG_ALGORITHM);
251        Object subject = X500NameCons.newInstance(dn);
252        Method certAndKeyGenGenerate = certKeyGenClass.getMethod(
253            GENERATE_METHOD, int.class);
254        certAndKeyGenGenerate.invoke(keypair, KEY_SIZE);
255        Method certAndKeyGetPrivateKey = certKeyGenClass
256            .getMethod(GET_PRIVATE_KEY_METHOD);
257        PrivateKey privatevKey = (PrivateKey) certAndKeyGetPrivateKey
258            .invoke(keypair);
259        Certificate[] certificateChain = new Certificate[1];
260        Method getSelfCertificate = certKeyGenClass.getMethod(
261            GET_SELFSIGNED_CERT_METHOD, X500NameClass, long.class);
262        int days = validity * SEC_IN_DAY;
263        certificateChain[0] = (Certificate) getSelfCertificate.invoke(keypair,
264            subject, days);
265        ks.setKeyEntry(alias, privatevKey, pwd, certificateChain);
266        FileOutputStream fileOutStream = new FileOutputStream(ksPath);
267        ks.store(fileOutStream, pwd);
268        fileOutStream.close();
269      }
270      catch (Exception e)
271      {
272        LocalizableMessage msg = ERR_CERTMGR_GEN_SELF_SIGNED_CERT.get(alias, e
273            .getMessage());
274        throw new KeyStoreException(msg.toString());
275      }
276      return ks;
277    }
278
279
280
281    /**
282     * Generate a x509 certificate from the input stream. Verification is done
283     * only if it is self-signed.
284     */
285    private void trustedCert(String alias, CertificateFactory cf, KeyStore ks,
286        InputStream in) throws KeyStoreException
287    {
288      try
289      {
290        if (ks.containsAlias(alias))
291        {
292          LocalizableMessage msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
293          throw new KeyStoreException(msg.toString());
294        }
295        X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
296        if (isSelfSigned(cert))
297        {
298          cert.verify(cert.getPublicKey());
299        }
300        ks.setCertificateEntry(alias, cert);
301      }
302      catch (Exception e)
303      {
304        LocalizableMessage msg = ERR_CERTMGR_TRUSTED_CERT.get(alias, e.getMessage());
305        throw new KeyStoreException(msg.toString());
306      }
307    }
308
309
310
311    /**
312     * Check that the issuer and subject DNs match.
313     */
314    private boolean isSelfSigned(X509Certificate cert)
315    {
316      return cert.getSubjectDN().equals(cert.getIssuerDN());
317    }
318
319
320
321    private long getUsableMemoryForCaching()
322    {
323      long youngGenSize = 0;
324      long oldGenSize = 0;
325
326      List<MemoryPoolMXBean> mpools = ManagementFactory.getMemoryPoolMXBeans();
327      for (MemoryPoolMXBean mpool : mpools)
328      {
329        MemoryUsage usage = mpool.getUsage();
330        if (usage != null)
331        {
332          String name = mpool.getName();
333          if (name.equalsIgnoreCase("PS Eden Space"))
334          {
335            // Parallel.
336            youngGenSize = usage.getMax();
337          }
338          else if (name.equalsIgnoreCase("PS Old Gen"))
339          {
340            // Parallel.
341            oldGenSize = usage.getMax();
342          }
343          else if (name.equalsIgnoreCase("Par Eden Space"))
344          {
345            // CMS.
346            youngGenSize = usage.getMax();
347          }
348          else if (name.equalsIgnoreCase("CMS Old Gen"))
349          {
350            // CMS.
351            oldGenSize = usage.getMax();
352          }
353        }
354      }
355
356      if (youngGenSize > 0 && oldGenSize > youngGenSize)
357      {
358        // We can calculate available memory based on GC info.
359        return oldGenSize - youngGenSize;
360      }
361      else if (oldGenSize > 0)
362      {
363        // Small old gen. It is going to be difficult to avoid full GCs if the
364        // young gen is bigger.
365        return oldGenSize * 40 / 100;
366      }
367      else
368      {
369        // Unknown GC (G1, JRocket, etc).
370        Runtime runTime = Runtime.getRuntime();
371        runTime.gc();
372        runTime.gc();
373        return (runTime.freeMemory() + (runTime.maxMemory() - runTime
374            .totalMemory())) * 40 / 100;
375      }
376    }
377  }
378
379
380
381  /** Prevent instantiation. */
382  private Platform()
383  {
384  }
385
386
387
388  /**
389   * Add the certificate in the specified path to the provided keystore;
390   * creating the keystore with the provided type and path if it doesn't exist.
391   *
392   * @param ks
393   *          The keystore to add the certificate to, may be null if it doesn't
394   *          exist.
395   * @param ksType
396   *          The type to use if the keystore is created.
397   * @param ksPath
398   *          The path to the keystore if it is created.
399   * @param alias
400   *          The alias to store the certificate under.
401   * @param pwd
402   *          The password to use in saving the certificate.
403   * @param certPath
404   *          The path to the file containing the certificate.
405   * @throws KeyStoreException
406   *           If an error occurred adding the certificate to the keystore.
407   */
408  public static void addCertificate(KeyStore ks, String ksType, String ksPath,
409      String alias, char[] pwd, String certPath) throws KeyStoreException
410  {
411    IMPL.addCertificate(ks, ksType, ksPath, alias, pwd, certPath);
412  }
413
414
415
416  /**
417   * Delete the specified alias from the provided keystore.
418   *
419   * @param ks
420   *          The keystore to delete the alias from.
421   * @param ksPath
422   *          The path to the keystore.
423   * @param alias
424   *          The alias to use in the request generation.
425   * @param pwd
426   *          The keystore password to use.
427   * @throws KeyStoreException
428   *           If an error occurred deleting the alias.
429   */
430  public static void deleteAlias(KeyStore ks, String ksPath, String alias,
431      char[] pwd) throws KeyStoreException
432  {
433    IMPL.deleteAlias(ks, ksPath, alias, pwd);
434  }
435
436
437
438  /**
439   * Generate a self-signed certificate using the specified alias, dn string and
440   * validity period. If the keystore does not exist, it will be created using
441   * the specified keystore type and path.
442   *
443   * @param ks
444   *          The keystore to save the certificate in. May be null if it does
445   *          not exist.
446   * @param ksType
447   *          The keystore type to use if the keystore is created.
448   * @param ksPath
449   *          The path to the keystore if the keystore is created.
450   * @param alias
451   *          The alias to store the certificate under.
452   * @param pwd
453   *          The password to us in saving the certificate.
454   * @param dn
455   *          The dn string used as the certificate subject.
456   * @param validity
457   *          The validity of the certificate in days.
458   * @throws KeyStoreException
459   *           If the self-signed certificate cannot be generated.
460   */
461  public static void generateSelfSignedCertificate(KeyStore ks, String ksType,
462      String ksPath, String alias, char[] pwd, String dn, int validity)
463      throws KeyStoreException
464  {
465    IMPL.generateSelfSignedCertificate(ks, ksType, ksPath, alias, pwd, dn,
466        validity);
467  }
468
469  /**
470   * Default platform class.
471   */
472  private static class DefaultPlatformIMPL extends PlatformIMPL
473  {
474  }
475
476
477
478  /**
479   * Test if a platform java vendor property starts with the specified vendor
480   * string.
481   *
482   * @param vendor
483   *          The vendor to check for.
484   * @return {@code true} if the java vendor starts with the specified vendor
485   *         string.
486   */
487  public static boolean isVendor(String vendor)
488  {
489    String javaVendor = System.getProperty("java.vendor");
490    return javaVendor.startsWith(vendor);
491  }
492
493
494
495  /**
496   * Calculates the usable memory which could potentially be used by the
497   * application for caching objects. This method <b>does not</b> look at the
498   * amount of free memory, but instead tries to query the JVM's GC settings in
499   * order to determine the amount of usable memory in the old generation (or
500   * equivalent). More specifically, applications may also need to take into
501   * account the amount of memory already in use, for example by performing the
502   * following:
503   *
504   * <pre>
505   * Runtime runTime = Runtime.getRuntime();
506   * runTime.gc();
507   * runTime.gc();
508   * long freeCommittedMemory = runTime.freeMemory();
509   * long uncommittedMemory = runTime.maxMemory() - runTime.totalMemory();
510   * long freeMemory = freeCommittedMemory + uncommittedMemory;
511   * </pre>
512   *
513   * @return The usable memory which could potentially be used by the
514   *         application for caching objects.
515   */
516  public static long getUsableMemoryForCaching()
517  {
518    return IMPL.getUsableMemoryForCaching();
519  }
520}