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 2008-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS 026 */ 027package org.opends.server.util; 028 029import java.io.*; 030import java.security.*; 031import java.security.cert.Certificate; 032import java.util.ArrayList; 033import java.util.Enumeration; 034import org.forgerock.i18n.LocalizableMessage; 035import static org.opends.messages.UtilityMessages.*; 036import org.opends.server.util.Platform.KeyType; 037 038/** 039 * This class provides an interface for generating self-signed certificates and 040 * certificate signing requests, and for importing, exporting, and deleting 041 * certificates from a key store. It supports JKS, PKCS11, and PKCS12 key store 042 * types. 043 * <BR><BR> 044 This code uses the Platform class to perform all of the certificate 045 management. 046 */ 047@org.opends.server.types.PublicAPI( 048 stability=org.opends.server.types.StabilityLevel.VOLATILE, 049 mayInstantiate=true, 050 mayExtend=false, 051 mayInvoke=true) 052public final class CertificateManager { 053 054 /** 055 * The key store type value that should be used for the "JKS" key store. 056 */ 057 public static final String KEY_STORE_TYPE_JKS = "JKS"; 058 059 /** 060 * The key store type value that should be used for the "JCEKS" key store. 061 */ 062 public static final String KEY_STORE_TYPE_JCEKS = "JCEKS"; 063 064 /** 065 * The key store type value that should be used for the "PKCS11" key store. 066 */ 067 public static final String KEY_STORE_TYPE_PKCS11 = "PKCS11"; 068 069 /** 070 * The key store type value that should be used for the "PKCS12" key store. 071 */ 072 public static final String KEY_STORE_TYPE_PKCS12 = "PKCS12"; 073 074 /** 075 * The key store path value that must be used in conjunction with the PKCS11 076 * key store type. 077 */ 078 public static final String KEY_STORE_PATH_PKCS11 = "NONE"; 079 080 /** Error message strings. */ 081 private static final String KEYSTORE_PATH_MSG = "key store path"; 082 private static final String KEYSTORE_TYPE_MSG = "key store type"; 083 private static final String SUBJECT_DN_MSG = "subject DN"; 084 private static final String CERT_ALIAS_MSG = "certificate alias"; 085 private static final String CERT_REQUEST_FILE_MSG = 086 "certificate request file"; 087 /** The parsed key store backing this certificate manager. */ 088 private KeyStore keyStore; 089 090 /** The path to the key store that we should be using. */ 091 private final String keyStorePath; 092 /** The name of the key store type we are using. */ 093 private final String keyStoreType; 094 095 private final char[] password; 096 097 private Boolean realAliases; 098 099 /** 100 * Always return true. 101 * 102 * @return This always returns true; 103 */ 104 public static boolean mayUseCertificateManager() { 105 return true; 106 } 107 108 109 110 /** 111 * Creates a new certificate manager instance with the provided information. 112 * 113 * @param keyStorePath The path to the key store file, or "NONE" if the key 114 * store type is "PKCS11". For the other key store 115 * types, the file does not need to exist if a new 116 * self-signed certificate or certificate signing 117 * request is to be generated, although the directory 118 * containing the file must exist. The key store file 119 * must exist if import or export operations are to be 120 * performed. 121 * @param keyStoreType The key store type to use. It should be one of 122 * {@code KEY_STORE_TYPE_JKS}, 123 * {@code KEY_STORE_TYPE_JCEKS}, 124 * {@code KEY_STORE_TYPE_PKCS11}, or 125 * {@code KEY_STORE_TYPE_PKCS12}. 126 * @param keyStorePassword The password required to access the key store. 127 * It must not be {@code null}. 128 * @throws IllegalArgumentException If an argument is invalid or {@code null}. 129 * 130 */ 131 public CertificateManager(String keyStorePath, String keyStoreType, 132 String keyStorePassword) 133 throws IllegalArgumentException { 134 ensureValid(keyStorePath, KEYSTORE_PATH_MSG); 135 ensureValid(keyStoreType, KEYSTORE_TYPE_MSG); 136 if (keyStoreType.equals(KEY_STORE_TYPE_PKCS11)) { 137 if (! keyStorePath.equals(KEY_STORE_PATH_PKCS11)) { 138 LocalizableMessage msg = 139 ERR_CERTMGR_INVALID_PKCS11_PATH.get(KEY_STORE_PATH_PKCS11); 140 throw new IllegalArgumentException(msg.toString()); 141 } 142 } else if (keyStoreType.equals(KEY_STORE_TYPE_JKS) || 143 keyStoreType.equals(KEY_STORE_TYPE_JCEKS) || 144 keyStoreType.equals(KEY_STORE_TYPE_PKCS12)) { 145 File keyStoreFile = new File(keyStorePath); 146 if (keyStoreFile.exists()) { 147 if (! keyStoreFile.isFile()) { 148 LocalizableMessage msg = ERR_CERTMGR_INVALID_KEYSTORE_PATH.get(keyStorePath); 149 throw new IllegalArgumentException(msg.toString()); 150 } 151 } else { 152 final File keyStoreDirectory = keyStoreFile.getParentFile(); 153 if (keyStoreDirectory == null || !keyStoreDirectory.exists() || !keyStoreDirectory.isDirectory()) { 154 LocalizableMessage msg = ERR_CERTMGR_INVALID_PARENT.get(keyStorePath); 155 throw new IllegalArgumentException(msg.toString()); 156 } 157 } 158 } else { 159 LocalizableMessage msg = ERR_CERTMGR_INVALID_STORETYPE.get( 160 KEY_STORE_TYPE_JKS, KEY_STORE_TYPE_JCEKS, 161 KEY_STORE_TYPE_PKCS11, KEY_STORE_TYPE_PKCS12); 162 throw new IllegalArgumentException(msg.toString()); 163 } 164 this.keyStorePath = keyStorePath; 165 this.keyStoreType = keyStoreType; 166 this.password = 167 keyStorePassword == null ? null : keyStorePassword.toCharArray(); 168 keyStore = null; 169 } 170 171 172 173 /** 174 * Indicates whether the provided alias is in use in the key store. 175 * 176 * @param alias The alias for which to make the determination. It must not 177 * be {@code null} or empty. 178 * 179 * @return {@code true} if the key store exist and already contains a 180 * certificate with the given alias, or {@code false} if not. 181 * 182 * @throws KeyStoreException If a problem occurs while attempting to 183 * interact with the key store. 184 */ 185 public boolean aliasInUse(final String alias) 186 throws KeyStoreException { 187 ensureValid(alias, CERT_ALIAS_MSG); 188 KeyStore keyStore = getKeyStore(); 189 return keyStore != null && keyStore.containsAlias(alias); 190 } 191 192 193 194 /** 195 * Retrieves the aliases of the certificates in the specified key store. 196 * 197 * @return The aliases of the certificates in the specified key store, or 198 * {@code null} if the key store does not exist. 199 * 200 * @throws KeyStoreException If a problem occurs while attempting to 201 * interact with the key store. 202 */ 203 public String[] getCertificateAliases() throws KeyStoreException { 204 Enumeration<String> aliasEnumeration = null; 205 KeyStore keyStore = getKeyStore(); 206 if (keyStore == null) 207 { 208 return null; 209 } 210 aliasEnumeration = keyStore.aliases(); 211 if (aliasEnumeration == null) 212 { 213 return new String[0]; 214 } 215 ArrayList<String> aliasList = new ArrayList<>(); 216 while (aliasEnumeration.hasMoreElements()) 217 { 218 aliasList.add(aliasEnumeration.nextElement()); 219 } 220 String[] aliases = new String[aliasList.size()]; 221 return aliasList.toArray(aliases); 222 } 223 224 225 226 /** 227 * Retrieves the certificate with the specified alias from the key store. 228 * 229 * @param alias The alias of the certificate to retrieve. It must not be 230 * {@code null} or empty. 231 * 232 * @return The requested certificate, or {@code null} if the specified 233 * certificate does not exist. 234 * 235 * @throws KeyStoreException If a problem occurs while interacting with the 236 * key store, or the key store does not exist.. 237 */ 238 public Certificate getCertificate(String alias) 239 throws KeyStoreException { 240 ensureValid(alias, CERT_ALIAS_MSG); 241 KeyStore ks = getKeyStore(); 242 if (ks == null) { 243 LocalizableMessage msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get(); 244 throw new KeyStoreException(msg.toString()); 245 } 246 return ks.getCertificate(alias); 247 } 248 249 250 /** 251 * Generates a self-signed certificate using the provided information. 252 * 253 * @param keyType Specifies the key size, key and signature algorithms. 254 * @param alias The nickname to use for the certificate in the key 255 * store. For the server certificate, it should generally 256 * be "server-cert". It must not be {@code null} or empty. 257 * @param subjectDN The subject DN to use for the certificate. It must not 258 * be {@code null} or empty. 259 * @param validity The length of time in days that the certificate should 260 * be valid, starting from the time the certificate is 261 * generated. It must be a positive integer value. 262 * @throws KeyStoreException If a problem occurs while actually attempting 263 * to generate the certificate in the key store. 264 *@throws IllegalArgumentException If the validity parameter is not a 265 * positive integer, or the alias is already 266 * in the keystore. 267 */ 268 public void generateSelfSignedCertificate(KeyType keyType, String alias, String subjectDN, 269 int validity) 270 throws KeyStoreException, IllegalArgumentException { 271 ensureValid(alias, CERT_ALIAS_MSG); 272 ensureValid(subjectDN, SUBJECT_DN_MSG); 273 if (validity <= 0) { 274 LocalizableMessage msg = ERR_CERTMGR_VALIDITY.get(validity); 275 throw new IllegalArgumentException(msg.toString()); 276 } 277 if (aliasInUse(alias)) { 278 LocalizableMessage msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias); 279 throw new IllegalArgumentException(msg.toString()); 280 } 281 keyStore = null; 282 Platform.generateSelfSignedCertificate(getKeyStore(), keyStoreType, 283 keyStorePath, keyType, alias, password, subjectDN, validity); 284 } 285 286 287 288 /** 289 * Adds the provided certificate to the key store. This may be used to 290 * associate an externally-signed certificate with an existing private key 291 * with the given alias. 292 * 293 * @param alias The alias to use for the certificate. It must not 294 * be {@code null} or empty. 295 * @param certificateFile The file containing the encoded certificate. It 296 * must not be {@code null}, and the file must exist. 297 298 * @throws KeyStoreException If a problem occurs while interacting with the 299 * key store. 300 * 301 *@throws IllegalArgumentException If the certificate file is not valid. 302 */ 303 public void addCertificate(String alias, File certificateFile) 304 throws KeyStoreException, IllegalArgumentException { 305 ensureValid(alias, CERT_ALIAS_MSG); 306 ensureFileValid(certificateFile, CERT_REQUEST_FILE_MSG); 307 if (!certificateFile.exists() || !certificateFile.isFile()) { 308 LocalizableMessage msg = ERR_CERTMGR_INVALID_CERT_FILE.get( 309 certificateFile.getAbsolutePath()); 310 throw new IllegalArgumentException(msg.toString()); 311 } 312 keyStore = null; 313 Platform.addCertificate(getKeyStore(), keyStoreType, keyStorePath, alias, 314 password, certificateFile.getAbsolutePath()); 315 } 316 317 318 /** 319 * Removes the specified certificate from the key store. 320 * 321 * @param alias The alias to use for the certificate to remove. It must not 322 * be {@code null} or an empty string, and it must exist in 323 * the key store. 324 * 325 * @throws KeyStoreException If a problem occurs while interacting with the 326 * key store. 327 *@throws IllegalArgumentException If the alias is in use and cannot be 328 * deleted. 329 */ 330 public void removeCertificate(String alias) 331 throws KeyStoreException, IllegalArgumentException { 332 ensureValid(alias, CERT_ALIAS_MSG); 333 if (!aliasInUse(alias)) { 334 LocalizableMessage msg = ERR_CERTMGR_ALIAS_CAN_NOT_DELETE.get(alias); 335 throw new IllegalArgumentException(msg.toString()); 336 } 337 keyStore = null; 338 Platform.deleteAlias(getKeyStore(), keyStorePath, alias, password); 339 } 340 341 342 /** 343 * Retrieves a handle to the key store. 344 * 345 * @return The handle to the key store, or {@code null} if the key store 346 * doesn't exist. 347 * 348 * @throws KeyStoreException If a problem occurs while trying to open the 349 * key store. 350 */ 351 private KeyStore getKeyStore() 352 throws KeyStoreException 353 { 354 if (keyStore != null) 355 { 356 return keyStore; 357 } 358 359 // For JKS and PKCS12 key stores, we should make sure the file exists, and 360 // we'll need an input stream that we can use to read it. For PKCS11 key 361 // stores there won't be a file and the input stream should be null. 362 FileInputStream keyStoreInputStream = null; 363 if (keyStoreType.equals(KEY_STORE_TYPE_JKS) || 364 keyStoreType.equals(KEY_STORE_TYPE_JCEKS) || 365 keyStoreType.equals(KEY_STORE_TYPE_PKCS12)) 366 { 367 final File keyStoreFile = new File(keyStorePath); 368 if (! keyStoreFile.exists()) 369 { 370 return null; 371 } 372 373 try 374 { 375 keyStoreInputStream = new FileInputStream(keyStoreFile); 376 } 377 catch (final Exception e) 378 { 379 throw new KeyStoreException(String.valueOf(e), e); 380 } 381 } 382 383 384 final KeyStore keyStore = KeyStore.getInstance(keyStoreType); 385 try 386 { 387 keyStore.load(keyStoreInputStream, password); 388 return this.keyStore = keyStore; 389 } 390 catch (final Exception e) 391 { 392 throw new KeyStoreException(String.valueOf(e), e); 393 } 394 finally 395 { 396 if (keyStoreInputStream != null) 397 { 398 try 399 { 400 keyStoreInputStream.close(); 401 } 402 catch (final Throwable t) 403 { 404 } 405 } 406 } 407 } 408 409 /** 410 * Returns whether this certificate manager contains 'real' aliases or not. 411 * For instance, the certificate manager can contain a PKCS12 certificate 412 * with no alias. 413 * @return whether this certificate manager contains 'real' aliases or not. 414 * @throws KeyStoreException if there is a problem accessing the key store. 415 */ 416 public boolean hasRealAliases() throws KeyStoreException 417 { 418 if (realAliases == null) 419 { 420 String[] aliases = getCertificateAliases(); 421 if (aliases == null || aliases.length == 0) 422 { 423 realAliases = Boolean.FALSE; 424 } 425 else if (aliases.length > 1) 426 { 427 realAliases = Boolean.TRUE; 428 } 429 else 430 { 431 CertificateManager certManager2 = new CertificateManager(keyStorePath, 432 keyStoreType, new String(password)); 433 String[] aliases2 = certManager2.getCertificateAliases(); 434 if (aliases2 != null && aliases2.length == 1) 435 { 436 realAliases = aliases[0].equalsIgnoreCase(aliases2[0]); 437 } 438 else 439 { 440 realAliases = Boolean.FALSE; 441 } 442 } 443 } 444 return realAliases; 445 } 446 447 private static void ensureFileValid(File arg, String msgStr) { 448 if(arg == null) { 449 LocalizableMessage msg = ERR_CERTMGR_FILE_NAME_INVALID.get(msgStr); 450 throw new NullPointerException(msg.toString()); 451 } 452 } 453 454 private static void ensureValid(String arg, String msgStr) { 455 if(arg == null || arg.length() == 0) { 456 LocalizableMessage msg = ERR_CERTMGR_VALUE_INVALID.get(msgStr); 457 throw new NullPointerException(msg.toString()); 458 } 459 } 460}