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