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