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