2 * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package edu.internet2.middleware.shibboleth.utils;
19 import java.io.BufferedInputStream;
20 import java.io.BufferedReader;
21 import java.io.ByteArrayOutputStream;
23 import java.io.FileInputStream;
24 import java.io.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.OutputStream;
30 import java.io.PrintStream;
31 import java.security.Key;
32 import java.security.KeyFactory;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.NoSuchProviderException;
37 import java.security.PrivateKey;
38 import java.security.Provider;
39 import java.security.PublicKey;
40 import java.security.Security;
41 import java.security.UnrecoverableKeyException;
42 import java.security.cert.Certificate;
43 import java.security.cert.CertificateException;
44 import java.security.cert.CertificateFactory;
45 import java.security.cert.X509Certificate;
46 import java.security.interfaces.RSAKey;
47 import java.security.spec.KeySpec;
48 import java.security.spec.PKCS8EncodedKeySpec;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.Properties;
53 import javax.crypto.Cipher;
54 import javax.crypto.SecretKey;
55 import javax.crypto.SecretKeyFactory;
56 import javax.crypto.spec.DESKeySpec;
57 import javax.crypto.spec.DESedeKeySpec;
59 import org.apache.log4j.ConsoleAppender;
60 import org.apache.log4j.Level;
61 import org.apache.log4j.LogManager;
62 import org.apache.log4j.Logger;
63 import org.apache.log4j.PatternLayout;
64 import org.bouncycastle.util.encoders.Base64;
67 * Extension utility for use alongside Sun's keytool program. Performs useful functions not found in original.
69 * @author Walter Hoehn
72 public class ExtKeyTool {
74 protected static Logger log = Logger.getLogger(ExtKeyTool.class.getName());
77 * Creates and initializes a java <code>KeyStore</code>
80 * name of the jce provider to use in loading the keystore
81 * @param keyStoreStream
82 * stream used to retrieve the keystore
84 * the type of the keystore
85 * @param keyStorePassword
86 * password used to verify the integrity of the keystore
87 * @throws ExtKeyToolException
88 * if a problem is encountered loading the keystore
91 protected KeyStore loadKeyStore(String provider, InputStream keyStoreStream, String storeType,
92 char[] keyStorePassword) throws ExtKeyToolException {
95 if (storeType == null) {
99 log.debug("Using keystore type: (" + storeType + ")");
100 log.debug("Using provider: (" + provider + ")");
103 if (storeType.equals("JKS")) {
104 keyStore = KeyStore.getInstance(storeType, "SUN");
105 } else if (storeType.equals("JCEKS")) {
106 keyStore = KeyStore.getInstance(storeType, "SunJCE");
108 keyStore = KeyStore.getInstance(storeType, provider);
111 if (keyStoreStream == null) {
112 log.error("Keystore must be specified.");
113 throw new ExtKeyToolException("Keystore must be specified.");
115 if (keyStorePassword == null) {
116 log.warn("No password given for keystore, integrity will not be verified.");
118 keyStore.load(keyStoreStream, keyStorePassword);
122 } catch (KeyStoreException e) {
123 log.error("Problem loading keystore: " + e);
124 throw new ExtKeyToolException("Problem loading keystore: " + e);
125 } catch (NoSuchProviderException e) {
126 log.error("The specified provider is not available.");
127 throw new ExtKeyToolException("The specified provider is not available.");
128 } catch (CertificateException ce) {
129 log.error("Could not open keystore: " + ce);
130 throw new ExtKeyToolException("Could not open keystore: " + ce);
131 } catch (IOException ioe) {
132 log.error("Could not export key: " + ioe);
133 throw new ExtKeyToolException("Could not export key: " + ioe);
134 } catch (NoSuchAlgorithmException nse) {
135 log.error("Could not open keystore with the installed JCE providers: " + nse);
136 throw new ExtKeyToolException("Could not open keystore with the installed JCE providers: " + nse);
140 private void writeWithWrapping(byte[] base64text, int linesize, OutputStream out)
143 int length = base64text.length;
144 if (length == 0) return;
146 out.write( (int)base64text[0] );
147 for (int i=1; i < length; i++)
149 if (i%linesize == 0) out.write('\n');
150 out.write( (int)base64text[i] );
155 * Retrieves a private key from a java keystore and writes it to an <code>PrintStream</code>
158 * name of the jce provider to use in retrieving the key
160 * stream that should be used to output the retrieved key
161 * @param keyStoreStream
162 * stream used to retrieve the keystore
164 * the type of the keystore
165 * @param keyStorePassword
166 * password used to verify the integrity of the keystore
168 * the alias under which the key is stored
170 * the password for recovering the key
172 * boolean indicator of whether the key should be Base64 encoded before being written to the stream
173 * @throws ExtKeyToolException
174 * if there a problem retrieving or writing the key
177 public void exportKey(String provider, PrintStream outStream, InputStream keyStoreStream, String storeType,
178 char[] keyStorePassword, String keyAlias, char[] keyPassword, boolean rfc) throws ExtKeyToolException {
182 KeyStore keyStore = loadKeyStore(provider, keyStoreStream, storeType, keyStorePassword);
184 if (keyAlias == null) {
185 log.error("Key alias must be specified.");
186 throw new ExtKeyToolException("Key alias must be specified.");
188 log.info("Searching for key.");
190 Key key = keyStore.getKey(keyAlias, keyPassword);
192 log.error("Key not found in store.");
193 throw new ExtKeyToolException("Key not found in store.");
195 log.info("Found key.");
197 byte[] encodedKey = key.getEncoded();
200 log.debug("Dumping with rfc encoding");
201 outStream.println("-----BEGIN PRIVATE KEY-----");
202 writeWithWrapping(Base64.encode(encodedKey), 76, outStream);
204 outStream.println("-----END PRIVATE KEY-----");
206 log.debug("Dumping with default encoding.");
207 outStream.write(encodedKey);
210 } catch (KeyStoreException e) {
211 log.error("Problem accessing keystore: " + e);
212 throw new ExtKeyToolException("Problem loading keystore: " + e);
213 } catch (IOException ioe) {
214 log.error("Could not export key: " + ioe);
215 throw new ExtKeyToolException("Could not export key: " + ioe);
216 } catch (NoSuchAlgorithmException nse) {
217 log.error("Could not recover key with the installed JCE providers: " + nse);
218 throw new ExtKeyToolException("Could not recover key with the installed JCE providers: " + nse);
219 } catch (UnrecoverableKeyException uke) {
220 log.error("The key specified key cannot be recovered with the given password: " + uke);
221 throw new ExtKeyToolException("The key specified key cannot be recovered with the given password: " + uke);
226 * Attempts to unmarshall a secret key from a given stream.
229 * the <code>InputStream</code> suppying the key
232 * @throws ExtKeyToolException
233 * if there a problem unmarshalling the key
236 protected SecretKey readSecretKey(String provider, InputStream keyStream, String algorithm)
237 throws ExtKeyToolException {
240 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm, provider);
242 byte[] inputBuffer = new byte[8];
244 ByteContainer inputBytes = new ByteContainer(400);
246 i = keyStream.read(inputBuffer);
247 for (int j = 0; j < i; j++) {
248 inputBytes.append(inputBuffer[j]);
252 KeySpec keySpec = null;
253 if (algorithm.equals("DESede")) keySpec = new DESedeKeySpec(inputBytes.toByteArray());
254 else if (algorithm.equals("DES")) keySpec = new DESKeySpec(inputBytes.toByteArray());
255 return keyFactory.generateSecret(keySpec);
257 } catch (Exception e) {
258 log.error("Problem reading secret key: " + e.getMessage());
259 throw new ExtKeyToolException("Problem reading secret key. Keys should be DER encoded native format.");
264 * Boolean indication of whether a given private key and public key form a valid keypair.
272 protected boolean isMatchingKey(String algorithm, PublicKey pubKey, PrivateKey privKey) {
275 String controlString = "asdf";
276 log.debug("Checking for matching private key/public key pair");
279 * If both keys are RSA, compare the modulus. They can't be a pair if that doesn't match. Doing this early
280 * check means we don't have to do a trial encryption for every public key (faster) and avoids warning
281 * messages from the depths of the crypto provider if the key lengths differ.
283 if (privKey instanceof RSAKey && pubKey instanceof RSAKey) {
284 RSAKey pubRSA = (RSAKey) pubKey;
285 RSAKey privRSA = (RSAKey) privKey;
286 if (!privRSA.getModulus().equals(pubRSA.getModulus())) {
287 log.debug("RSA modulus mismatch");
292 Cipher cipher = Cipher.getInstance(algorithm);
293 cipher.init(Cipher.ENCRYPT_MODE, pubKey);
294 byte[] encryptedData = cipher.doFinal(controlString.getBytes("UTF-8"));
296 cipher.init(Cipher.DECRYPT_MODE, privKey);
297 byte[] decryptedData = cipher.doFinal(encryptedData);
298 if (controlString.equals(new String(decryptedData, "UTF-8"))) {
299 log.debug("Found match.");
302 } catch (Exception e) {
305 log.debug("This pair does not match.");
310 * Attempts to unmarshall a private key from a given stream.
313 * the <code>InputStream</code> suppying the key
316 * @throws ExtKeyToolException
317 * if there a problem unmarshalling the key
320 protected PrivateKey readPrivateKey(String provider, InputStream keyStream, String algorithm)
321 throws ExtKeyToolException {
324 KeyFactory keyFactory = KeyFactory.getInstance(algorithm, provider);
326 byte[] inputBuffer = new byte[8];
328 ByteContainer inputBytes = new ByteContainer(400);
330 i = keyStream.read(inputBuffer);
331 for (int j = 0; j < i; j++) {
332 inputBytes.append(inputBuffer[j]);
336 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(inputBytes.toByteArray());
337 return keyFactory.generatePrivate(keySpec);
339 } catch (Exception e) {
340 log.error("Problem reading private key: " + e.getMessage());
341 throw new ExtKeyToolException(
342 "Problem reading private key. Keys should be DER encoded pkcs8 or DER encoded native format.");
347 * Converts an array of certificates into an ordered chain. A certificate that matches the specified private key
348 * will be returned first and the root certificate will be returned last.
350 * @param untestedCerts
351 * array of certificates
353 * the private key used to determine the first cert in the chain
354 * @throws InvalidCertificateChainException
355 * thrown if a chain cannot be constructed from the specified elements
358 protected X509Certificate[] linkChain(String keyAlgorithm, X509Certificate[] untestedCerts, PrivateKey privKey)
359 throws InvalidCertificateChainException {
361 log.debug("Located " + untestedCerts.length + " cert(s) in input file");
363 log.info("Finding end cert in chain.");
364 ArrayList<X509Certificate> replyCerts = new ArrayList<X509Certificate>();
365 for (int i = 0; untestedCerts.length > i; i++) {
366 if (isMatchingKey(keyAlgorithm, untestedCerts[i].getPublicKey(), privKey)) {
367 log.debug("Found matching end cert: " + untestedCerts[i].getSubjectDN());
368 replyCerts.add(untestedCerts[i]);
371 if (replyCerts.size() < 1) {
372 log.error("No certificate in chain that matches specified private key");
373 throw new InvalidCertificateChainException("No certificate in chain that matches specified private key");
375 if (replyCerts.size() > 1) {
376 log.error("More than one certificate in chain that matches specified private key");
377 throw new InvalidCertificateChainException(
378 "More than one certificate in chain that matches specified private key");
381 log.info("Populating chain with remaining certs.");
382 walkChain(untestedCerts, replyCerts);
384 log.info("Verifying that each link in the cert chain is signed appropriately");
385 for (int i = 0; i < replyCerts.size() - 1; i++) {
386 PublicKey pubKey = ((X509Certificate) replyCerts.get(i + 1)).getPublicKey();
388 ((X509Certificate) replyCerts.get(i)).verify(pubKey);
389 } catch (Exception e) {
390 log.error("Certificate chain cannot be verified: " + e.getMessage());
391 throw new InvalidCertificateChainException("Certificate chain cannot be verified: " + e.getMessage());
394 log.info("All signatures verified. Certificate chain successfully created.");
396 return (X509Certificate[]) replyCerts.toArray(new X509Certificate[0]);
400 * Given an ArrayList containing a base certificate and an array of unordered certificates, populates the ArrayList
401 * with an ordered certificate chain, based on subject and issuer.
404 * array of certificates to pull from
406 * ArrayList containing base certificate
407 * @throws InvalidCertificateChainException
408 * thrown if a chain cannot be constructed from the specified elements
411 protected void walkChain(X509Certificate[] chainSource, ArrayList<X509Certificate> chainDest)
412 throws InvalidCertificateChainException {
414 X509Certificate currentCert = (X509Certificate) chainDest.get(chainDest.size() - 1);
415 if (currentCert.getSubjectDN().equals(currentCert.getIssuerDN())) {
416 log.debug("Found self-signed root cert: " + currentCert.getSubjectDN());
419 for (int i = 0; chainSource.length > i; i++) {
420 if (currentCert.getIssuerDN().equals(chainSource[i].getSubjectDN())) {
421 chainDest.add(chainSource[i]);
422 walkChain(chainSource, chainDest);
426 log.error("Incomplete certificate chain.");
427 throw new InvalidCertificateChainException("Incomplete cerficate chain.");
432 * Given a java keystore, private key, and matching certificate chain; creates a new keystore containing the union
436 * the name of the jce provider to use
437 * @param keyAlgorithm
438 * the algorithm of the key to be added, defaults to RSA if null
440 * strema used to retrieve the private key, can contain a PEM encoded or pkcs8 encoded key
442 * stream used to retrieve certificates, can contain a series of PEM encoded certs or a pkcs7 chain
443 * @param keyStoreInStream
444 * stream used to retrieve the initial keystore
446 * the type of the keystore
448 * the alias under which the key/chain should be saved
449 * @param keyStorePassword
450 * password used to verify the integrity of the old keystore and save the new keystore
452 * the password for saving the key
454 * indicates this is a secret key import
455 * @return an OutputStream containing the new keystore
456 * @throws ExtKeyToolException
457 * if there a problem importing the key
460 public ByteArrayOutputStream importKey(String provider, String keyAlgorithm, InputStream keyStream,
461 InputStream chainStream, InputStream keyStoreInStream, String storeType, String keyAlias,
462 char[] keyStorePassword, char[] keyPassword, boolean secret) throws ExtKeyToolException {
464 log.info("Importing " + (secret ? "key pair" : "secret key."));
467 // The Sun provider incorrectly reads only the first cert in the stream.
468 // No loss, it won't even read RSA keys
469 if (provider == "SUN") {
470 log.error("Sorry, this function not supported with the SUN provider.");
471 throw new ExtKeyToolException("Sorry, this function not supported with the SUN provider.");
474 KeyStore keyStore = loadKeyStore(provider, keyStoreInStream, storeType, keyStorePassword);
476 if (keyAlias == null) {
477 log.error("Key alias must be specified.");
478 throw new ExtKeyToolException("Key alias must be specified.");
480 if (keyStore.containsAlias(keyAlias) == true && keyStore.isKeyEntry(keyAlias)) {
481 log.error("Could not import key: " + "key alias (" + keyAlias + ") already exists");
482 throw new ExtKeyToolException("Could not import key: " + "key alias (" + keyAlias + ") already exists");
484 keyStore.deleteEntry(keyAlias);
487 log.info("Reading secret key.");
488 if (keyAlgorithm == null) {
489 keyAlgorithm = "AES";
491 log.debug("Using key algorithm: (" + keyAlgorithm + ")");
492 SecretKey key = readSecretKey(provider, keyStream, keyAlgorithm);
493 keyStore.setKeyEntry(keyAlias, key, keyPassword, null);
495 log.info("Reading private key.");
496 if (keyAlgorithm == null) {
497 keyAlgorithm = "RSA";
499 log.debug("Using key algorithm: (" + keyAlgorithm + ")");
500 PrivateKey key = readPrivateKey(provider, keyStream, keyAlgorithm);
502 log.info("Reading certificate chain.");
504 CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider);
505 Collection<? extends Certificate> chain = certFactory.generateCertificates(new BufferedInputStream(
507 if (chain.isEmpty()) {
508 log.error("Input did not contain any valid certificates.");
509 throw new ExtKeyToolException("Input did not contain any valid certificates.");
512 X509Certificate[] verifiedChain = linkChain(keyAlgorithm, (X509Certificate[]) chain
513 .toArray(new X509Certificate[0]), key);
515 keyStore.setKeyEntry(keyAlias, key, keyPassword, verifiedChain);
518 ByteArrayOutputStream keyStoreOutStream = new ByteArrayOutputStream();
519 keyStore.store(keyStoreOutStream, keyStorePassword);
520 log.info("Key Store saved to stream.");
521 return keyStoreOutStream;
523 } catch (KeyStoreException e) {
524 log.error("Encountered a problem accessing the keystore: " + e.getMessage());
525 throw new ExtKeyToolException("Encountered a problem accessing the keystore: " + e.getMessage());
526 } catch (CertificateException e) {
527 log.error("Could not load certificate(s): " + e.getMessage());
528 throw new ExtKeyToolException("Could not load certificate(s): " + e.getMessage());
529 } catch (NoSuchProviderException e) {
530 log.error("The specified provider is not available.");
531 throw new ExtKeyToolException("The specified provider is not available.");
532 } catch (IOException ioe) {
533 log.error("Could not export key: " + ioe);
534 throw new ExtKeyToolException("Could not export key: " + ioe);
535 } catch (NoSuchAlgorithmException nse) {
536 log.error("Could not save with the installed JCE providers: " + nse);
537 throw new ExtKeyToolException("Could not save with the installed JCE providers: " + nse);
542 * Tries to decipher command line arguments.
544 * @throws IllegalArgumentException
545 * if arguments are not properly formatted
548 private static Properties parseArguments(String[] args) throws IllegalArgumentException {
550 if (args.length < 1) { throw new IllegalArgumentException("No arguments found."); }
551 Properties parsedArguments = new Properties();
553 for (int i = 0; (i < args.length) && args[i].startsWith("-"); i++) {
555 String flags = args[i];
558 if (flags.equalsIgnoreCase("-exportkey")) {
559 parsedArguments.setProperty("command", "exportKey");
560 } else if (flags.equalsIgnoreCase("-importkey")) {
561 parsedArguments.setProperty("command", "importKey");
565 else if (flags.equalsIgnoreCase("-secret")) {
566 parsedArguments.setProperty("secret", "true");
567 } else if (flags.equalsIgnoreCase("-alias")) {
568 if (++i == args.length) { throw new IllegalArgumentException("The argument -alias requires a parameter"); }
569 parsedArguments.setProperty("alias", args[i]);
570 } else if (flags.equalsIgnoreCase("-keyfile")) {
571 if (++i == args.length) { throw new IllegalArgumentException(
572 "The argument -keyfile requires a parameter"); }
573 parsedArguments.setProperty("keyFile", args[i]);
574 } else if (flags.equalsIgnoreCase("-certfile")) {
575 if (++i == args.length) { throw new IllegalArgumentException(
576 "The argument -certfile requires a parameter"); }
577 parsedArguments.setProperty("certFile", args[i]);
578 } else if (flags.equalsIgnoreCase("-keystore")) {
579 if (++i == args.length) { throw new IllegalArgumentException(
580 "The argument -keystore requires a parameter"); }
581 parsedArguments.setProperty("keyStore", args[i]);
582 } else if (flags.equalsIgnoreCase("-storepass")) {
583 if (++i == args.length) { throw new IllegalArgumentException(
584 "The argument -storepass requires a parameter"); }
585 parsedArguments.setProperty("storePass", args[i]);
586 } else if (flags.equalsIgnoreCase("-storetype")) {
587 if (++i == args.length) { throw new IllegalArgumentException(
588 "The argument -storetype requires a parameter"); }
589 parsedArguments.setProperty("storeType", args[i]);
590 } else if (flags.equalsIgnoreCase("-keypass")) {
591 if (++i == args.length) { throw new IllegalArgumentException(
592 "The argument -keypass requires a parameter"); }
593 parsedArguments.setProperty("keyPass", args[i]);
594 } else if (flags.equalsIgnoreCase("-provider")) {
595 if (++i == args.length) { throw new IllegalArgumentException(
596 "The argument -provider requires a parameter"); }
597 parsedArguments.setProperty("provider", args[i]);
598 } else if (flags.equalsIgnoreCase("-file")) {
599 if (++i == args.length) { throw new IllegalArgumentException("The argument -file requires a parameter"); }
600 parsedArguments.setProperty("file", args[i]);
601 } else if (flags.equalsIgnoreCase("-algorithm")) {
602 if (++i == args.length) { throw new IllegalArgumentException(
603 "The argument -algorithm requires a parameter"); }
604 parsedArguments.setProperty("keyAlgorithm", args[i]);
608 else if (flags.equalsIgnoreCase("-v")) {
609 parsedArguments.setProperty("verbose", "true");
610 } else if (flags.equalsIgnoreCase("-rfc")) {
611 parsedArguments.setProperty("rfc", "true");
613 throw new IllegalArgumentException("Unrecognized argument: " + flags);
616 if (parsedArguments.getProperty("command", null) == null) { throw new IllegalArgumentException(
617 "No action specified"); }
618 return parsedArguments;
622 * Ensures that providers specified on the command line are in fact loaded into the current environment.
624 * @return the name of the provider add, null if no provider was added
627 protected String initProvider(Properties arguments) {
630 if (arguments.getProperty("provider", null) != null) {
632 Provider provider = (Provider) Class.forName(arguments.getProperty("provider")).newInstance();
633 log.info("Adding Provider to environment: (" + provider.getName() + ")");
634 Security.addProvider(provider);
635 return provider.getName();
637 } catch (Exception e) {
638 log.error("Could not load specified jce provider: " + e);
645 * Initializes Log4J logger mode based on command line arguments.
648 protected void startLogger(Properties arguments) {
650 Logger root = Logger.getRootLogger();
651 if (arguments.getProperty("verbose", null) == null || arguments.getProperty("verbose", null).equals("false")) {
652 root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN)));
653 root.setLevel(Level.WARN);
655 root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
656 root.setLevel(Level.DEBUG);
660 public static void main(String[] args) {
663 ExtKeyTool tool = new ExtKeyTool();
664 Properties arguments = null;
666 arguments = parseArguments(args);
667 } catch (IllegalArgumentException iae) {
668 System.err.println("Illegal argument specified: " + iae.getMessage()
669 + System.getProperty("line.separator"));
670 printUsage(System.err);
673 tool.startLogger(arguments);
674 String providerName = tool.initProvider(arguments);
675 if (providerName != null) {
676 arguments.setProperty("providerName", providerName);
680 } catch (ExtKeyToolException ske) {
681 log.fatal("Cannot Perform Operation: " + ske.getMessage() + System.getProperty("line.separator"));
682 LogManager.shutdown();
683 printUsage(System.err);
688 * Based on on a set of properties, executes <code>ExtKeyTool</code> actions.
691 * runtime parameters specified on the command line
694 private void run(Properties arguments) throws ExtKeyToolException {
696 // common for all actions
697 char[] storePassword = null;
698 if (arguments.getProperty("storePass", null) != null) {
699 storePassword = arguments.getProperty("storePass").toCharArray();
702 String providerName = null;
703 if (arguments.getProperty("providerName", null) != null) {
704 providerName = arguments.getProperty("providerName");
706 providerName = "SUN";
710 if (arguments.getProperty("command").equals("exportKey")) {
713 if ("true".equalsIgnoreCase(arguments.getProperty("rfc", null))) {
717 PrintStream outStream = null;
718 if (arguments.getProperty("file", null) != null) {
720 outStream = new PrintStream(new FileOutputStream(arguments.getProperty("file")));
721 } catch (FileNotFoundException e) {
722 throw new ExtKeyToolException("Could not open output file: " + e);
725 outStream = System.out;
729 exportKey(providerName, outStream, new FileInputStream(resolveKeyStore(arguments.getProperty(
730 "keyStore", null))), arguments.getProperty("storeType", null), storePassword, arguments
731 .getProperty("alias", null), resolveKeyPass(arguments.getProperty("keyPass", null),
732 storePassword), rfc);
733 } catch (FileNotFoundException e) {
734 throw new ExtKeyToolException("KeyStore not found.");
739 } else if (arguments.getProperty("command").equals("importKey")) {
741 InputStream keyInStream = null;
742 if (arguments.getProperty("keyFile", null) != null) {
744 keyInStream = new FileInputStream(arguments.getProperty("keyFile"));
745 } catch (FileNotFoundException e) {
746 throw new ExtKeyToolException("Could not open key file." + e.getMessage());
749 throw new IllegalArgumentException("Key file must be specified.");
752 InputStream certInStream = null;
753 if (arguments.getProperty("certFile", null) != null) {
755 certInStream = new FileInputStream(arguments.getProperty("certFile"));
756 } catch (FileNotFoundException e) {
757 throw new ExtKeyToolException("Could not open cert file." + e.getMessage());
759 } else if (!arguments.getProperty("secret").equalsIgnoreCase("true")) { throw new IllegalArgumentException(
760 "Certificate file must be specified."); }
764 ByteArrayOutputStream keyStoreOutStream = importKey(providerName, arguments.getProperty("keyAlgorithm",
765 null), keyInStream, certInStream, new FileInputStream(resolveKeyStore(arguments.getProperty(
766 "keyStore", null))), arguments.getProperty("storeType", null), arguments.getProperty("alias",
767 null), storePassword, resolveKeyPass(arguments.getProperty("keyPass", null), storePassword),
768 arguments.getProperty("secret", "false").equalsIgnoreCase("true"));
771 // A quick sanity check before we overwrite the old keystore
772 if (keyStoreOutStream == null || keyStoreOutStream.size() < 1) { throw new ExtKeyToolException(
773 "Failed to create keystore: results are null"); }
775 .writeTo(new FileOutputStream(resolveKeyStore(arguments.getProperty("keyStore", null))));
776 System.out.println("Key import successful.");
778 } catch (FileNotFoundException e) {
779 throw new ExtKeyToolException("Could not open keystore file." + e.getMessage());
780 } catch (IOException e) {
781 throw new ExtKeyToolException("Error writing keystore." + e.getMessage());
785 throw new IllegalArgumentException("This keytool cannot perform the operation: ("
786 + arguments.getProperty("command") + ")");
792 * Determines the location of the keystore to use when performing the action
794 * @return the <code>File</code> representation of the selected keystore
797 protected File resolveKeyStore(String keyStoreLocation) throws ExtKeyToolException, FileNotFoundException {
799 if (keyStoreLocation == null) {
800 keyStoreLocation = System.getProperty("user.home") + File.separator + ".keystore";
802 log.debug("Using keystore (" + keyStoreLocation + ")");
803 File file = new File(keyStoreLocation);
804 if (file.exists() && file.length() == 0) {
805 log.error("Keystore file is empty.");
806 throw new ExtKeyToolException("Keystore file is empty.");
812 * Decides what password to use for storing/retrieving keys from the keystore. NOTE: Possible terminal interaction
815 * @return a char array containing the password
818 protected char[] resolveKeyPass(String keyPass, char[] storePass) {
820 if (keyPass != null) {
821 return keyPass.toCharArray();
823 System.out.println("Enter key password");
824 System.out.print("\t(RETURN if same as keystore password): ");
827 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
828 String passwordInput = reader.readLine();
829 passwordInput.trim();
830 if (passwordInput != null && !passwordInput.equals("")) { return passwordInput.toCharArray(); }
831 } catch (IOException e) {
832 log.warn(e.getMessage());
834 log.warn("No password specified, defaulting to keystore password.");
839 private static void printUsage(PrintStream out) {
841 out.println("extkeytool usage:");
842 out.print("-exportkey [-v] [-rfc] [-alias <alias>] ");
843 out.println("[-keystore <keystore>] ");
844 out.print("\t [-storepass <storepass>] ");
845 out.println("[-storetype <storetype>]");
846 out.print("\t [-keypass <keypass>] ");
847 out.println("[-provider <provider_class_name>] ");
848 out.print("\t [-file <output_file>] ");
852 out.print("-importkey [-v] [-secret] [-alias <alias>] ");
853 out.println("[-keyfile <key_file>]");
854 out.print("\t [-keystore <keystore>] ");
855 out.println("[-storepass <storepass>]");
856 out.print("\t [-storetype <storetype>] ");
857 out.println("[-keypass <keypass>] ");
858 out.print("\t [-provider <provider_class_name>] ");
859 out.println("[-certfile <cert_file>] ");
860 out.print("\t [-algorithm <key_algorithm>] ");
866 * Auto-enlarging container for bytes.
869 // Sure makes you wish bytes were first class objects.
870 private class ByteContainer {
872 private byte[] buffer;
874 private int currentSize = 0;
876 private ByteContainer(int cushion) {
878 buffer = new byte[cushion];
879 this.cushion = cushion;
882 private void grow() {
884 log.debug("Growing ByteContainer.");
885 int newSize = currentSize + cushion;
886 byte[] b = new byte[newSize];
887 int toCopy = Math.min(currentSize, newSize);
889 for (i = 0; i < toCopy; i++) {
896 * Returns an array of the bytes in the container.
900 private byte[] toByteArray() {
902 byte[] b = new byte[currentSize];
903 for (int i = 0; i < currentSize; i++) {
910 * Add one byte to the end of the container.
913 private void append(byte b) {
915 if (currentSize == buffer.length) {
918 buffer[currentSize] = b;
925 * Signals that an error was encounted while using <code>ExtKeyTool</code> functions.
928 protected class ExtKeyToolException extends Exception {
930 protected ExtKeyToolException(String message) {
937 * Signals that an error occurred while trying to constuct a certificate chain.
940 protected class InvalidCertificateChainException extends ExtKeyToolException {
942 protected InvalidCertificateChainException(String message) {