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}