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; 028 029import java.io.FileInputStream; 030import java.io.FileOutputStream; 031import java.io.InputStream; 032import java.lang.reflect.Constructor; 033import java.lang.reflect.Method; 034 035import org.forgerock.i18n.LocalizableMessage; 036import org.forgerock.util.Reject; 037 038import static org.opends.messages.UtilityMessages.*; 039import static org.opends.server.util.ServerConstants.CERTANDKEYGEN_PROVIDER; 040 041/** 042 * Provides a wrapper class that collects all of the JVM vendor and JDK version 043 * specific code in a single place. 044 */ 045public final class Platform 046{ 047 048 /** Prefix that determines which security package to use. */ 049 private static final String pkgPrefix; 050 051 /** The two security package prefixes (IBM and SUN). */ 052 private static final String IBM_SEC = "com.ibm.security"; 053 private static final String SUN_SEC = "sun.security"; 054 055 /** The CertAndKeyGen class is located in different packages depending on JVM environment. */ 056 private static final String[] CERTANDKEYGEN_PATHS = new String[] { 057 "sun.security.x509.CertAndKeyGen", // Oracle/Sun/OpenJDK 6,7 058 "sun.security.tools.keytool.CertAndKeyGen", // Oracle/Sun/OpenJDK 8 059 "com.ibm.security.x509.CertAndKeyGen", // IBM SDK 7 060 "com.ibm.security.tools.CertAndKeyGen" // IBM SDK 8 061 }; 062 063 private static final PlatformIMPL IMPL; 064 065 /** The minimum java supported version. */ 066 public static final String JAVA_MINIMUM_VERSION_NUMBER = "7.0"; 067 068 static 069 { 070 String vendor = System.getProperty("java.vendor"); 071 072 if (vendor.startsWith("IBM")) 073 { 074 pkgPrefix = IBM_SEC; 075 } 076 else 077 { 078 pkgPrefix = SUN_SEC; 079 } 080 IMPL = new DefaultPlatformIMPL(); 081 } 082 083 /** Key size, key algorithm and signature algorithms used. */ 084 public static enum KeyType 085 { 086 /** RSA key algorithm with 2048 bits size and SHA1withRSA signing algorithm. */ 087 RSA("rsa", 2048, "SHA1WithRSA"), 088 089 /** Elliptic Curve key algorithm with 233 bits size and SHA1withECDSA signing algorithm. */ 090 EC("ec", 256, "SHA1withECDSA"); 091 092 /** Default key type used when none can be determined. */ 093 public final static KeyType DEFAULT = RSA; 094 095 final String keyAlgorithm; 096 final int keySize; 097 final String signatureAlgorithm; 098 099 private KeyType(String keyAlgorithm, int keySize, String signatureAlgorithm) 100 { 101 this.keySize = keySize; 102 this.keyAlgorithm = keyAlgorithm; 103 this.signatureAlgorithm = signatureAlgorithm; 104 } 105 106 /** 107 * Check whether this key type is supported by the current JVM. 108 * @return true if this key type is supported, false otherwise. 109 */ 110 public boolean isSupported() 111 { 112 try 113 { 114 return KeyPairGenerator.getInstance(keyAlgorithm.toUpperCase()) != null; 115 } 116 catch (NoSuchAlgorithmException e) 117 { 118 return false; 119 } 120 } 121 122 /** 123 * Get a KeyType based on the alias name. 124 * 125 * @param alias 126 * certificate alias 127 * @return KeyTpe deduced from the alias. 128 */ 129 public static KeyType getTypeOrDefault(String alias) 130 { 131 try 132 { 133 return KeyType.valueOf(alias.substring(alias.lastIndexOf('-') + 1).toUpperCase()); 134 } 135 catch (Exception e) 136 { 137 return KeyType.DEFAULT; 138 } 139 } 140 } 141 142 /** 143 * Platform base class. Performs all of the certificate management functions. 144 */ 145 private static abstract class PlatformIMPL 146 { 147 /** Time values used in validity calculations. */ 148 private static final int SEC_IN_DAY = 24 * 60 * 60; 149 150 /** Methods pulled from the classes. */ 151 private static final String GENERATE_METHOD = "generate"; 152 private static final String GET_PRIVATE_KEY_METHOD = "getPrivateKey"; 153 private static final String GET_SELFSIGNED_CERT_METHOD = 154 "getSelfCertificate"; 155 156 /** Classes needed to manage certificates. */ 157 private static final Class<?> certKeyGenClass, X500NameClass; 158 159 /** Constructors for each of the above classes. */ 160 private static Constructor<?> certKeyGenCons, X500NameCons; 161 162 /** Filesystem APIs */ 163 164 static 165 { 166 167 String certAndKeyGen = getCertAndKeyGenClassName(); 168 if(certAndKeyGen == null) 169 { 170 LocalizableMessage msg = ERR_CERTMGR_CERTGEN_NOT_FOUND.get(CERTANDKEYGEN_PROVIDER); 171 throw new ExceptionInInitializerError(msg.toString()); 172 } 173 174 String X500Name = pkgPrefix + ".x509.X500Name"; 175 try 176 { 177 certKeyGenClass = Class.forName(certAndKeyGen); 178 X500NameClass = Class.forName(X500Name); 179 certKeyGenCons = certKeyGenClass.getConstructor(String.class, 180 String.class); 181 X500NameCons = X500NameClass.getConstructor(String.class); 182 } 183 catch (ClassNotFoundException e) 184 { 185 LocalizableMessage msg = ERR_CERTMGR_CLASS_NOT_FOUND.get(e.getMessage()); 186 throw new ExceptionInInitializerError(msg.toString()); 187 } 188 catch (SecurityException e) 189 { 190 LocalizableMessage msg = ERR_CERTMGR_SECURITY.get(e.getMessage()); 191 throw new ExceptionInInitializerError(msg.toString()); 192 } 193 catch (NoSuchMethodException e) 194 { 195 LocalizableMessage msg = ERR_CERTMGR_NO_METHOD.get(e.getMessage()); 196 throw new ExceptionInInitializerError(msg.toString()); 197 } 198 } 199 200 /** 201 * Try to decide which CertAndKeyGen class to use. 202 * 203 * @return a fully qualified class name or null 204 */ 205 private static String getCertAndKeyGenClassName() { 206 String certAndKeyGen = System.getProperty(CERTANDKEYGEN_PROVIDER); 207 if (certAndKeyGen != null) 208 { 209 return certAndKeyGen; 210 } 211 212 for (String className : CERTANDKEYGEN_PATHS) 213 { 214 if (classExists(className)) 215 { 216 return className; 217 } 218 } 219 return null; 220 } 221 222 /** 223 * A quick check to see if a class can be loaded. Doesn't check if 224 * it can be instantiated. 225 * 226 * @param className full class name to check 227 * @return true if the class is found 228 */ 229 private static boolean classExists(final String className) 230 { 231 try { 232 Class.forName(className); 233 return true; 234 } catch (ClassNotFoundException | ClassCastException e) { 235 return false; 236 } 237 } 238 239 protected PlatformIMPL() 240 { 241 } 242 243 244 245 private final void deleteAlias(KeyStore ks, String ksPath, String alias, 246 char[] pwd) throws KeyStoreException 247 { 248 try 249 { 250 if (ks == null) 251 { 252 LocalizableMessage msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get(); 253 throw new KeyStoreException(msg.toString()); 254 } 255 ks.deleteEntry(alias); 256 try (final FileOutputStream fs = new FileOutputStream(ksPath)) 257 { 258 ks.store(fs, pwd); 259 } 260 } 261 catch (Exception e) 262 { 263 throw new KeyStoreException(ERR_CERTMGR_DELETE_ALIAS.get(alias, e.getMessage()).toString(), e); 264 } 265 } 266 267 268 269 private final void addCertificate(KeyStore ks, String ksType, String ksPath, 270 String alias, char[] pwd, String certPath) throws KeyStoreException 271 { 272 try 273 { 274 CertificateFactory cf = CertificateFactory.getInstance("X509"); 275 if (ks == null) 276 { 277 ks = KeyStore.getInstance(ksType); 278 ks.load(null, pwd); 279 } 280 // Do not support certificate replies. 281 if (ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) 282 { 283 LocalizableMessage msg = ERR_CERTMGR_CERT_REPLIES_INVALID.get(alias); 284 throw new KeyStoreException(msg.toString()); 285 } 286 else if (!ks.containsAlias(alias) 287 || ks.entryInstanceOf(alias, KeyStore.TrustedCertificateEntry.class)) 288 { 289 try (InputStream inStream = new FileInputStream(certPath)) { 290 trustedCert(alias, cf, ks, inStream); 291 } 292 } 293 else 294 { 295 LocalizableMessage msg = ERR_CERTMGR_ALIAS_INVALID.get(alias); 296 throw new KeyStoreException(msg.toString()); 297 } 298 try (FileOutputStream fileOutStream = new FileOutputStream(ksPath)) { 299 ks.store(fileOutStream, pwd); 300 } 301 } 302 catch (Exception e) 303 { 304 throw new KeyStoreException(ERR_CERTMGR_ADD_CERT.get(alias, e.getMessage()).toString(), e); 305 } 306 } 307 308 309 310 private static final KeyStore generateSelfSignedCertificate(KeyStore ks, 311 String ksType, String ksPath, KeyType keyType, String alias, char[] pwd, String dn, 312 int validity) throws KeyStoreException 313 { 314 try 315 { 316 if (ks == null) 317 { 318 ks = KeyStore.getInstance(ksType); 319 ks.load(null, pwd); 320 } 321 else if (ks.containsAlias(alias)) 322 { 323 LocalizableMessage msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias); 324 throw new KeyStoreException(msg.toString()); 325 } 326 327 final Object keypair = newKeyPair(keyType); 328 final Object subject = newX500Name(dn); 329 generate(keypair, keyType.keySize); 330 final PrivateKey privateKey = getPrivateKey(keypair); 331 final Certificate[] certificateChain = new Certificate[] { 332 getSelfCertificate(keypair, subject, validity * SEC_IN_DAY) 333 }; 334 ks.setKeyEntry(alias, privateKey, pwd, certificateChain); 335 try (FileOutputStream fileOutStream = new FileOutputStream(ksPath)) { 336 ks.store(fileOutStream, pwd); 337 } 338 return ks; 339 } 340 catch (Exception e) 341 { 342 throw new KeyStoreException(ERR_CERTMGR_GEN_SELF_SIGNED_CERT.get(alias, e.getMessage()).toString(), e); 343 } 344 } 345 346 private static Object newKeyPair(KeyType keyType) throws Exception 347 { 348 return certKeyGenCons.newInstance(keyType.keyAlgorithm, keyType.signatureAlgorithm); 349 } 350 351 private static Object newX500Name(String dn) throws Exception 352 { 353 return X500NameCons.newInstance(dn); 354 } 355 356 private static void generate(Object keypair, int keySize) throws Exception 357 { 358 Method certAndKeyGenGenerate = certKeyGenClass.getMethod(GENERATE_METHOD, int.class); 359 certAndKeyGenGenerate.invoke(keypair, keySize); 360 } 361 362 private static PrivateKey getPrivateKey(Object keypair) throws Exception 363 { 364 Method certAndKeyGetPrivateKey = certKeyGenClass.getMethod(GET_PRIVATE_KEY_METHOD); 365 return (PrivateKey) certAndKeyGetPrivateKey.invoke(keypair); 366 } 367 368 private static Certificate getSelfCertificate(Object keypair, Object subject, int days) throws Exception 369 { 370 Method getSelfCertificate = certKeyGenClass.getMethod(GET_SELFSIGNED_CERT_METHOD, X500NameClass, long.class); 371 return (Certificate) getSelfCertificate.invoke(keypair, subject, days); 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 414 /** Prevent instantiation. */ 415 private Platform() 416 { 417 } 418 419 420 421 /** 422 * Add the certificate in the specified path to the provided keystore; 423 * creating the keystore with the provided type and path if it doesn't exist. 424 * 425 * @param ks 426 * The keystore to add the certificate to, may be null if it doesn't 427 * exist. 428 * @param ksType 429 * The type to use if the keystore is created. 430 * @param ksPath 431 * The path to the keystore if it is created. 432 * @param alias 433 * The alias to store the certificate under. 434 * @param pwd 435 * The password to use in saving the certificate. 436 * @param certPath 437 * The path to the file containing the certificate. 438 * @throws KeyStoreException 439 * If an error occurred adding the certificate to the keystore. 440 */ 441 public static void addCertificate(KeyStore ks, String ksType, String ksPath, 442 String alias, char[] pwd, String certPath) throws KeyStoreException 443 { 444 IMPL.addCertificate(ks, ksType, ksPath, alias, pwd, certPath); 445 } 446 447 448 449 /** 450 * Delete the specified alias from the provided keystore. 451 * 452 * @param ks 453 * The keystore to delete the alias from. 454 * @param ksPath 455 * The path to the keystore. 456 * @param alias 457 * The alias to use in the request generation. 458 * @param pwd 459 * The keystore password to use. 460 * @throws KeyStoreException 461 * If an error occurred deleting the alias. 462 */ 463 public static void deleteAlias(KeyStore ks, String ksPath, String alias, 464 char[] pwd) throws KeyStoreException 465 { 466 IMPL.deleteAlias(ks, ksPath, alias, pwd); 467 } 468 469 470 471 /** 472 * Generate a self-signed certificate using the specified alias, dn string and 473 * validity period. If the keystore does not exist, it will be created using 474 * the specified keystore type and path. 475 * 476 * @param ks 477 * The keystore to save the certificate in. May be null if it does 478 * not exist. 479 * @param keyType 480 * The keystore type to use if the keystore is created. 481 * @param ksPath 482 * The path to the keystore if the keystore is created. 483 * @param ksType 484 * Specify the key size, key algorithm and signature algorithms used. 485 * @param alias 486 * The alias to store the certificate under. 487 * @param pwd 488 * The password to us in saving the certificate. 489 * @param dn 490 * The dn string used as the certificate subject. 491 * @param validity 492 * The validity of the certificate in days. 493 * @throws KeyStoreException 494 * If the self-signed certificate cannot be generated. 495 */ 496 public static void generateSelfSignedCertificate(KeyStore ks, String ksType, 497 String ksPath, KeyType keyType, String alias, char[] pwd, String dn, int validity) 498 throws KeyStoreException 499 { 500 PlatformIMPL.generateSelfSignedCertificate(ks, ksType, ksPath, keyType, alias, pwd, dn, validity); 501 } 502 503 /** 504 * Default platform class. 505 */ 506 private static class DefaultPlatformIMPL extends PlatformIMPL 507 { 508 } 509 510 511 512 /** 513 * Test if a platform java vendor property starts with the specified vendor 514 * string. 515 * 516 * @param vendor 517 * The vendor to check for. 518 * @return {@code true} if the java vendor starts with the specified vendor 519 * string. 520 */ 521 public static boolean isVendor(String vendor) 522 { 523 String javaVendor = System.getProperty("java.vendor"); 524 return javaVendor.startsWith(vendor); 525 } 526 527 /** 528 * Computes the number of replay/worker/cleaner threads based on the number of cpus in the system. 529 * Allows for a multiplier to be specified and a minimum value to be returned if not enough processors 530 * are present in the system. 531 * 532 * @param minimumValue at least this value should be returned. 533 * @param cpuMultiplier the scaling multiplier of the number of threads to return 534 * @return the number of threads based on the number of cpus in the system. 535 * @throws IllegalArgumentException if {@code cpuMultiplier} is a non positive number 536 */ 537 public static int computeNumberOfThreads(int minimumValue, float cpuMultiplier) 538 { 539 Reject.ifTrue(cpuMultiplier < 0, "Multiplier must be a positive number"); 540 return Math.max(minimumValue, (int)(Runtime.getRuntime().availableProcessors() * cpuMultiplier)); 541 } 542}