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
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.util;
028
029
030
031 import java.io.ByteArrayOutputStream;
032 import java.io.FileInputStream;
033 import java.io.InputStream;
034 import java.io.IOException;
035 import java.io.File;
036 import java.io.OutputStream;
037 import java.security.KeyStore;
038 import java.security.KeyStoreException;
039 import java.security.cert.Certificate;
040 import java.util.ArrayList;
041 import java.util.Enumeration;
042
043 import org.opends.server.types.OperatingSystem;
044
045
046 /**
047 * This class provides an interface for generating self-signed certificates and
048 * certificate signing requests, and for importing, exporting, and deleting
049 * certificates from a key store. It supports JKS, PKCS11, and PKCS12 key store
050 * types.
051 * <BR><BR>
052 * Note that for some operations, particularly those that require updating the
053 * contents of a key store (including generating certificates and/or certificate
054 * signing requests, importing certificates, or removing certificates), this
055 * class relies on the keytool utility provided with Sun's implementation of the
056 * Java runtime environment. It will perform the associated operations by
057 * invoking the appropriate command. It is possible that the keytool command
058 * will not exist in all Java runtime environments, especially those not created
059 * by Sun. In those cases, it will not be possible to invoke operations that
060 * require altering the contents of the key store. Therefore, it is strongly
061 * recommended that any code that may want to make use of this facility should
062 * first call {@code mayUseCertificateManager} and if it returns {@code false}
063 * the caller should gracefully degrade and suggest that the user perform the
064 * operation manually.
065 */
066 @org.opends.server.types.PublicAPI(
067 stability=org.opends.server.types.StabilityLevel.VOLATILE,
068 mayInstantiate=true,
069 mayExtend=false,
070 mayInvoke=true)
071 public final class CertificateManager
072 {
073 /**
074 * The path to the keytool command, which will be required to perform
075 * operations that modify the contents of a key store.
076 */
077 public static final String KEYTOOL_COMMAND;
078
079
080
081 /**
082 * The key store type value that should be used for the "JKS" key store.
083 */
084 public static final String KEY_STORE_TYPE_JKS = "JKS";
085
086
087
088 /**
089 * The key store type value that should be used for the "PKCS11" key store.
090 */
091 public static final String KEY_STORE_TYPE_PKCS11 = "PKCS11";
092
093
094
095 /**
096 * The key store type value that should be used for the "PKCS12" key store.
097 */
098 public static final String KEY_STORE_TYPE_PKCS12 = "PKCS12";
099
100
101
102 /**
103 * The key store path value that must be used in conjunction with the PKCS11
104 * key store type.
105 */
106 public static final String KEY_STORE_PATH_PKCS11 = "NONE";
107
108
109
110 // The parsed key store backing this certificate manager.
111 private KeyStore keyStore;
112
113 // The password that should be used to interact with the key store.
114 private String keyStorePIN;
115
116 // The path to the key store that we should be using.
117 private String keyStorePath;
118
119 // The name of the key store type we are using.
120 private String keyStoreType;
121
122
123
124 static
125 {
126 String keytoolCommand = null;
127
128 try
129 {
130 String cmd = System.getProperty("java.home") + File.separator + "bin" +
131 File.separator + "keytool";
132 File cmdFile = new File(cmd);
133 if (cmdFile.exists())
134 {
135 keytoolCommand = cmdFile.getAbsolutePath();
136 }
137 else
138 {
139 cmd = cmd + ".exe";
140 cmdFile = new File(cmd);
141 if (cmdFile.exists())
142 {
143 keytoolCommand = cmdFile.getAbsolutePath();
144 }
145 else
146 {
147 keytoolCommand = null;
148 }
149 }
150 }
151 catch (Exception e)
152 {
153 keytoolCommand = null;
154 }
155
156 KEYTOOL_COMMAND = SetupUtils.getScriptPath(keytoolCommand);
157 }
158
159
160
161 /**
162 * Indicates whether it is possible to use this certificate manager code to
163 * perform operations which may alter the contents of a key store.
164 *
165 * @return {@code true} if it appears that the keytool utility is available
166 * and may be used to execute commands that may alter the contents of
167 * a key store, or {@code false} if not.
168 */
169 public static boolean mayUseCertificateManager()
170 {
171 return (KEYTOOL_COMMAND != null);
172 }
173
174
175
176 /**
177 * Creates a new certificate manager instance with the provided information.
178 *
179 * @param keyStorePath The path to the key store file, or "NONE" if the key
180 * store type is "PKCS11". For the other key store
181 * types, the file does not need to exist if a new
182 * self-signed certificate or certificate signing
183 * request is to be generated, although the directory
184 * containing the file must exist. The key store file
185 * must exist if import or export operations are to be
186 * performed.
187 * @param keyStoreType The key store type to use. It should be one of
188 * {@code KEY_STORE_TYPE_JKS},
189 * {@code KEY_STORE_TYPE_PKCS11}, or
190 * {@code KEY_STORE_TYPE_PKCS12}.
191 * @param keyStorePIN The PIN required to access the key store. It must
192 * not be {@code null}.
193 *
194 * @throws IllegalArgumentException If any of the provided arguments is
195 * invalid.
196 *
197 * @throws NullPointerException If any of the provided arguments is
198 * {@code null}.
199 *
200 * @throws UnsupportedOperationException If it is not possible to use the
201 * certificate manager on the
202 * underlying platform.
203 */
204 public CertificateManager(String keyStorePath, String keyStoreType,
205 String keyStorePIN)
206 throws IllegalArgumentException, NullPointerException,
207 UnsupportedOperationException
208 {
209 if ((keyStorePath == null) || (keyStorePath.length() == 0))
210 {
211 throw new NullPointerException("keyStorePath");
212 }
213 else if ((keyStoreType == null) || (keyStoreType.length() == 0))
214 {
215 throw new NullPointerException("keyStoreType");
216 }
217 else if ((keyStorePIN == null) || (keyStorePIN.length() == 0))
218 {
219 throw new NullPointerException("keyStorePIN");
220 }
221
222
223 if (keyStoreType.equals(KEY_STORE_TYPE_PKCS11))
224 {
225 if (! keyStorePath.equals(KEY_STORE_PATH_PKCS11))
226 {
227 // FIXME -- Make this an internationalizeable string.
228 throw new IllegalArgumentException("Invalid key store path for " +
229 "PKCS11 keystore -- it must be " +
230 KEY_STORE_PATH_PKCS11);
231 }
232 }
233 else if (keyStoreType.equals(KEY_STORE_TYPE_JKS) ||
234 keyStoreType.equals(KEY_STORE_TYPE_PKCS12))
235 {
236 File keyStoreFile = new File(keyStorePath);
237 if (keyStoreFile.exists())
238 {
239 if (! keyStoreFile.isFile())
240 {
241 // FIXME -- Make this an internationalizeable string.
242 throw new IllegalArgumentException("Key store path " + keyStorePath +
243 " exists but is not a file.");
244 }
245 }
246 else
247 {
248 File keyStoreDirectory = keyStoreFile.getParentFile();
249 if ((keyStoreDirectory == null) || (! keyStoreDirectory.exists()) ||
250 (! keyStoreDirectory.isDirectory()))
251 {
252 // FIXME -- Make this an internationalizeable string.
253 throw new IllegalArgumentException("Parent directory for key " +
254 "store path " + keyStorePath + " does not exist or " +
255 "is not a directory.");
256 }
257 }
258 }
259 else
260 {
261 // FIXME -- Make this an internationalizeable string.
262 throw new IllegalArgumentException("Invalid key store type -- it must " +
263 "be one of " + KEY_STORE_TYPE_JKS + ", " +
264 KEY_STORE_TYPE_PKCS11 + ", or " + KEY_STORE_TYPE_PKCS12);
265 }
266
267
268 this.keyStorePath = keyStorePath;
269 this.keyStoreType = keyStoreType;
270 this.keyStorePIN = keyStorePIN;
271
272 keyStore = null;
273 }
274
275
276
277 /**
278 * Indicates whether the provided alias is in use in the key store.
279 *
280 * @param alias The alias for which to make the determination. It must not
281 * be {@code null} or empty.
282 *
283 * @return {@code true} if the key store exist and already contains a
284 * certificate with the given alias, or {@code false} if not.
285 *
286 * @throws KeyStoreException If a problem occurs while attempting to
287 * interact with the key store.
288 *
289 * @throws NullPointerException If the provided alias is {@code null} or a
290 * zero-length string.
291 */
292 public boolean aliasInUse(String alias)
293 throws KeyStoreException, NullPointerException
294 {
295 if ((alias == null) || (alias.length() == 0))
296 {
297 throw new NullPointerException("alias");
298 }
299
300
301 KeyStore keyStore = getKeyStore();
302 if (keyStore == null)
303 {
304 return false;
305 }
306
307 return keyStore.containsAlias(alias);
308 }
309
310
311
312 /**
313 * Retrieves the aliases of the certificates in the specified key store.
314 *
315 * @return The aliases of the certificates in the specified key store, or
316 * {@code null} if the key store does not exist.
317 *
318 * @throws KeyStoreException If a problem occurs while attempting to
319 * interact with the key store.
320 */
321 public String[] getCertificateAliases()
322 throws KeyStoreException
323 {
324 KeyStore keyStore = getKeyStore();
325 if (keyStore == null)
326 {
327 return null;
328 }
329
330 Enumeration<String> aliasEnumeration = keyStore.aliases();
331 if (aliasEnumeration == null)
332 {
333 return new String[0];
334 }
335
336 ArrayList<String> aliasList = new ArrayList<String>();
337 while (aliasEnumeration.hasMoreElements())
338 {
339 aliasList.add(aliasEnumeration.nextElement());
340 }
341
342
343 String[] aliases = new String[aliasList.size()];
344 return aliasList.toArray(aliases);
345 }
346
347
348
349 /**
350 * Retrieves the certificate with the specified alias from the key store.
351 *
352 * @param alias The alias of the certificate to retrieve. It must not be
353 * {@code null} or empty.
354 *
355 * @return The requested certificate, or {@code null} if the specified
356 * certificate does not exist.
357 *
358 * @throws KeyStoreException If a problem occurs while interacting with the
359 * key store, or the key store does not exist.
360 *
361 * @throws NullPointerException If the provided alias is {@code null} or a
362 * zero-length string.
363 */
364 public Certificate getCertificate(String alias)
365 throws KeyStoreException, NullPointerException
366 {
367 if ((alias == null) || (alias.length() == 0))
368 {
369 throw new NullPointerException("alias");
370 }
371
372 KeyStore keyStore = getKeyStore();
373 if (keyStore == null)
374 {
375 // FIXME -- Make this an internationalizeable string.
376 throw new KeyStoreException("The key store does not exist.");
377 }
378
379 return keyStore.getCertificate(alias);
380 }
381
382
383
384 /**
385 * Generates a self-signed certificate using the provided information.
386 *
387 * @param alias The nickname to use for the certificate in the key
388 * store. For the server certificate, it should generally
389 * be "server-cert". It must not be {@code null} or empty.
390 * @param subjectDN The subject DN to use for the certificate. It must not
391 * be {@code null} or empty.
392 * @param validity The length of time in days that the certificate should
393 * be valid, starting from the time the certificate is
394 * generated. It must be a positive integer value.
395 *
396 * @throws IllegalArgumentException If the validity is not positive.
397 *
398 * @throws KeyStoreException If a problem occurs while actually attempting
399 * to generate the certificate in the key store.
400 *
401 * @throws NullPointerException If either the alias or subject DN is null or
402 * a zero-length string.
403 *
404 * @throws UnsupportedOperationException If it is not possible to use the
405 * keytool utility to alter the
406 * contents of the key store.
407 */
408 public void generateSelfSignedCertificate(String alias, String subjectDN,
409 int validity)
410 throws KeyStoreException, IllegalArgumentException,
411 NullPointerException, UnsupportedOperationException
412 {
413 if ((alias == null) || (alias.length() == 0))
414 {
415 throw new NullPointerException("alias");
416 }
417 else if ((subjectDN == null) || (subjectDN.length() == 0))
418 {
419 throw new NullPointerException("subjectDN");
420 }
421 else if (validity <= 0)
422 {
423 // FIXME -- Make this an internationalizeable string.
424 throw new IllegalArgumentException("The validity must be positive.");
425 }
426
427 if (KEYTOOL_COMMAND == null)
428 {
429 // FIXME -- Make this an internationalizeable string.
430 throw new UnsupportedOperationException("The certificate manager may " +
431 "not be used to alter the contents of key stores on " +
432 "this system.");
433 }
434
435 if (aliasInUse(alias))
436 {
437 // FIXME -- Make this an internationalizeable string.
438 throw new IllegalArgumentException("A certificate with alias " + alias +
439 " already exists in the key store.");
440 }
441
442
443 // Clear the reference to the key store, since it will be altered by
444 // invoking the KeyTool command.
445 keyStore = null;
446
447 // First, we need to run with the "-genkey" command to create the private
448 // key.
449 String[] commandElements =
450 {
451 KEYTOOL_COMMAND,
452 getGenKeyCommand(),
453 "-alias", alias,
454 "-dname", subjectDN,
455 "-keyalg", "rsa",
456 "-keystore", keyStorePath,
457 "-storetype", keyStoreType
458 };
459 runKeyTool(commandElements, keyStorePIN, keyStorePIN, true);
460
461 // Next, we need to run with the "-selfcert" command to self-sign the
462 // certificate.
463 commandElements = new String[]
464 {
465 KEYTOOL_COMMAND,
466 "-selfcert",
467 "-alias", alias,
468 "-validity", String.valueOf(validity),
469 "-keystore", keyStorePath,
470 "-storetype", keyStoreType
471 };
472 runKeyTool(commandElements, keyStorePIN, keyStorePIN, true);
473 }
474
475
476
477 /**
478 * Generates a certificate signing request (CSR) using the provided
479 * information.
480 *
481 * @param alias The nickname to use for the certificate in the key
482 * store. For the server certificate, it should generally
483 * be "server-cert". It must not be {@code null} or empty.
484 * @param subjectDN The subject DN to use for the certificate. It must not
485 * be {@code null} or empty.
486 *
487 * @return The file containing the generated certificate signing request.
488 *
489 * @throws KeyStoreException If a problem occurs while actually attempting
490 * to generate the private key in the key store or
491 * generate the certificate signing request based
492 * on that key.
493 *
494 * @throws IOException If a problem occurs while attempting to create the
495 * file to which the certificate signing request will be
496 * written.
497 *
498 * @throws NullPointerException If either the alias or subject DN is null or
499 * a zero-length string.
500 *
501 * @throws UnsupportedOperationException If it is not possible to use the
502 * keytool utility to alter the
503 * contents of the key store.
504 */
505 public File generateCertificateSigningRequest(String alias, String subjectDN)
506 throws KeyStoreException, IOException, NullPointerException,
507 UnsupportedOperationException
508 {
509 if ((alias == null) || (alias.length() == 0))
510 {
511 throw new NullPointerException("alias");
512 }
513 else if ((subjectDN == null) || (subjectDN.length() == 0))
514 {
515 throw new NullPointerException("subjectDN");
516 }
517
518 if (KEYTOOL_COMMAND == null)
519 {
520 // FIXME -- Make this an internationalizeable string.
521 throw new UnsupportedOperationException("The certificate manager may " +
522 "not be used to alter the contents of key stores on " +
523 "this system.");
524 }
525
526 if (aliasInUse(alias))
527 {
528 // FIXME -- Make this an internationalizeable string.
529 throw new IllegalArgumentException("A certificate with alias " + alias +
530 " already exists in the key store.");
531 }
532
533
534 // Clear the reference to the key store, since it will be altered by
535 // invoking the KeyTool command.
536 keyStore = null;
537
538
539 // First, we need to run with the "-genkey" command to create the private
540 // key.
541 String[] commandElements =
542 {
543 KEYTOOL_COMMAND,
544 getGenKeyCommand(),
545 "-alias", alias,
546 "-dname", subjectDN,
547 "-keyalg", "rsa",
548 "-keystore", keyStorePath,
549 "-storetype", keyStoreType
550 };
551 runKeyTool(commandElements, keyStorePIN, keyStorePIN, true);
552
553 // Next, we need to run with the "-certreq" command to generate the
554 // certificate signing request.
555 File csrFile = File.createTempFile("CertificateManager-", ".csr");
556 csrFile.deleteOnExit();
557 commandElements = new String[]
558 {
559 KEYTOOL_COMMAND,
560 "-certreq",
561 "-alias", alias,
562 "-file", csrFile.getAbsolutePath(),
563 "-keystore", keyStorePath,
564 "-storetype", keyStoreType
565 };
566 runKeyTool(commandElements, keyStorePIN, keyStorePIN, true);
567
568 return csrFile;
569 }
570
571
572
573 /**
574 * Adds the provided certificate to the key store. This may be used to
575 * associate an externally-signed certificate with an existing private key
576 * with the given alias.
577 *
578 * @param alias The alias to use for the certificate. It must not
579 * be {@code null} or empty.
580 * @param certificateFile The file containing the encoded certificate. It
581 * must not be {@code null}, and the file must exist.
582 *
583 * @throws IllegalArgumentException If the provided certificate file does
584 * not exist.
585 *
586 * @throws KeyStoreException If a problem occurs while interacting with the
587 * key store.
588 *
589 * @throws NullPointerException If the provided alias is {@code null} or a
590 * zero-length string, or the certificate file
591 * is {@code null}.
592 *
593 * @throws UnsupportedOperationException If it is not possible to use the
594 * keytool utility to alter the
595 * contents of the key store.
596 */
597 public void addCertificate(String alias, File certificateFile)
598 throws IllegalArgumentException, KeyStoreException,
599 NullPointerException, UnsupportedOperationException
600 {
601 if ((alias == null) || (alias.length() == 0))
602 {
603 throw new NullPointerException("alias");
604 }
605
606 if (certificateFile == null)
607 {
608 throw new NullPointerException("certificateFile");
609 }
610 else if ((! certificateFile.exists()) ||
611 (! certificateFile.isFile()))
612 {
613 // FIXME -- Make this an internationalizeable string.
614 throw new IllegalArgumentException("Certificate file " +
615 certificateFile.getAbsolutePath() +
616 " does not exist or is not a file.");
617 }
618
619 if (KEYTOOL_COMMAND == null)
620 {
621 // FIXME -- Make this an internationalizeable string.
622 throw new UnsupportedOperationException("The certificate manager may " +
623 "not be used to alter the contents of key stores on " +
624 "this system.");
625 }
626
627
628 // Clear the reference to the key store, since it will be altered by
629 // invoking the KeyTool command.
630 keyStore = null;
631
632
633 String[] commandElements =
634 {
635 KEYTOOL_COMMAND,
636 "-import",
637 "-noprompt",
638 "-alias", alias,
639 "-file", certificateFile.getAbsolutePath(),
640 "-keystore", keyStorePath,
641 "-storetype", keyStoreType
642 };
643 runKeyTool(commandElements, keyStorePIN, keyStorePIN, true);
644 }
645
646
647 /**
648 * Removes the specified certificate from the key store.
649 *
650 * @param alias The alias to use for the certificate to remove. It must not
651 * be {@code null} or an empty string, and it must exist in
652 * the key store.
653 *
654 * @throws IllegalArgumentException If the specified certificate does not
655 * exist in the key store.
656 *
657 * @throws KeyStoreException If a problem occurs while interacting with the
658 * key store.
659 *
660 * @throws NullPointerException If the provided alias is {@code null} or a
661 * zero-length string, or the certificate file
662 * is {@code null}.
663 *
664 * @throws UnsupportedOperationException If it is not possible to use the
665 * keytool utility to alter the
666 * contents of the key store.
667 */
668 public void removeCertificate(String alias)
669 throws IllegalArgumentException, KeyStoreException,
670 NullPointerException, UnsupportedOperationException
671 {
672 if ((alias == null) || (alias.length() == 0))
673 {
674 throw new NullPointerException("alias");
675 }
676
677 if (KEYTOOL_COMMAND == null)
678 {
679 // FIXME -- Make this an internationalizeable string.
680 throw new UnsupportedOperationException("The certificate manager may " +
681 "not be used to alter the contents of key stores on " +
682 "this system.");
683 }
684
685 if (! aliasInUse(alias))
686 {
687 // FIXME -- Make this an internationalizeable string.
688 throw new IllegalArgumentException("There is no certificate with alias " +
689 alias + " in the key store.");
690 }
691
692
693 // Clear the reference to the key store, since it will be altered by
694 // invoking the KeyTool command.
695 keyStore = null;
696
697
698 String[] commandElements =
699 {
700 KEYTOOL_COMMAND,
701 "-delete",
702 "-alias", alias,
703 "-keystore", keyStorePath,
704 "-storetype", keyStoreType
705 };
706 runKeyTool(commandElements, keyStorePIN, keyStorePIN, true);
707 }
708
709
710
711 /**
712 * Attempts to run the keytool utility with the provided arguments.
713 *
714 * @param commandElements The command and arguments to execute. The first
715 * element of the array must be the command, and the
716 * remaining elements must be the arguments.
717 * @param keyStorePassword The password of the key store.
718 * @param storePassword The password of the certificate.
719 * @param outputAcceptable Indicates whether it is acceptable for the
720 * command to generate output, as long as the exit
721 * code is zero. Some commands (like "keytool
722 * -import") may generate output even on successful
723 * completion. If the command generates output and
724 * this is {@code false}, then an exception will
725 * be thrown.
726 *
727 * @throws KeyStoreException If a problem occurs while attempting to invoke
728 * the keytool utility, if it does not exit with
729 * the expected exit code, or if any unexpected
730 * output is generated while running the tool.
731 */
732 private void runKeyTool(String[] commandElements, String keyStorePassword,
733 String storePassword, boolean outputAcceptable)
734 throws KeyStoreException
735 {
736 String lineSeparator = System.getProperty("line.separator");
737 if (lineSeparator == null)
738 {
739 lineSeparator = "\n";
740 }
741 boolean keyStoreDefined;
742 File keyStoreFile = new File(keyStorePath);
743 keyStoreDefined = (keyStoreFile.exists() && (keyStoreFile.length() > 0)) ||
744 KEY_STORE_TYPE_PKCS11.equals(keyStoreType);
745
746 boolean isNewKeyStorePassword = !keyStoreDefined &&
747 (getGenKeyCommand().equalsIgnoreCase(commandElements[1]) ||
748 "-import".equalsIgnoreCase(commandElements[1]));
749
750 boolean isNewStorePassword =
751 getGenKeyCommand().equalsIgnoreCase(commandElements[1]);
752
753 boolean askForStorePassword =
754 !"-import".equalsIgnoreCase(commandElements[1]);
755
756 try
757 {
758 ProcessBuilder processBuilder = new ProcessBuilder(commandElements);
759 processBuilder.redirectErrorStream(true);
760
761 ByteArrayOutputStream output = new ByteArrayOutputStream();
762 byte[] buffer = new byte[1024];
763 Process process = processBuilder.start();
764 InputStream inputStream = process.getInputStream();
765 OutputStream out = process.getOutputStream();
766 if (!isJDK15() &&
767 (SetupUtils.getOperatingSystem() == OperatingSystem.AIX))
768 {
769 // This is required when using JDK 1.6 on AIX to be able to write
770 // on the OutputStream.
771 try
772 {
773 Thread.sleep(1500);
774 } catch (Throwable t) {}
775 }
776 out.write(keyStorePassword.getBytes()) ;
777 out.write(lineSeparator.getBytes()) ;
778 out.flush() ;
779 // With Java6 and above, keytool asks for the password twice.
780 if (!isJDK15() && isNewKeyStorePassword)
781 {
782 if (SetupUtils.getOperatingSystem() == OperatingSystem.AIX)
783 {
784 // This is required when using JDK 1.6 on AIX to be able to write
785 // on the OutputStream.
786 try
787 {
788 Thread.sleep(1500);
789 } catch (Throwable t) {}
790 }
791 out.write(keyStorePassword.getBytes()) ;
792 out.write(lineSeparator.getBytes()) ;
793 out.flush() ;
794 }
795
796 if (askForStorePassword)
797 {
798 out.write(storePassword.getBytes()) ;
799 out.write(lineSeparator.getBytes()) ;
800 out.flush() ;
801
802 // With Java6 and above, keytool asks for the password twice (if we
803 // are not running AIX).
804 if (!isJDK15() && isNewStorePassword &&
805 (SetupUtils.getOperatingSystem() != OperatingSystem.AIX))
806 {
807 out.write(storePassword.getBytes()) ;
808 out.write(lineSeparator.getBytes()) ;
809 out.flush() ;
810 }
811 }
812 // Close the output stream since it can generate a deadlock on IBM JVM
813 // (issue 2795).
814 out.close();
815 while (true)
816 {
817 int bytesRead = inputStream.read(buffer);
818 if (bytesRead < 0)
819 {
820 break;
821 }
822 else if (bytesRead > 0)
823 {
824 output.write(buffer, 0, bytesRead);
825 }
826 }
827 process.waitFor();
828 int exitValue = process.exitValue();
829 byte[] outputBytes = output.toByteArray();
830 if (exitValue != 0)
831 {
832 // FIXME -- Make this an internationalizeable string.
833 StringBuilder message = new StringBuilder();
834 message.append("Unexpected exit code of ");
835 message.append(exitValue);
836 message.append(" returned from the keytool utility.");
837
838 if ((outputBytes != null) && (outputBytes.length > 0))
839 {
840 message.append(" The generated output was: '");
841 message.append(new String(outputBytes));
842 message.append("'.");
843 }
844
845 throw new KeyStoreException(message.toString());
846 }
847 else if ((! outputAcceptable) && (outputBytes != null) &&
848 (outputBytes.length > 0))
849 {
850 // FIXME -- Make this an internationalizeable string.
851 StringBuilder message = new StringBuilder();
852 message.append("Unexpected output generated by the keytool " +
853 "utility: '");
854 message.append(new String(outputBytes));
855 message.append("'.");
856
857 throw new KeyStoreException(message.toString());
858 }
859 }
860 catch (KeyStoreException kse)
861 {
862 throw kse;
863 }
864 catch (Exception e)
865 {
866 // FIXME -- Make this an internationalizeable string.
867 throw new KeyStoreException("Could not invoke the KeyTool.run method: " +
868 e, e);
869 }
870 }
871
872
873
874 /**
875 * Retrieves a handle to the key store.
876 *
877 * @return The handle to the key store, or {@code null} if the key store
878 * doesn't exist.
879 *
880 * @throws KeyStoreException If a problem occurs while trying to open the
881 * key store.
882 */
883 private KeyStore getKeyStore()
884 throws KeyStoreException
885 {
886 if (keyStore != null)
887 {
888 return keyStore;
889 }
890
891 // For JKS and PKCS12 key stores, we should make sure the file exists, and
892 // we'll need an input stream that we can use to read it. For PKCS11 key
893 // stores there won't be a file and the input stream should be null.
894 FileInputStream keyStoreInputStream = null;
895 if (keyStoreType.equals(KEY_STORE_TYPE_JKS) ||
896 keyStoreType.equals(KEY_STORE_TYPE_PKCS12))
897 {
898 File keyStoreFile = new File(keyStorePath);
899 if (! keyStoreFile.exists())
900 {
901 return null;
902 }
903
904 try
905 {
906 keyStoreInputStream = new FileInputStream(keyStoreFile);
907 }
908 catch (Exception e)
909 {
910 throw new KeyStoreException(String.valueOf(e), e);
911 }
912 }
913
914
915 KeyStore keyStore = KeyStore.getInstance(keyStoreType);
916 try
917 {
918 keyStore.load(keyStoreInputStream, keyStorePIN.toCharArray());
919 return this.keyStore = keyStore;
920 }
921 catch (Exception e)
922 {
923 throw new KeyStoreException(String.valueOf(e), e);
924 }
925 finally
926 {
927 if (keyStoreInputStream != null)
928 {
929 try
930 {
931 keyStoreInputStream.close();
932 }
933 catch (Throwable t)
934 {
935 }
936 }
937 }
938 }
939
940 /**
941 * Returns whether we are running JDK 1.5 or not.
942 * @return <CODE>true</CODE> if we are running JDK 1.5 and <CODE>false</CODE>
943 * otherwise.
944 */
945 private boolean isJDK15()
946 {
947 boolean isJDK15 = false;
948 try
949 {
950 String javaRelease = System.getProperty ("java.version");
951 isJDK15 = javaRelease.startsWith("1.5");
952 }
953 catch (Throwable t)
954 {
955 System.err.println("Cannot get the java version: " + t);
956 }
957 return isJDK15;
958 }
959
960 private String getGenKeyCommand()
961 {
962 String genKeyCommand;
963 if (!isJDK15())
964 {
965 genKeyCommand = "-genkeypair";
966 }
967 else
968 {
969 genKeyCommand = "-genkey";
970 }
971 return genKeyCommand;
972 }
973 }
974
975