2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 package edu.internet2.middleware.shibboleth.utils;
52 import java.io.BufferedInputStream;
53 import java.io.BufferedReader;
54 import java.io.ByteArrayOutputStream;
56 import java.io.FileInputStream;
57 import java.io.FileNotFoundException;
58 import java.io.FileOutputStream;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.InputStreamReader;
62 import java.io.PrintStream;
63 import java.security.Key;
64 import java.security.KeyFactory;
65 import java.security.KeyStore;
66 import java.security.KeyStoreException;
67 import java.security.NoSuchAlgorithmException;
68 import java.security.NoSuchProviderException;
69 import java.security.PrivateKey;
70 import java.security.Provider;
71 import java.security.PublicKey;
72 import java.security.Security;
73 import java.security.UnrecoverableKeyException;
74 import java.security.cert.CertificateException;
75 import java.security.cert.CertificateFactory;
76 import java.security.cert.X509Certificate;
77 import java.security.spec.PKCS8EncodedKeySpec;
78 import java.util.ArrayList;
79 import java.util.Collection;
80 import java.util.Properties;
82 import javax.crypto.Cipher;
84 import org.apache.log4j.ConsoleAppender;
85 import org.apache.log4j.Level;
86 import org.apache.log4j.LogManager;
87 import org.apache.log4j.Logger;
88 import org.apache.log4j.PatternLayout;
89 import sun.misc.BASE64Encoder;
92 * Extension utility for use alongside Sun's keytool program. Performs useful functions not found
95 * @author Walter Hoehn
98 public class ExtKeyTool {
100 protected static Logger log = Logger.getLogger(ExtKeyTool.class.getName());
103 * Creates and initializes a java <code>KeyStore</code>
105 * @param provider name of the jce provider to use in loading the keystore
106 * @param keyStoreStream stream used to retrieve the keystore
107 * @param storeType the type of the keystore
108 * @param keyStorePassword password used to verify the integrity of the keystore
110 * @throws ExtKeyToolException if a problem is encountered loading the keystore
113 protected KeyStore loadKeyStore(
115 InputStream keyStoreStream,
117 char[] keyStorePassword)
118 throws ExtKeyToolException {
121 if (storeType == null) {
125 log.debug("Using keystore type: (" + storeType + ")");
126 log.debug("Using provider: (" + provider + ")");
129 if (storeType.equals("JKS")) {
130 keyStore = KeyStore.getInstance(storeType, "SUN");
131 } else if (storeType.equals("JCEKS")) {
132 keyStore = KeyStore.getInstance(storeType, "SunJCE");
134 keyStore = KeyStore.getInstance(storeType, provider);
137 if (keyStoreStream == null) {
138 log.error("Keystore must be specified.");
139 throw new ExtKeyToolException("Keystore must be specified.");
141 if (keyStorePassword == null) {
142 log.warn("No password given for keystore, integrity will not be verified.");
144 keyStore.load(keyStoreStream, keyStorePassword);
148 } catch (KeyStoreException e) {
149 log.error("Problem loading keystore: " + e);
150 throw new ExtKeyToolException("Problem loading keystore: " + e);
151 } catch (NoSuchProviderException e) {
152 log.error("The specified provider is not available.");
153 throw new ExtKeyToolException("The specified provider is not available.");
154 } catch (CertificateException ce) {
155 log.error("Could not open keystore: " + ce);
156 throw new ExtKeyToolException("Could not open keystore: " + ce);
157 } catch (IOException ioe) {
158 log.error("Could not export key: " + ioe);
159 throw new ExtKeyToolException("Could not export key: " + ioe);
160 } catch (NoSuchAlgorithmException nse) {
161 log.error("Could not open keystore with the installed JCE providers: " + nse);
162 throw new ExtKeyToolException("Could not open keystore with the installed JCE providers: " + nse);
167 * Retrieves a private key from a java keystore and writes it to an <code>PrintStream</code>
169 * @param provider name of the jce provider to use in retrieving the key
170 * @param outStream stream that should be used to output the retrieved key
171 * @param keyStoreStream stream used to retrieve the keystore
172 * @param storeType the type of the keystore
173 * @param keyStorePassword password used to verify the integrity of the keystore
174 * @param keyAlias the alias under which the key is stored
175 * @param keyPassword the password for recovering the key
176 * @param rfc boolean indicator of whether the key should be Base64 encoded
177 * before being written to the stream
178 * @throws ExtKeyToolException if there a problem retrieving or writing the key
181 public void exportKey(
183 PrintStream outStream,
184 InputStream keyStoreStream,
186 char[] keyStorePassword,
190 throws ExtKeyToolException {
194 KeyStore keyStore = loadKeyStore(provider, keyStoreStream, storeType, keyStorePassword);
196 if (keyAlias == null) {
197 log.error("Key alias must be specified.");
198 throw new ExtKeyToolException("Key alias must be specified.");
200 log.info("Searching for key.");
202 Key key = keyStore.getKey(keyAlias, keyPassword);
204 log.error("Key not found in store.");
205 throw new ExtKeyToolException("Key not found in store.");
207 log.info("Found key.");
210 log.debug("Dumping with rfc encoding");
211 outStream.println("-----BEGIN PRIVATE KEY-----");
212 BASE64Encoder encoder = new BASE64Encoder();
213 encoder.encodeBuffer(key.getEncoded(), outStream);
214 outStream.println("-----END PRIVATE KEY-----");
216 log.debug("Dumping with default encoding.");
217 outStream.write(key.getEncoded());
220 } catch (KeyStoreException e) {
221 log.error("Problem accessing keystore: " + e);
222 throw new ExtKeyToolException("Problem loading keystore: " + e);
223 } catch (IOException ioe) {
224 log.error("Could not export key: " + ioe);
225 throw new ExtKeyToolException("Could not export key: " + ioe);
226 } catch (NoSuchAlgorithmException nse) {
227 log.error("Could not recover key with the installed JCE providers: " + nse);
228 throw new ExtKeyToolException("Could not recover key with the installed JCE providers: " + nse);
229 } catch (UnrecoverableKeyException uke) {
230 log.error("The key specified key cannot be recovered with the given password: " + uke);
231 throw new ExtKeyToolException(
232 "The key specified key cannot be recovered with the given password: " + uke);
237 * Boolean indication of whether a given private key and public key form a valid keypair.
239 * @param pubKey the public key
240 * @param privKey the private key
243 protected boolean isMatchingKey(String algorithm, PublicKey pubKey, PrivateKey privKey) {
246 String controlString = "asdf";
247 log.debug("Checking for matching private key/public key pair");
248 Cipher cipher = Cipher.getInstance(algorithm);
249 cipher.init(Cipher.ENCRYPT_MODE, pubKey);
250 byte[] encryptedData = cipher.doFinal(controlString.getBytes("UTF-8"));
252 cipher.init(Cipher.DECRYPT_MODE, privKey);
253 byte[] decryptedData = cipher.doFinal(encryptedData);
254 if (controlString.equals(new String(decryptedData, "UTF-8"))) {
255 log.debug("Found match.");
258 } catch (Exception e) {
261 log.debug("This pair does not match.");
266 * Attempts to unmarshall a private key from a given stream.
268 * @param keyStream the <code>InputStream</code> suppying the key
269 * @param algorithm the key algorithm
270 * @throws ExtKeyToolException if there a problem unmarshalling the key
273 protected PrivateKey readPrivateKey(String provider, InputStream keyStream, String algorithm)
274 throws ExtKeyToolException {
276 KeyFactory keyFactory = KeyFactory.getInstance(algorithm, provider);
278 byte[] inputBuffer = new byte[8];
280 ByteContainer inputBytes = new ByteContainer(400);
282 i = keyStream.read(inputBuffer);
283 for (int j = 0; j < i; j++) {
284 inputBytes.append(inputBuffer[j]);
288 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(inputBytes.toByteArray());
289 return keyFactory.generatePrivate(keySpec);
291 } catch (Exception e) {
292 log.error("Problem reading private key: " + e.getMessage());
293 throw new ExtKeyToolException("Problem reading private key. Keys should be DER encoded pkcs8 or DER encoded native format.");
298 * Converts an array of certificates into an ordered chain. A
299 * certificate that matches the specified private key will be returned
300 * first and the root certificate will be returned last.
302 * @param untestedCerts array of certificates
303 * @param privKey the private key used to determine the first cert in the chain
304 * @throws InvalidCertificateChainException thrown if a chain cannot be constructed
305 * from the specified elements
308 protected X509Certificate[] linkChain(
310 X509Certificate[] untestedCerts,
312 throws InvalidCertificateChainException {
314 log.debug("Located " + untestedCerts.length + " cert(s) in input file");
316 log.info("Finding end cert in chain.");
317 ArrayList replyCerts = new ArrayList();
318 for (int i = 0; untestedCerts.length > i; i++) {
319 if (isMatchingKey(keyAlgorithm, untestedCerts[i].getPublicKey(), privKey)) {
320 log.debug("Found matching end cert: " + untestedCerts[i].getSubjectDN());
321 replyCerts.add(untestedCerts[i]);
324 if (replyCerts.size() < 1) {
325 log.error("No certificate in chain that matches specified private key");
326 throw new InvalidCertificateChainException("No certificate in chain that matches specified private key");
328 if (replyCerts.size() > 1) {
329 log.error("More than one certificate in chain that matches specified private key");
330 throw new InvalidCertificateChainException("More than one certificate in chain that matches specified private key");
333 log.info("Populating chain with remaining certs.");
334 walkChain(untestedCerts, replyCerts);
336 log.info("Verifying that each link in the cert chain is signed appropriately");
337 for (int i = 0; i < replyCerts.size() - 1; i++) {
338 PublicKey pubKey = ((X509Certificate) replyCerts.get(i + 1)).getPublicKey();
340 ((X509Certificate) replyCerts.get(i)).verify(pubKey);
341 } catch (Exception e) {
342 log.error("Certificate chain cannot be verified: " + e.getMessage());
343 throw new InvalidCertificateChainException(
344 "Certificate chain cannot be verified: " + e.getMessage());
347 log.info("All signatures verified. Certificate chain successfully created.");
349 return (X509Certificate[]) replyCerts.toArray(new X509Certificate[0]);
353 * Given an ArrayList containing a base certificate and an array of unordered certificates,
354 * populates the ArrayList with an ordered certificate chain, based on subject and issuer.
356 * @param chainSource array of certificates to pull from
357 * @param chainDest ArrayList containing base certificate
358 * @throws InvalidCertificateChainException thrown if a chain cannot be constructed from
359 * the specified elements
362 protected void walkChain(X509Certificate[] chainSource, ArrayList chainDest)
363 throws InvalidCertificateChainException {
365 X509Certificate currentCert = (X509Certificate) chainDest.get(chainDest.size() - 1);
366 if (currentCert.getSubjectDN().equals(currentCert.getIssuerDN())) {
367 log.debug("Found self-signed root cert: " + currentCert.getSubjectDN());
370 for (int i = 0; chainSource.length > i; i++) {
371 if (currentCert.getIssuerDN().equals(chainSource[i].getSubjectDN())) {
372 chainDest.add(chainSource[i]);
373 walkChain(chainSource, chainDest);
377 log.error("Incomplete certificate chain.");
378 throw new InvalidCertificateChainException("Incomplete cerficate chain.");
383 * Given a java keystore, private key, and matching certificate chain; creates a new
384 * keystore containing the union of these objects
386 * @param provider the name of the jce provider to use
387 * @param keyAlgorithm the algorithm of the key to be added, defaults to RSA if null
388 * @param keyStream strema used to retrieve the private key, can contain a PEM encoded
389 * or pkcs8 encoded key
390 * @param chainStream stream used to retrieve certificates, can contain a series of
391 * PEM encoded certs or a pkcs7 chain
392 * @param keyStoreInStream stream used to retrieve the initial keystore
393 * @param storeType the type of the keystore
394 * @param keyAlias the alias under which the key/chain should be saved
395 * @param keyStorePassword password used to verify the integrity of the old keystore and
396 * save the new keystore
397 * @param keyPassword the password for saving the key
399 * @return an OutputStream containing the new keystore
401 * @throws ExtKeyToolException if there a problem importing the key
404 public ByteArrayOutputStream importKey(
407 InputStream keyStream,
408 InputStream chainStream,
409 InputStream keyStoreInStream,
412 char[] keyStorePassword,
414 throws ExtKeyToolException {
416 log.info("Importing key pair.");
419 // The Sun provider incorrectly reads only the first cert in the stream.
420 // No loss, it won't even read RSA keys
421 if (provider == "SUN") {
422 log.error("Sorry, this function not supported with the SUN provider.");
423 throw new ExtKeyToolException("Sorry, this function not supported with the SUN provider.");
426 KeyStore keyStore = loadKeyStore(provider, keyStoreInStream, storeType, keyStorePassword);
428 if (keyAlias == null) {
429 log.error("Key alias must be specified.");
430 throw new ExtKeyToolException("Key alias must be specified.");
432 if (keyStore.containsAlias(keyAlias) == true && keyStore.isKeyEntry(keyAlias)) {
433 log.error("Could not import key: " + "key alias (" + keyAlias + ") already exists");
434 throw new ExtKeyToolException(
435 "Could not import key: " + "key alias (" + keyAlias + ") already exists");
437 keyStore.deleteEntry(keyAlias);
439 log.info("Reading private key.");
440 if (keyAlgorithm == null) {
441 keyAlgorithm = "RSA";
443 log.debug("Using key algorithm: (" + keyAlgorithm + ")");
444 PrivateKey key = readPrivateKey(provider, keyStream, keyAlgorithm);
446 log.info("Reading certificate chain.");
448 CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider);
449 Collection chain = certFactory.generateCertificates(new BufferedInputStream(chainStream));
450 if (chain.isEmpty()) {
451 log.error("Input did not contain any valid certificates.");
452 throw new ExtKeyToolException("Input did not contain any valid certificates.");
455 X509Certificate[] verifiedChain =
456 linkChain(keyAlgorithm, (X509Certificate[]) chain.toArray(new X509Certificate[0]), key);
458 keyStore.setKeyEntry(keyAlias, key, keyPassword, verifiedChain);
459 ByteArrayOutputStream keyStoreOutStream = new ByteArrayOutputStream();
460 keyStore.store(keyStoreOutStream, keyStorePassword);
461 log.info("Key Store saved to stream.");
462 return keyStoreOutStream;
464 } catch (KeyStoreException e) {
465 log.error("Encountered a problem accessing the keystore: " + e.getMessage());
466 throw new ExtKeyToolException("Encountered a problem accessing the keystore: " + e.getMessage());
467 } catch (CertificateException e) {
468 log.error("Could not load certificate(s): " + e.getMessage());
469 throw new ExtKeyToolException("Could not load certificate(s): " + e.getMessage());
470 } catch (NoSuchProviderException e) {
471 log.error("The specified provider is not available.");
472 throw new ExtKeyToolException("The specified provider is not available.");
473 } catch (IOException ioe) {
474 log.error("Could not export key: " + ioe);
475 throw new ExtKeyToolException("Could not export key: " + ioe);
476 } catch (NoSuchAlgorithmException nse) {
477 log.error("Could not save with the installed JCE providers: " + nse);
478 throw new ExtKeyToolException("Could not save with the installed JCE providers: " + nse);
483 * Tries to decipher command line arguments.
485 * @throws IllegalArgumentException if arguments are not properly formatted
488 private static Properties parseArguments(String[] args) throws IllegalArgumentException {
490 if (args.length < 1) {
491 throw new IllegalArgumentException("No arguments found.");
493 Properties parsedArguments = new Properties();
495 for (int i = 0;(i < args.length) && args[i].startsWith("-"); i++) {
497 String flags = args[i];
500 if (flags.equalsIgnoreCase("-exportkey")) {
501 parsedArguments.setProperty("command", "exportKey");
502 } else if (flags.equalsIgnoreCase("-importkey")) {
503 parsedArguments.setProperty("command", "importKey");
507 else if (flags.equalsIgnoreCase("-alias")) {
508 if (++i == args.length) {
509 throw new IllegalArgumentException("The argument -alias requires a parameter");
511 parsedArguments.setProperty("alias", args[i]);
512 } else if (flags.equalsIgnoreCase("-keyfile")) {
513 if (++i == args.length) {
514 throw new IllegalArgumentException("The argument -keyfile requires a parameter");
516 parsedArguments.setProperty("keyFile", args[i]);
517 } else if (flags.equalsIgnoreCase("-certfile")) {
518 if (++i == args.length) {
519 throw new IllegalArgumentException("The argument -certfile requires a parameter");
521 parsedArguments.setProperty("certFile", args[i]);
522 } else if (flags.equalsIgnoreCase("-keystore")) {
523 if (++i == args.length) {
524 throw new IllegalArgumentException("The argument -keystore requires a parameter");
526 parsedArguments.setProperty("keyStore", args[i]);
527 } else if (flags.equalsIgnoreCase("-storepass")) {
528 if (++i == args.length) {
529 throw new IllegalArgumentException("The argument -storepass requires a parameter");
531 parsedArguments.setProperty("storePass", args[i]);
532 } else if (flags.equalsIgnoreCase("-storetype")) {
533 if (++i == args.length) {
534 throw new IllegalArgumentException("The argument -storetype requires a parameter");
536 parsedArguments.setProperty("storeType", args[i]);
537 } else if (flags.equalsIgnoreCase("-keypass")) {
538 if (++i == args.length) {
539 throw new IllegalArgumentException("The argument -keypass requires a parameter");
541 parsedArguments.setProperty("keyPass", args[i]);
542 } else if (flags.equalsIgnoreCase("-provider")) {
543 if (++i == args.length) {
544 throw new IllegalArgumentException("The argument -provider requires a parameter");
546 parsedArguments.setProperty("provider", args[i]);
547 } else if (flags.equalsIgnoreCase("-file")) {
548 if (++i == args.length) {
549 throw new IllegalArgumentException("The argument -file requires a parameter");
551 parsedArguments.setProperty("file", args[i]);
552 } else if (flags.equalsIgnoreCase("-algorithm")) {
553 if (++i == args.length) {
554 throw new IllegalArgumentException("The argument -algorithm requires a parameter");
556 parsedArguments.setProperty("keyAlgorithm", args[i]);
560 else if (flags.equalsIgnoreCase("-v")) {
561 parsedArguments.setProperty("verbose", "true");
562 } else if (flags.equalsIgnoreCase("-rfc")) {
563 parsedArguments.setProperty("rfc", "true");
565 throw new IllegalArgumentException("Unrecognized argument: " + flags);
568 if (parsedArguments.getProperty("command", null) == null) {
569 throw new IllegalArgumentException("No action specified");
571 return parsedArguments;
575 * Ensures that providers specified on the command line are in fact loaded
576 * into the current environment.
578 * @return the name of the provider add, null if no provider was added
581 protected String initProvider(Properties arguments) {
584 if (arguments.getProperty("provider", null) != null) {
586 Provider provider = (Provider) Class.forName(arguments.getProperty("provider")).newInstance();
587 log.info("Adding Provider to environment: (" + provider.getName() + ")");
588 Security.addProvider(provider);
589 return provider.getName();
591 } catch (Exception e) {
592 log.error("Could not load specified jce provider: " + e);
599 * Initializes Log4J logger mode based on command line arguments.
602 protected void startLogger(Properties arguments) {
603 Logger root = Logger.getRootLogger();
604 if (arguments.getProperty("verbose", null) == null
605 || arguments.getProperty("verbose", null).equals("false")) {
606 root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN)));
607 root.setLevel(Level.WARN);
609 root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
610 root.setLevel(Level.DEBUG);
614 public static void main(String[] args) {
617 ExtKeyTool tool = new ExtKeyTool();
618 Properties arguments = null;
620 arguments = parseArguments(args);
621 } catch (IllegalArgumentException iae) {
623 "Illegal argument specified: " + iae.getMessage() + System.getProperty("line.separator"));
624 printUsage(System.err);
627 tool.startLogger(arguments);
628 String providerName = tool.initProvider(arguments);
629 if (providerName != null) {
630 arguments.setProperty("providerName", providerName);
634 } catch (ExtKeyToolException ske) {
635 log.fatal("Cannot Perform Operation: " + ske.getMessage() + System.getProperty("line.separator"));
636 LogManager.shutdown();
637 printUsage(System.err);
642 * Based on on a set of properties, executes <code>ExtKeyTool</code> actions.
644 * @param arguments runtime parameters specified on the command line
647 private void run(Properties arguments) throws ExtKeyToolException {
649 //common for all actions
650 char[] storePassword = null;
651 if (arguments.getProperty("storePass", null) != null) {
652 storePassword = arguments.getProperty("storePass").toCharArray();
655 String providerName = null;
656 if (arguments.getProperty("providerName", null) != null) {
657 providerName = arguments.getProperty("providerName");
659 providerName = "SUN";
663 if (arguments.getProperty("command").equals("exportKey")) {
666 if ("true".equalsIgnoreCase(arguments.getProperty("rfc", null))) {
670 PrintStream outStream = null;
671 if (arguments.getProperty("file", null) != null) {
673 outStream = new PrintStream(new FileOutputStream(arguments.getProperty("file")));
674 } catch (FileNotFoundException e) {
675 throw new ExtKeyToolException("Could not open output file: " + e);
678 outStream = System.out;
685 new FileInputStream(resolveKeyStore(arguments.getProperty("keyStore", null))),
686 arguments.getProperty("storeType", null),
688 arguments.getProperty("alias", null),
689 resolveKeyPass(arguments.getProperty("keyPass", null), storePassword),
691 } catch (FileNotFoundException e) {
692 throw new ExtKeyToolException("KeyStore not found.");
697 } else if (arguments.getProperty("command").equals("importKey")) {
699 InputStream keyInStream = null;
700 if (arguments.getProperty("keyFile", null) != null) {
702 keyInStream = new FileInputStream(arguments.getProperty("keyFile"));
703 } catch (FileNotFoundException e) {
704 throw new ExtKeyToolException("Could not open key file." + e.getMessage());
707 throw new IllegalArgumentException("Key file must be specified.");
710 InputStream certInStream = null;
711 if (arguments.getProperty("certFile", null) != null) {
713 certInStream = new FileInputStream(arguments.getProperty("certFile"));
714 } catch (FileNotFoundException e) {
715 throw new ExtKeyToolException("Could not open cert file." + e.getMessage());
718 throw new IllegalArgumentException("Certificate file must be specified.");
723 ByteArrayOutputStream keyStoreOutStream =
726 arguments.getProperty("keyAlgorithm", null),
729 new FileInputStream(resolveKeyStore(arguments.getProperty("keyStore", null))),
730 arguments.getProperty("storeType", null),
731 arguments.getProperty("alias", null),
733 resolveKeyPass(arguments.getProperty("keyPass", null), storePassword));
736 // A quick sanity check before we overwrite the old keystore
737 if (keyStoreOutStream == null || keyStoreOutStream.size() < 1) {
738 throw new ExtKeyToolException("Failed to create keystore: results are null");
740 keyStoreOutStream.writeTo(
741 new FileOutputStream(resolveKeyStore(arguments.getProperty("keyStore", null))));
742 System.out.println("Key import successful.");
744 } catch (FileNotFoundException e) {
745 throw new ExtKeyToolException("Could not open keystore file." + e.getMessage());
746 } catch (IOException e) {
747 throw new ExtKeyToolException("Error writing keystore." + e.getMessage());
751 throw new IllegalArgumentException(
752 "This keytool cannot perform the operation: (" + arguments.getProperty("command") + ")");
758 * Determines the location of the keystore to use when performing the action
760 * @return the <code>File</code> representation of the selected keystore
763 protected File resolveKeyStore(String keyStoreLocation)
764 throws ExtKeyToolException, FileNotFoundException {
766 if (keyStoreLocation == null) {
767 keyStoreLocation = System.getProperty("user.home") + File.separator + ".keystore";
769 log.debug("Using keystore (" + keyStoreLocation + ")");
770 File file = new File(keyStoreLocation);
771 if (file.exists() && file.length() == 0) {
772 log.error("Keystore file is empty.");
773 throw new ExtKeyToolException("Keystore file is empty.");
779 * Decides what password to use for storing/retrieving keys from the keystore. NOTE: Possible
780 * terminal interaction with the user.
781 * @return a char array containing the password
784 protected char[] resolveKeyPass(String keyPass, char[] storePass) {
786 if (keyPass != null) {
787 return keyPass.toCharArray();
789 System.out.println("Enter key password");
790 System.out.print("\t(RETURN if same as keystore password): ");
793 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
794 String passwordInput = reader.readLine();
795 passwordInput.trim();
796 if (passwordInput != null && !passwordInput.equals("")) {
797 return passwordInput.toCharArray();
799 } catch (IOException e) {
800 log.warn(e.getMessage());
802 log.warn("No password specified, defaulting to keystore password.");
807 private static void printUsage(PrintStream out) {
809 out.println("extkeytool usage:");
810 out.print("-exportkey [-v] [-rfc] [-alias <alias>] ");
811 out.println("[-keystore <keystore>] ");
812 out.print("\t [-storepass <storepass>] ");
813 out.println("[-storetype <storetype>]");
814 out.print("\t [-keypass <keypass>] ");
815 out.println("[-provider <provider_class_name>] ");
816 out.print("\t [-file <output_file>] ");
820 out.print("-importkey [-v] [-alias <alias>] ");
821 out.println("[-keyfile <key_file>]");
822 out.print("\t [-keystore <keystore>] ");
823 out.println("[-storepass <storepass>]");
824 out.print("\t [-storetype <storetype>] ");
825 out.println("[-keypass <keypass>] ");
826 out.print("\t [-provider <provider_class_name>] ");
827 out.println("[-certfile <cert_file>] ");
828 out.print("\t [-algorithm <key_algorithm>] ");
834 * Auto-enlarging container for bytes.
837 // Sure makes you wish bytes were first class objects.
839 private class ByteContainer {
841 private byte[] buffer;
843 private int currentSize = 0;
845 private ByteContainer(int cushion) {
846 buffer = new byte[cushion];
847 this.cushion = cushion;
850 private void grow() {
851 log.debug("Growing ByteContainer.");
852 int newSize = currentSize + cushion;
853 byte[] b = new byte[newSize];
854 int toCopy = Math.min(currentSize, newSize);
856 for (i = 0; i < toCopy; i++) {
863 * Returns an array of the bytes in the container. <p>
866 private byte[] toByteArray() {
867 byte[] b = new byte[currentSize];
868 for (int i = 0; i < currentSize; i++) {
875 * Add one byte to the end of the container.
878 private void append(byte b) {
879 if (currentSize == buffer.length) {
882 buffer[currentSize] = b;
889 * Signals that an error was encounted while using <code>ExtKeyTool</code> functions.
892 protected class ExtKeyToolException extends Exception {
894 protected ExtKeyToolException(String message) {
900 * Signals that an error occurred while trying to constuct a
904 protected class InvalidCertificateChainException extends ExtKeyToolException {
906 protected InvalidCertificateChainException(String message) {