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}