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}