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.NoSuchAlgorithmException;
034import java.security.KeyPairGenerator;
035import java.security.KeyStore;
036import java.security.PrivateKey;
037import java.security.cert.Certificate;
038import java.security.cert.CertificateFactory;
039import java.security.cert.X509Certificate;
040import java.util.List;
041import java.io.FileInputStream;
042import java.io.FileOutputStream;
043import java.io.InputStream;
044import java.lang.management.ManagementFactory;
045import java.lang.management.MemoryPoolMXBean;
046import java.lang.management.MemoryUsage;
047import java.lang.reflect.Constructor;
048import java.lang.reflect.Method;
049
050import org.forgerock.i18n.LocalizableMessage;
051import org.forgerock.util.Reject;
052
053import static org.opends.messages.UtilityMessages.*;
054import static org.opends.server.util.ServerConstants.CERTANDKEYGEN_PROVIDER;
055
056
057
058/**
059 * Provides a wrapper class that collects all of the JVM vendor and JDK version
060 * specific code in a single place.
061 */
062public final class Platform
063{
064
065  /** Prefix that determines which security package to use. */
066  private static final String pkgPrefix;
067
068  /** The two security package prefixes (IBM and SUN). */
069  private static final String IBM_SEC = "com.ibm.security";
070  private static final String SUN_SEC = "sun.security";
071
072  /** The CertAndKeyGen class is located in different packages depending on JVM environment. */
073  private static final String[] CERTANDKEYGEN_PATHS = new String[] {
074      "sun.security.x509.CertAndKeyGen",          // Oracle/Sun/OpenJDK 6,7
075      "sun.security.tools.keytool.CertAndKeyGen", // Oracle/Sun/OpenJDK 8
076      "com.ibm.security.x509.CertAndKeyGen",      // IBM SDK 7
077      "com.ibm.security.tools.CertAndKeyGen"      // IBM SDK 8
078    };
079
080  private static final PlatformIMPL IMPL;
081
082  /** The minimum java supported version. */
083  public static final String JAVA_MINIMUM_VERSION_NUMBER = "7.0";
084
085  static
086  {
087    String vendor = System.getProperty("java.vendor");
088
089    if (vendor.startsWith("IBM"))
090    {
091      pkgPrefix = IBM_SEC;
092    }
093    else
094    {
095      pkgPrefix = SUN_SEC;
096    }
097    IMPL = new DefaultPlatformIMPL();
098  }
099
100  /** Key size, key algorithm and signature algorithms used. */
101  public static enum KeyType
102  {
103    /** RSA key algorithm with 2048 bits size and SHA1withRSA signing algorithm. */
104    RSA("rsa", 2048, "SHA1WithRSA"),
105
106    /** Elliptic Curve key algorithm with 233 bits size and SHA1withECDSA signing algorithm. */
107    EC("ec", 256, "SHA1withECDSA");
108
109    /** Default key type used when none can be determined. */
110    public final static KeyType DEFAULT = RSA;
111
112    final String keyAlgorithm;
113    final int keySize;
114    final String signatureAlgorithm;
115
116    private KeyType(String keyAlgorithm, int keySize, String signatureAlgorithm)
117    {
118      this.keySize = keySize;
119      this.keyAlgorithm = keyAlgorithm;
120      this.signatureAlgorithm = signatureAlgorithm;
121    }
122
123    /**
124     * Check whether or not, this key type is supported by the current JVM.
125     * @return true if this key type is supported, false otherwise.
126     */
127    public boolean isSupported()
128    {
129      try
130      {
131        return KeyPairGenerator.getInstance(keyAlgorithm.toUpperCase()) != null;
132      }
133      catch (NoSuchAlgorithmException e)
134      {
135        return false;
136      }
137    }
138
139    /**
140     * Get a KeyType based on the alias name.
141     *
142     * @param alias
143     *          certificate alias
144     * @return KeyTpe deduced from the alias.
145     */
146    public static KeyType getTypeOrDefault(String alias)
147    {
148      try
149      {
150        return KeyType.valueOf(alias.substring(alias.lastIndexOf('-') + 1).toUpperCase());
151      }
152      catch (Exception e)
153      {
154        return KeyType.DEFAULT;
155      }
156    }
157  }
158
159  /**
160   * Platform base class. Performs all of the certificate management functions.
161   */
162  private static abstract class PlatformIMPL
163  {
164    /** Time values used in validity calculations. */
165    private static final int SEC_IN_DAY = 24 * 60 * 60;
166
167    /** Methods pulled from the classes. */
168    private static final String GENERATE_METHOD = "generate";
169    private static final String GET_PRIVATE_KEY_METHOD = "getPrivateKey";
170    private static final String GET_SELFSIGNED_CERT_METHOD =
171      "getSelfCertificate";
172
173    /** Classes needed to manage certificates. */
174    private static final Class<?> certKeyGenClass, X500NameClass;
175
176    /** Constructors for each of the above classes. */
177    private static Constructor<?> certKeyGenCons, X500NameCons;
178
179    /** Filesystem APIs */
180
181    static
182    {
183
184      String certAndKeyGen = getCertAndKeyGenClassName();
185      if(certAndKeyGen == null)
186      {
187        LocalizableMessage msg = ERR_CERTMGR_CERTGEN_NOT_FOUND.get(CERTANDKEYGEN_PROVIDER);
188        throw new ExceptionInInitializerError(msg.toString());
189      }
190
191      String X500Name = pkgPrefix + ".x509.X500Name";
192      try
193      {
194        certKeyGenClass = Class.forName(certAndKeyGen);
195        X500NameClass = Class.forName(X500Name);
196        certKeyGenCons = certKeyGenClass.getConstructor(String.class,
197            String.class);
198        X500NameCons = X500NameClass.getConstructor(String.class);
199      }
200      catch (ClassNotFoundException e)
201      {
202        LocalizableMessage msg = ERR_CERTMGR_CLASS_NOT_FOUND.get(e.getMessage());
203        throw new ExceptionInInitializerError(msg.toString());
204      }
205      catch (SecurityException e)
206      {
207        LocalizableMessage msg = ERR_CERTMGR_SECURITY.get(e.getMessage());
208        throw new ExceptionInInitializerError(msg.toString());
209      }
210      catch (NoSuchMethodException e)
211      {
212        LocalizableMessage msg = ERR_CERTMGR_NO_METHOD.get(e.getMessage());
213        throw new ExceptionInInitializerError(msg.toString());
214      }
215    }
216
217    /**
218     * Try to decide which CertAndKeyGen class to use.
219     *
220     * @return a fully qualified class name or null
221     */
222    private static String getCertAndKeyGenClassName() {
223      String certAndKeyGen = System.getProperty(CERTANDKEYGEN_PROVIDER);
224      if (certAndKeyGen != null)
225      {
226        return certAndKeyGen;
227      }
228
229      for (String className : CERTANDKEYGEN_PATHS)
230      {
231        if (classExists(className))
232        {
233          return className;
234        }
235      }
236      return null;
237    }
238
239    /**
240     * A quick check to see if a class can be loaded. Doesn't check if
241     * it can be instantiated.
242     *
243     * @param className full class name to check
244     * @return true if the class is found
245     */
246    private static boolean classExists(final String className)
247    {
248      try {
249        Class clazz = Class.forName(className);
250        return true;
251      } catch (ClassNotFoundException | ClassCastException e) {
252        return false;
253      }
254    }
255
256    protected PlatformIMPL()
257    {
258    }
259
260
261
262    private final void deleteAlias(KeyStore ks, String ksPath, String alias,
263        char[] pwd) throws KeyStoreException
264    {
265      try
266      {
267        if (ks == null)
268        {
269          LocalizableMessage msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get();
270          throw new KeyStoreException(msg.toString());
271        }
272        ks.deleteEntry(alias);
273        try (final FileOutputStream fs = new FileOutputStream(ksPath))
274        {
275          ks.store(fs, pwd);
276        }
277      }
278      catch (Exception e)
279      {
280        throw new KeyStoreException(ERR_CERTMGR_DELETE_ALIAS.get(alias, e.getMessage()).toString(), e);
281      }
282    }
283
284
285
286    private final void addCertificate(KeyStore ks, String ksType, String ksPath,
287        String alias, char[] pwd, String certPath) throws KeyStoreException
288    {
289      try
290      {
291        CertificateFactory cf = CertificateFactory.getInstance("X509");
292        InputStream inStream = new FileInputStream(certPath);
293        if (ks == null)
294        {
295          ks = KeyStore.getInstance(ksType);
296          ks.load(null, pwd);
297        }
298        // Do not support certificate replies.
299        if (ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class))
300        {
301          LocalizableMessage msg = ERR_CERTMGR_CERT_REPLIES_INVALID.get(alias);
302          throw new KeyStoreException(msg.toString());
303        }
304        else if (!ks.containsAlias(alias)
305            || ks
306                .entryInstanceOf(alias, KeyStore.TrustedCertificateEntry.class))
307        {
308          trustedCert(alias, cf, ks, inStream);
309        }
310        else
311        {
312          LocalizableMessage msg = ERR_CERTMGR_ALIAS_INVALID.get(alias);
313          throw new KeyStoreException(msg.toString());
314        }
315        FileOutputStream fileOutStream = new FileOutputStream(ksPath);
316        ks.store(fileOutStream, pwd);
317        fileOutStream.close();
318        inStream.close();
319      }
320      catch (Exception e)
321      {
322        throw new KeyStoreException(ERR_CERTMGR_ADD_CERT.get(alias, e.getMessage()).toString(), e);
323      }
324    }
325
326
327
328    private static final KeyStore generateSelfSignedCertificate(KeyStore ks,
329        String ksType, String ksPath, KeyType keyType, String alias, char[] pwd, String dn,
330        int validity) throws KeyStoreException
331    {
332      try
333      {
334        if (ks == null)
335        {
336          ks = KeyStore.getInstance(ksType);
337          ks.load(null, pwd);
338        }
339        else if (ks.containsAlias(alias))
340        {
341          LocalizableMessage msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
342          throw new KeyStoreException(msg.toString());
343        }
344
345        try (final FileOutputStream fileOutStream = new FileOutputStream(ksPath))
346        {
347            final Object keypair = certKeyGenCons.newInstance(keyType.keyAlgorithm, keyType.signatureAlgorithm);
348
349            final Method certAndKeyGenGenerate = certKeyGenClass.getMethod(GENERATE_METHOD, int.class);
350            certAndKeyGenGenerate.invoke(keypair, keyType.keySize);
351
352            final Method certAndKeyGetPrivateKey = certKeyGenClass.getMethod(GET_PRIVATE_KEY_METHOD);
353            final Certificate[] certificateChain = new Certificate[1];
354            final Method getSelfCertificate =
355                certKeyGenClass.getMethod(GET_SELFSIGNED_CERT_METHOD, X500NameClass, long.class);
356
357            final int days = validity * SEC_IN_DAY;
358            final Object subject = X500NameCons.newInstance(dn);
359            certificateChain[0] = (Certificate) getSelfCertificate.invoke(keypair, subject, days);
360            ks.setKeyEntry(alias , (PrivateKey) certAndKeyGetPrivateKey.invoke(keypair), pwd, certificateChain);
361
362            ks.store(fileOutStream, pwd);
363        }
364      }
365      catch (Exception e)
366      {
367        throw new KeyStoreException(ERR_CERTMGR_GEN_SELF_SIGNED_CERT.get(alias, e.getMessage()).toString(), e);
368      }
369      return ks;
370    }
371
372
373
374    /**
375     * Generate a x509 certificate from the input stream. Verification is done
376     * only if it is self-signed.
377     */
378    private void trustedCert(String alias, CertificateFactory cf, KeyStore ks,
379        InputStream in) throws KeyStoreException
380    {
381      try
382      {
383        if (ks.containsAlias(alias))
384        {
385          LocalizableMessage msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
386          throw new KeyStoreException(msg.toString());
387        }
388        X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
389        if (isSelfSigned(cert))
390        {
391          cert.verify(cert.getPublicKey());
392        }
393        ks.setCertificateEntry(alias, cert);
394      }
395      catch (Exception e)
396      {
397        throw new KeyStoreException(ERR_CERTMGR_TRUSTED_CERT.get(alias, e.getMessage()).toString(), e);
398      }
399    }
400
401
402
403    /**
404     * Check that the issuer and subject DNs match.
405     */
406    private boolean isSelfSigned(X509Certificate cert)
407    {
408      return cert.getSubjectDN().equals(cert.getIssuerDN());
409    }
410
411
412
413    private long getUsableMemoryForCaching()
414    {
415      long youngGenSize = 0;
416      long oldGenSize = 0;
417
418      List<MemoryPoolMXBean> mpools = ManagementFactory.getMemoryPoolMXBeans();
419      for (MemoryPoolMXBean mpool : mpools)
420      {
421        MemoryUsage usage = mpool.getUsage();
422        if (usage != null)
423        {
424          String name = mpool.getName();
425          if (name.equalsIgnoreCase("PS Eden Space"))
426          {
427            // Parallel.
428            youngGenSize = usage.getMax();
429          }
430          else if (name.equalsIgnoreCase("PS Old Gen"))
431          {
432            // Parallel.
433            oldGenSize = usage.getMax();
434          }
435          else if (name.equalsIgnoreCase("Par Eden Space"))
436          {
437            // CMS.
438            youngGenSize = usage.getMax();
439          }
440          else if (name.equalsIgnoreCase("CMS Old Gen"))
441          {
442            // CMS.
443            oldGenSize = usage.getMax();
444          }
445        }
446      }
447
448      if (youngGenSize > 0 && oldGenSize > youngGenSize)
449      {
450        // We can calculate available memory based on GC info.
451        return oldGenSize - youngGenSize;
452      }
453      else if (oldGenSize > 0)
454      {
455        // Small old gen. It is going to be difficult to avoid full GCs if the
456        // young gen is bigger.
457        return oldGenSize * 40 / 100;
458      }
459      else
460      {
461        // Unknown GC (G1, JRocket, etc).
462        Runtime runTime = Runtime.getRuntime();
463        runTime.gc();
464        runTime.gc();
465        return (runTime.freeMemory() + (runTime.maxMemory() - runTime
466            .totalMemory())) * 40 / 100;
467      }
468    }
469  }
470
471
472
473  /** Prevent instantiation. */
474  private Platform()
475  {
476  }
477
478
479
480  /**
481   * Add the certificate in the specified path to the provided keystore;
482   * creating the keystore with the provided type and path if it doesn't exist.
483   *
484   * @param ks
485   *          The keystore to add the certificate to, may be null if it doesn't
486   *          exist.
487   * @param ksType
488   *          The type to use if the keystore is created.
489   * @param ksPath
490   *          The path to the keystore if it is created.
491   * @param alias
492   *          The alias to store the certificate under.
493   * @param pwd
494   *          The password to use in saving the certificate.
495   * @param certPath
496   *          The path to the file containing the certificate.
497   * @throws KeyStoreException
498   *           If an error occurred adding the certificate to the keystore.
499   */
500  public static void addCertificate(KeyStore ks, String ksType, String ksPath,
501      String alias, char[] pwd, String certPath) throws KeyStoreException
502  {
503    IMPL.addCertificate(ks, ksType, ksPath, alias, pwd, certPath);
504  }
505
506
507
508  /**
509   * Delete the specified alias from the provided keystore.
510   *
511   * @param ks
512   *          The keystore to delete the alias from.
513   * @param ksPath
514   *          The path to the keystore.
515   * @param alias
516   *          The alias to use in the request generation.
517   * @param pwd
518   *          The keystore password to use.
519   * @throws KeyStoreException
520   *           If an error occurred deleting the alias.
521   */
522  public static void deleteAlias(KeyStore ks, String ksPath, String alias,
523      char[] pwd) throws KeyStoreException
524  {
525    IMPL.deleteAlias(ks, ksPath, alias, pwd);
526  }
527
528
529
530  /**
531   * Generate a self-signed certificate using the specified alias, dn string and
532   * validity period. If the keystore does not exist, it will be created using
533   * the specified keystore type and path.
534   *
535   * @param ks
536   *          The keystore to save the certificate in. May be null if it does
537   *          not exist.
538   * @param keyType
539   *          The keystore type to use if the keystore is created.
540   * @param ksPath
541   *          The path to the keystore if the keystore is created.
542   * @param ksType
543   *          Specify the key size, key algorithm and signature algorithms used.
544   * @param alias
545   *          The alias to store the certificate under.
546   * @param pwd
547   *          The password to us in saving the certificate.
548   * @param dn
549   *          The dn string used as the certificate subject.
550   * @param validity
551   *          The validity of the certificate in days.
552   * @throws KeyStoreException
553   *           If the self-signed certificate cannot be generated.
554   */
555  public static void generateSelfSignedCertificate(KeyStore ks, String ksType,
556      String ksPath, KeyType keyType, String alias, char[] pwd, String dn, int validity)
557      throws KeyStoreException
558  {
559    PlatformIMPL.generateSelfSignedCertificate(ks, ksType, ksPath, keyType, alias, pwd, dn, validity);
560  }
561
562  /**
563   * Default platform class.
564   */
565  private static class DefaultPlatformIMPL extends PlatformIMPL
566  {
567  }
568
569
570
571  /**
572   * Test if a platform java vendor property starts with the specified vendor
573   * string.
574   *
575   * @param vendor
576   *          The vendor to check for.
577   * @return {@code true} if the java vendor starts with the specified vendor
578   *         string.
579   */
580  public static boolean isVendor(String vendor)
581  {
582    String javaVendor = System.getProperty("java.vendor");
583    return javaVendor.startsWith(vendor);
584  }
585
586
587
588  /**
589   * Calculates the usable memory which could potentially be used by the
590   * application for caching objects. This method <b>does not</b> look at the
591   * amount of free memory, but instead tries to query the JVM's GC settings in
592   * order to determine the amount of usable memory in the old generation (or
593   * equivalent). More specifically, applications may also need to take into
594   * account the amount of memory already in use, for example by performing the
595   * following:
596   *
597   * <pre>
598   * Runtime runTime = Runtime.getRuntime();
599   * runTime.gc();
600   * runTime.gc();
601   * long freeCommittedMemory = runTime.freeMemory();
602   * long uncommittedMemory = runTime.maxMemory() - runTime.totalMemory();
603   * long freeMemory = freeCommittedMemory + uncommittedMemory;
604   * </pre>
605   *
606   * @return The usable memory which could potentially be used by the
607   *         application for caching objects.
608   */
609  public static long getUsableMemoryForCaching()
610  {
611    return IMPL.getUsableMemoryForCaching();
612  }
613
614  /**
615   * Computes the number of replay/worker/cleaner threads based on the number of cpus in the system.
616   * Allows for a multiplier to be specified and a minimum value to be returned if not enough processors
617   * are present in the system.
618   *
619   * @param minimumValue at least this value should be returned.
620   * @param cpuMultiplier the scaling multiplier of the number of threads to return
621   * @return the number of threads based on the number of cpus in the system.
622   * @throws IllegalArgumentException if {@code cpuMultiplier} is a non positive number
623   */
624  public static int computeNumberOfThreads(int minimumValue, float cpuMultiplier)
625  {
626    Reject.ifTrue(cpuMultiplier < 0, "Multiplier must be a positive number");
627    return Math.max(minimumValue, (int)(Runtime.getRuntime().availableProcessors() * cpuMultiplier));
628  }
629}