jdk/src/share/classes/sun/security/tools/KeyTool.java
changeset 2067 6f9db5f305cd
parent 909 c7bb1699d1b0
child 2179 e172c13ca87a
--- a/jdk/src/share/classes/sun/security/tools/KeyTool.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/jdk/src/share/classes/sun/security/tools/KeyTool.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 1997-2009 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -69,6 +69,10 @@
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
+import sun.misc.BASE64Decoder;
+import sun.security.pkcs.PKCS10Attribute;
+import sun.security.pkcs.PKCS9Attribute;
+import sun.security.util.DerValue;
 import sun.security.x509.*;
 
 import static java.security.KeyStore.*;
@@ -85,7 +89,6 @@
  *
  * @since 1.2
  */
-
 public final class KeyTool {
 
     private boolean debug = false;
@@ -100,6 +103,8 @@
     private String dname = null;
     private String dest = null;
     private String filename = null;
+    private String infilename = null;
+    private String outfilename = null;
     private String srcksfname = null;
 
     // User-specified providers are added before any command is called.
@@ -117,7 +122,6 @@
     private char[] storePassNew = null;
     private char[] keyPass = null;
     private char[] keyPassNew = null;
-    private char[] oldPass = null;
     private char[] newPass = null;
     private char[] destKeyPass = null;
     private char[] srckeyPass = null;
@@ -140,6 +144,8 @@
     private Set<char[]> passwords = new HashSet<char[]> ();
     private String startDate = null;
 
+    private List <String> v3ext = new ArrayList <String> ();
+
     private static final int CERTREQ = 1;
     private static final int CHANGEALIAS = 2;
     private static final int DELETE = 3;
@@ -156,6 +162,8 @@
     private static final int PRINTCERT = 13;
     private static final int SELFCERT = 14;
     private static final int STOREPASSWD = 15;
+    private static final int GENCERT = 16;
+    private static final int PRINTCERTREQ = 17;
 
     private static final Class[] PARAM_STRING = { String.class };
 
@@ -184,7 +192,9 @@
     private void run(String[] args, PrintStream out) throws Exception {
         try {
             parseArgs(args);
-            doCommands(out);
+            if (command != -1) {
+                doCommands(out);
+            }
         } catch (Exception e) {
             System.out.println(rb.getString("keytool error: ") + e);
             if (verbose) {
@@ -214,7 +224,10 @@
      */
     void parseArgs(String[] args) {
 
-        if (args.length == 0) usage();
+        if (args.length == 0) {
+            usage();
+            return;
+        }
 
         int i=0;
 
@@ -260,6 +273,10 @@
                 command = IMPORTKEYSTORE;
             } else if (collator.compare(flags, "-genseckey") == 0) {
                 command = GENSECKEY;
+            } else if (collator.compare(flags, "-gencert") == 0) {
+                command = GENCERT;
+            } else if (collator.compare(flags, "-printcertreq") == 0) {
+                command = PRINTCERTREQ;
             }
 
             /*
@@ -337,9 +354,18 @@
             } else if (collator.compare(flags, "-validity") == 0) {
                 if (++i == args.length) errorNeedArgument(flags);
                 validity = Long.parseLong(args[i]);
+            } else if (collator.compare(flags, "-ext") == 0) {
+                if (++i == args.length) errorNeedArgument(flags);
+                v3ext.add(args[i]);
             } else if (collator.compare(flags, "-file") == 0) {
                 if (++i == args.length) errorNeedArgument(flags);
                 filename = args[i];
+            } else if (collator.compare(flags, "-infile") == 0) {
+                if (++i == args.length) errorNeedArgument(flags);
+                infilename = args[i];
+            } else if (collator.compare(flags, "-outfile") == 0) {
+                if (++i == args.length) errorNeedArgument(flags);
+                outfilename = args[i];
             } else if (collator.compare(flags, "-sslserver") == 0) {
                 if (++i == args.length) errorNeedArgument(flags);
                 sslserver = args[i];
@@ -364,7 +390,7 @@
                     }
                 }
                 providers.add(
-                        new Pair<String, String>(providerClass, providerArg));
+                        Pair.of(providerClass, providerArg));
             }
 
             /*
@@ -404,6 +430,10 @@
         }
     }
 
+    boolean isKeyStoreRelated(int cmd) {
+        return cmd != PRINTCERT && cmd != PRINTCERTREQ;
+    }
+
     /**
      * Execute the commands.
      */
@@ -568,7 +598,7 @@
         // the default, which is located in $HOME/.keystore.
         // If the command is "genkey", "identitydb", "import", or "printcert",
         // it is OK not to have a keystore.
-        if (command != PRINTCERT) {
+        if (isKeyStoreRelated(command)) {
             if (ksfname == null) {
                 ksfname = System.getProperty("user.home") + File.separator
                     + ".keystore";
@@ -721,7 +751,7 @@
                 }
             } else if (!protectedPath
                     && !KeyStoreUtil.isWindowsKeyStore(storetype)
-                    && !(command == PRINTCERT)) {
+                    && isKeyStoreRelated(command)) {
                 // here we have EXPORTCERT and LIST (info valid until STOREPASSWD)
                 System.err.print(rb.getString("Enter keystore password:  "));
                 System.err.flush();
@@ -763,7 +793,7 @@
 
         // Create a certificate factory
         if (command == PRINTCERT || command == IMPORTCERT
-               || command == IDENTITYDB) {
+                || command == IDENTITYDB) {
             cf = CertificateFactory.getInstance("X509");
         }
 
@@ -930,6 +960,41 @@
                 storePassNew = getNewPasswd("keystore password", storePass);
             }
             kssave = true;
+        } else if (command == GENCERT) {
+            if (alias == null) {
+                alias = keyAlias;
+            }
+            InputStream inStream = System.in;
+            if (infilename != null) {
+                inStream = new FileInputStream(infilename);
+            }
+            PrintStream ps = null;
+            if (outfilename != null) {
+                ps = new PrintStream(new FileOutputStream(outfilename));
+                out = ps;
+            }
+            try {
+                doGenCert(alias, sigAlgName, inStream, out);
+            } finally {
+                if (inStream != System.in) {
+                    inStream.close();
+                }
+                if (ps != null) {
+                    ps.close();
+                }
+            }
+        } else if (command == PRINTCERTREQ) {
+            InputStream inStream = System.in;
+            if (filename != null) {
+                inStream = new FileInputStream(filename);
+            }
+            try {
+                doPrintCertReq(inStream, out);
+            } finally {
+                if (inStream != System.in) {
+                    inStream.close();
+                }
+            }
         }
 
         // If we need to save the keystore, do so.
@@ -962,6 +1027,91 @@
     }
 
     /**
+     * Generate a certificate: Read PKCS10 request from in, and print
+     * certificate to out. Use alias as CA, sigAlgName as the signature
+     * type.
+     */
+    private void doGenCert(String alias, String sigAlgName, InputStream in, PrintStream out)
+            throws Exception {
+
+
+        Certificate signerCert = keyStore.getCertificate(alias);
+        byte[] encoded = signerCert.getEncoded();
+        X509CertImpl signerCertImpl = new X509CertImpl(encoded);
+        X509CertInfo signerCertInfo = (X509CertInfo)signerCertImpl.get(
+                X509CertImpl.NAME + "." + X509CertImpl.INFO);
+        X500Name owner = (X500Name)signerCertInfo.get(X509CertInfo.SUBJECT + "." +
+                                           CertificateSubjectName.DN_NAME);
+
+        Date firstDate = getStartDate(startDate);
+        Date lastDate = new Date();
+        lastDate.setTime(firstDate.getTime() + validity*1000L*24L*60L*60L);
+        CertificateValidity interval = new CertificateValidity(firstDate,
+                                                               lastDate);
+
+        PrivateKey privateKey = (PrivateKey)recoverKey(alias, storePass, keyPass).fst;
+        if (sigAlgName == null) {
+            sigAlgName = getCompatibleSigAlgName(privateKey.getAlgorithm());
+        }
+        Signature signature = Signature.getInstance(sigAlgName);
+        signature.initSign(privateKey);
+
+        X500Signer signer = new X500Signer(signature, owner);
+
+        X509CertInfo info = new X509CertInfo();
+        info.set(X509CertInfo.VALIDITY, interval);
+        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber
+                 ((int)(firstDate.getTime()/1000)));
+        info.set(X509CertInfo.VERSION,
+                     new CertificateVersion(CertificateVersion.V3));
+        info.set(X509CertInfo.ALGORITHM_ID,
+                     new CertificateAlgorithmId(signer.getAlgorithmId()));
+        info.set(X509CertInfo.ISSUER,
+                     new CertificateIssuerName(signer.getSigner()));
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        boolean canRead = false;
+        StringBuffer sb = new StringBuffer();
+        while (true) {
+            String s = reader.readLine();
+            if (s == null) break;
+            // OpenSSL does not use NEW
+            //if (s.startsWith("-----BEGIN NEW CERTIFICATE REQUEST-----")) {
+            if (s.startsWith("-----BEGIN") && s.indexOf("REQUEST") >= 0) {
+                canRead = true;
+            //} else if (s.startsWith("-----END NEW CERTIFICATE REQUEST-----")) {
+            } else if (s.startsWith("-----END") && s.indexOf("REQUEST") >= 0) {
+                break;
+            } else if (canRead) {
+                sb.append(s);
+            }
+        }
+        byte[] rawReq = new BASE64Decoder().decodeBuffer(new String(sb));
+        PKCS10 req = new PKCS10(rawReq);
+
+        info.set(X509CertInfo.KEY, new CertificateX509Key(req.getSubjectPublicKeyInfo()));
+        info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(req.getSubjectName()));
+        CertificateExtensions reqex = null;
+        Iterator<PKCS10Attribute> attrs = req.getAttributes().getAttributes().iterator();
+        while (attrs.hasNext()) {
+            PKCS10Attribute attr = attrs.next();
+            if (attr.getAttributeId().equals(PKCS9Attribute.EXTENSION_REQUEST_OID)) {
+                reqex = (CertificateExtensions)attr.getAttributeValue();
+            }
+        }
+        CertificateExtensions ext = createV3Extensions(
+                reqex,
+                null,
+                v3ext,
+                req.getSubjectPublicKeyInfo(),
+                signerCert.getPublicKey());
+        info.set(X509CertInfo.EXTENSIONS, ext);
+        X509CertImpl cert = new X509CertImpl(info);
+        cert.sign(privateKey, sigAlgName);
+        dumpCert(cert, out);
+    }
+
+    /**
      * Creates a PKCS#10 cert signing request, corresponding to the
      * keys (and name) associated with a given alias.
      */
@@ -972,10 +1122,10 @@
             alias = keyAlias;
         }
 
-        Object[] objs = recoverKey(alias, storePass, keyPass);
-        PrivateKey privKey = (PrivateKey)objs[0];
+        Pair<Key,char[]> objs = recoverKey(alias, storePass, keyPass);
+        PrivateKey privKey = (PrivateKey)objs.fst;
         if (keyPass == null) {
-            keyPass = (char[])objs[1];
+            keyPass = objs.snd;
         }
 
         Certificate cert = keyStore.getCertificate(alias);
@@ -986,21 +1136,14 @@
             throw new Exception(form.format(source));
         }
         PKCS10 request = new PKCS10(cert.getPublicKey());
+        CertificateExtensions ext = createV3Extensions(null, null, v3ext, cert.getPublicKey(), null);
+        // Attribute name is not significant
+        request.getAttributes().setAttribute(X509CertInfo.EXTENSIONS,
+                new PKCS10Attribute(PKCS9Attribute.EXTENSION_REQUEST_OID, ext));
 
         // Construct an X500Signer object, so that we can sign the request
         if (sigAlgName == null) {
-            // If no signature algorithm was specified at the command line,
-            // we choose one that is compatible with the selected private key
-            String keyAlgName = privKey.getAlgorithm();
-            if ("DSA".equalsIgnoreCase(keyAlgName)
-                   || "DSS".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1WithDSA";
-            } else if ("RSA".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1WithRSA";
-            } else {
-                throw new Exception(rb.getString
-                        ("Cannot derive signature algorithm"));
-            }
+            sigAlgName = getCompatibleSigAlgName(privKey.getAlgorithm());
         }
 
         Signature signature = Signature.getInstance(sigAlgName);
@@ -1153,6 +1296,23 @@
     }
 
     /**
+     * If no signature algorithm was specified at the command line,
+     * we choose one that is compatible with the selected private key
+     */
+    private static String getCompatibleSigAlgName(String keyAlgName)
+            throws Exception {
+        if ("DSA".equalsIgnoreCase(keyAlgName)) {
+            return "SHA1WithDSA";
+        } else if ("RSA".equalsIgnoreCase(keyAlgName)) {
+            return "SHA1WithRSA";
+        } else if ("EC".equalsIgnoreCase(keyAlgName)) {
+            return "SHA1withECDSA";
+        } else {
+            throw new Exception(rb.getString
+                    ("Cannot derive signature algorithm"));
+        }
+    }
+    /**
      * Creates a new key pair and self-signed certificate.
      */
     private void doGenKeyPair(String alias, String dname, String keyAlgName,
@@ -1179,16 +1339,7 @@
         }
 
         if (sigAlgName == null) {
-            if ("DSA".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1WithDSA";
-            } else if ("RSA".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1WithRSA";
-            } else if ("EC".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1withECDSA";
-            } else {
-                throw new Exception(rb.getString
-                        ("Cannot derive signature algorithm"));
-            }
+            sigAlgName = getCompatibleSigAlgName(keyAlgName);
         }
         CertAndKeyGen keypair =
                 new CertAndKeyGen(keyAlgName, sigAlgName, providerName);
@@ -1225,6 +1376,9 @@
             keyPass = promptForKeyPass(alias, null, storePass);
         }
         keyStore.setKeyEntry(alias, privKey, keyPass, chain);
+
+        // resign so that -ext are applied.
+        doSelfCert(alias, null, sigAlgName);
     }
 
     /**
@@ -1247,9 +1401,9 @@
             throw new Exception(form.format(source));
         }
 
-        Object[] objs = recoverEntry(keyStore, orig, storePass, keyPass);
-        Entry entry = (Entry)objs[0];
-        keyPass = (char[])objs[1];
+        Pair<Entry,char[]> objs = recoverEntry(keyStore, orig, storePass, keyPass);
+        Entry entry = objs.fst;
+        keyPass = objs.snd;
 
         PasswordProtection pp = null;
 
@@ -1275,10 +1429,10 @@
         if (alias == null) {
             alias = keyAlias;
         }
-        Object[] objs = recoverKey(alias, storePass, keyPass);
-        Key privKey = (Key)objs[0];
+        Pair<Key,char[]> objs = recoverKey(alias, storePass, keyPass);
+        Key privKey = objs.fst;
         if (keyPass == null) {
-            keyPass = (char[])objs[1];
+            keyPass = objs.snd;
         }
 
         if (keyPassNew == null) {
@@ -1629,8 +1783,8 @@
             }
         }
 
-        Object[] objs = recoverEntry(srckeystore, alias, srcstorePass, srckeyPass);
-        Entry entry = (Entry)objs[0];
+        Pair<Entry,char[]> objs = recoverEntry(srckeystore, alias, srcstorePass, srckeyPass);
+        Entry entry = objs.fst;
 
         PasswordProtection pp = null;
 
@@ -1640,8 +1794,8 @@
         // so always try to protect with destKeyPass.
         if (destKeyPass != null) {
             pp = new PasswordProtection(destKeyPass);
-        } else if (objs[1] != null) {
-            pp = new PasswordProtection((char[])objs[1]);
+        } else if (objs.snd != null) {
+            pp = new PasswordProtection(objs.snd);
         }
 
         try {
@@ -1726,9 +1880,50 @@
         }
     }
 
+    private void doPrintCertReq(InputStream in, PrintStream out)
+            throws Exception {
+
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        StringBuffer sb = new StringBuffer();
+        boolean started = false;
+        while (true) {
+            String s = reader.readLine();
+            if (s == null) break;
+            if (!started) {
+                if (s.startsWith("-----")) {
+                    started = true;
+                }
+            } else {
+                if (s.startsWith("-----")) {
+                    break;
+                }
+                sb.append(s);
+            }
+        }
+        PKCS10 req = new PKCS10(new BASE64Decoder().decodeBuffer(new String(sb)));
+
+        PublicKey pkey = req.getSubjectPublicKeyInfo();
+        out.printf(rb.getString("PKCS #10 Certificate Request (Version 1.0)\n" +
+                "Subject: %s\nPublic Key: %s format %s key\n"),
+                req.getSubjectName(), pkey.getFormat(), pkey.getAlgorithm());
+        for (PKCS10Attribute attr: req.getAttributes().getAttributes()) {
+            ObjectIdentifier oid = attr.getAttributeId();
+            if (oid.equals(PKCS9Attribute.EXTENSION_REQUEST_OID)) {
+                CertificateExtensions exts = (CertificateExtensions)attr.getAttributeValue();
+                printExtensions(rb.getString("Extension Request:"), exts, out);
+            } else {
+                out.println(attr.getAttributeId());
+                out.println(attr.getAttributeValue());
+            }
+        }
+        if (debug) {
+            out.println(req);   // Just to see more, say, public key length...
+        }
+    }
+
     /**
      * Reads a certificate (or certificate chain) and prints its contents in
-     * a human readbable format.
+     * a human readable format.
      */
     private void printCertFromStream(InputStream in, PrintStream out)
         throws Exception
@@ -1840,7 +2035,18 @@
                 inStream = new FileInputStream(filename);
             }
             try {
-                printCertFromStream(inStream, out);
+                // Read the full stream before feeding to X509Factory,
+                // otherwise, keytool -gencert | keytool -printcert
+                // might not work properly, since -gencert is slow
+                // and there's no data in the pipe at the beginning.
+                ByteArrayOutputStream bout = new ByteArrayOutputStream();
+                byte[] b = new byte[4096];
+                while (true) {
+                    int len = inStream.read(b);
+                    if (len < 0) break;
+                    bout.write(b, 0, len);
+                }
+                printCertFromStream(new ByteArrayInputStream(bout.toByteArray()), out);
             } finally {
                 if (inStream != System.in) {
                     inStream.close();
@@ -1859,27 +2065,14 @@
             alias = keyAlias;
         }
 
-        Object[] objs = recoverKey(alias, storePass, keyPass);
-        PrivateKey privKey = (PrivateKey)objs[0];
+        Pair<Key,char[]> objs = recoverKey(alias, storePass, keyPass);
+        PrivateKey privKey = (PrivateKey)objs.fst;
         if (keyPass == null)
-            keyPass = (char[])objs[1];
+            keyPass = objs.snd;
 
         // Determine the signature algorithm
         if (sigAlgName == null) {
-            // If no signature algorithm was specified at the command line,
-            // we choose one that is compatible with the selected private key
-            String keyAlgName = privKey.getAlgorithm();
-            if ("DSA".equalsIgnoreCase(keyAlgName)
-                   || "DSS".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1WithDSA";
-            } else if ("RSA".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1WithRSA";
-            } else if ("EC".equalsIgnoreCase(keyAlgName)) {
-                sigAlgName = "SHA1withECDSA";
-            } else {
-                throw new Exception
-                        (rb.getString("Cannot derive signature algorithm"));
-            }
+            sigAlgName = getCompatibleSigAlgName(privKey.getAlgorithm());
         }
 
         // Get the old certificate
@@ -1943,11 +2136,16 @@
         certInfo.set(CertificateAlgorithmId.NAME + "." +
                      CertificateAlgorithmId.ALGORITHM, sigAlgid);
 
-        // first upgrade to version 3
-
         certInfo.set(X509CertInfo.VERSION,
                         new CertificateVersion(CertificateVersion.V3));
 
+        CertificateExtensions ext = createV3Extensions(
+                null,
+                (CertificateExtensions)certInfo.get(X509CertInfo.EXTENSIONS),
+                v3ext,
+                oldCert.getPublicKey(),
+                null);
+        certInfo.set(X509CertInfo.EXTENSIONS, ext);
         // Sign the new certificate
         newCert = new X509CertImpl(certInfo);
         newCert.sign(privKey, sigAlgName);
@@ -1985,10 +2183,10 @@
             alias = keyAlias;
         }
 
-        Object[] objs = recoverKey(alias, storePass, keyPass);
-        PrivateKey privKey = (PrivateKey)objs[0];
+        Pair<Key,char[]> objs = recoverKey(alias, storePass, keyPass);
+        PrivateKey privKey = (PrivateKey)objs.fst;
         if (keyPass == null) {
-            keyPass = (char[])objs[1];
+            keyPass = objs.snd;
         }
 
         Certificate userCert = keyStore.getCertificate(alias);
@@ -2290,36 +2488,40 @@
                         };
         out.println(form.format(source));
 
-        int extnum = 0;
         if (cert instanceof X509CertImpl) {
             X509CertImpl impl = (X509CertImpl)cert;
-            if (cert.getCriticalExtensionOIDs() != null) {
-                for (String extOID : cert.getCriticalExtensionOIDs()) {
-                    if (extnum == 0) {
-                        out.println();
-                        out.println(rb.getString("Extensions: "));
-                        out.println();
-                    }
-                    out.println("#"+(++extnum)+": "+
-                            impl.getExtension(new ObjectIdentifier(extOID)));
+            X509CertInfo certInfo = (X509CertInfo)impl.get(X509CertImpl.NAME
+                                                           + "." +
+                                                           X509CertImpl.INFO);
+            CertificateExtensions exts = (CertificateExtensions)
+                    certInfo.get(X509CertInfo.EXTENSIONS);
+            printExtensions(rb.getString("Extensions: "), exts, out);
+        }
+    }
+
+    private static void printExtensions(String title, CertificateExtensions exts, PrintStream out)
+            throws Exception {
+        int extnum = 0;
+        Iterator<Extension> i1 = exts.getAllExtensions().iterator();
+        Iterator<Extension> i2 = exts.getUnparseableExtensions().values().iterator();
+        while (i1.hasNext() || i2.hasNext()) {
+            Extension ext = i1.hasNext()?i1.next():i2.next();
+            if (extnum == 0) {
+                out.println();
+                out.println(title);
+                out.println();
+            }
+            out.print("#"+(++extnum)+": "+ ext);
+            if (ext.getClass() == Extension.class) {
+                byte[] v = ext.getExtensionValue();
+                if (v.length == 0) {
+                    out.println(rb.getString("(Empty value)"));
+                } else {
+                    new sun.misc.HexDumpEncoder().encode(ext.getExtensionValue(), out);
+                    out.println();
                 }
             }
-            if (cert.getNonCriticalExtensionOIDs() != null) {
-                for (String extOID : cert.getNonCriticalExtensionOIDs()) {
-                    if (extnum == 0) {
-                        out.println();
-                        out.println(rb.getString("Extensions: "));
-                        out.println();
-                    }
-                    Extension ext = impl.getExtension(new ObjectIdentifier(extOID));
-                    if (ext != null) {
-                        out.println("#"+(++extnum)+": "+ ext);
-                    } else {
-                        out.println("#"+(++extnum)+": "+
-                                impl.getUnparseableExtension(new ObjectIdentifier(extOID)));
-                    }
-                }
-            }
+            out.println();
         }
     }
 
@@ -2470,7 +2672,7 @@
      * recovered private key, and the 2nd element is the password used to
      * recover it.
      */
-    private Object[] recoverKey(String alias, char[] storePass,
+    private Pair<Key,char[]> recoverKey(String alias, char[] storePass,
                                        char[] keyPass)
         throws Exception
     {
@@ -2510,7 +2712,7 @@
             key = keyStore.getKey(alias, keyPass);
         }
 
-        return new Object[] {key, keyPass};
+        return Pair.of(key, keyPass);
     }
 
     /**
@@ -2520,7 +2722,7 @@
      * recovered entry, and the 2nd element is the password used to
      * recover it (null if no password).
      */
-    private Object[] recoverEntry(KeyStore ks,
+    private Pair<Entry,char[]> recoverEntry(KeyStore ks,
                             String alias,
                             char[] pstore,
                             char[] pkey) throws Exception {
@@ -2585,7 +2787,7 @@
             }
         }
 
-        return new Object[] {entry, pkey};
+        return Pair.of(entry, pkey);
     }
     /**
      * Gets the requested finger print of the certificate.
@@ -3027,6 +3229,443 @@
     }
 
     /**
+     * Match a command (may be abbreviated) with a command set.
+     * @param s the command provided
+     * @param list the legal command set
+     * @return the position of a single match, or -1 if none matched
+     * @throws Exception if s is ambiguous
+     */
+    private static int oneOf(String s, String... list) throws Exception {
+        int[] match = new int[list.length];
+        int nmatch = 0;
+        for (int i = 0; i<list.length; i++) {
+            String one = list[i];
+            if (one.toLowerCase().startsWith(s.toLowerCase())) {
+                match[nmatch++] = i;
+            } else {
+                StringBuffer sb = new StringBuffer();
+                boolean first = true;
+                for (char c: one.toCharArray()) {
+                    if (first) {
+                        sb.append(c);
+                        first = false;
+                    } else {
+                        if (!Character.isLowerCase(c)) {
+                            sb.append(c);
+                        }
+                    }
+                }
+                if (sb.toString().equalsIgnoreCase(s)) {
+                    match[nmatch++] = i;
+                }
+            }
+        }
+        if (nmatch == 0) return -1;
+        if (nmatch == 1) return match[0];
+        StringBuffer sb = new StringBuffer();
+        MessageFormat form = new MessageFormat(rb.getString
+            ("command {0} is ambiguous:"));
+        Object[] source = {s};
+        sb.append(form.format(source) +"\n    ");
+        for (int i=0; i<nmatch; i++) {
+            sb.append(" " + list[match[i]]);
+        }
+        throw new Exception(sb.toString());
+    }
+
+    /**
+     * Create a GeneralName object from known types
+     * @param t one of 5 known types
+     * @param v value
+     * @return which one
+     */
+    private GeneralName createGeneralName(String t, String v)
+            throws Exception {
+        GeneralNameInterface gn;
+        int p = oneOf(t, "EMAIL", "URI", "DNS", "IP", "OID");
+        if (p < 0) {
+            throw new Exception(rb.getString(
+                    "Unrecognized GeneralName type: ") + t);
+        }
+        switch (p) {
+            case 0: gn = new RFC822Name(v); break;
+            case 1: gn = new URIName(v); break;
+            case 2: gn = new DNSName(v); break;
+            case 3: gn = new IPAddressName(v); break;
+            default: gn = new OIDName(v); break; //4
+        }
+        return new GeneralName(gn);
+    }
+
+    private static final String[] extSupported = {
+                        "BasicConstraints",
+                        "KeyUsage",
+                        "ExtendedKeyUsage",
+                        "SubjectAlternativeName",
+                        "IssuerAlternativeName",
+                        "SubjectInfoAccess",
+                        "AuthorityInfoAccess",
+    };
+
+    private ObjectIdentifier findOidForExtName(String type)
+            throws Exception {
+        switch (oneOf(type, extSupported)) {
+            case 0: return PKIXExtensions.BasicConstraints_Id;
+            case 1: return PKIXExtensions.KeyUsage_Id;
+            case 2: return PKIXExtensions.ExtendedKeyUsage_Id;
+            case 3: return PKIXExtensions.SubjectAlternativeName_Id;
+            case 4: return PKIXExtensions.IssuerAlternativeName_Id;
+            case 5: return PKIXExtensions.SubjectInfoAccess_Id;
+            case 6: return PKIXExtensions.AuthInfoAccess_Id;
+            default: return new ObjectIdentifier(type);
+        }
+    }
+
+    /**
+     * Create X509v3 extensions from a string representation. Note that the
+     * SubjectKeyIdentifierExtension will always be created non-critical besides
+     * the extension requested in the <code>extstr</code> argument.
+     *
+     * @param reqex the requested extensions, can be null, used for -gencert
+     * @param ext the original extensions, can be null, used for -selfcert
+     * @param extstrs -ext values, Read keytool doc
+     * @param pkey the public key for the certificate
+     * @param akey the public key for the authority (issuer)
+     * @return the created CertificateExtensions
+     */
+    private CertificateExtensions createV3Extensions(
+            CertificateExtensions reqex,
+            CertificateExtensions ext,
+            List <String> extstrs,
+            PublicKey pkey,
+            PublicKey akey) throws Exception {
+
+        if (ext != null && reqex != null) {
+            // This should not happen
+            throw new Exception("One of request and original should be null.");
+        }
+        if (ext == null) ext = new CertificateExtensions();
+        try {
+            // name{:critical}{=value}
+            // Honoring requested extensions
+            if (reqex != null) {
+                for(String extstr: extstrs) {
+                    if (extstr.toLowerCase().startsWith("honored=")) {
+                        List<String> list = Arrays.asList(
+                                extstr.toLowerCase().substring(8).split(","));
+                        // First check existence of "all"
+                        if (list.contains("all")) {
+                            ext = reqex;    // we know ext was null
+                        }
+                        // one by one for others
+                        for (String item: list) {
+                            if (item.equals("all")) continue;
+
+                            // add or remove
+                            boolean add = true;
+                            // -1, unchanged, 0 crtical, 1 non-critical
+                            int action = -1;
+                            String type = null;
+                            if (item.startsWith("-")) {
+                                add = false;
+                                type = item.substring(1);
+                            } else {
+                                int colonpos = item.indexOf(':');
+                                if (colonpos >= 0) {
+                                    type = item.substring(0, colonpos);
+                                    action = oneOf(item.substring(colonpos+1),
+                                            "critical", "non-critical");
+                                    if (action == -1) {
+                                        throw new Exception(rb.getString
+                                            ("Illegal value: ") + item);
+                                    }
+                                }
+                            }
+                            String n = reqex.getNameByOid(findOidForExtName(type));
+                            if (add) {
+                                Extension e = (Extension)reqex.get(n);
+                                if (!e.isCritical() && action == 0
+                                        || e.isCritical() && action == 1) {
+                                    e = Extension.newExtension(
+                                            e.getExtensionId(),
+                                            !e.isCritical(),
+                                            e.getExtensionValue());
+                                    ext.set(n, e);
+                                }
+                            } else {
+                                ext.delete(n);
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+            for(String extstr: extstrs) {
+                String name, value;
+                boolean isCritical = false;
+
+                int eqpos = extstr.indexOf('=');
+                if (eqpos >= 0) {
+                    name = extstr.substring(0, eqpos);
+                    value = extstr.substring(eqpos+1);
+                } else {
+                    name = extstr;
+                    value = null;
+                }
+
+                int colonpos = name.indexOf(':');
+                if (colonpos >= 0) {
+                    if (name.substring(colonpos+1).equalsIgnoreCase("critical")) {
+                        isCritical = true;
+                    }
+                    name = name.substring(0, colonpos);
+                }
+
+                if (name.equalsIgnoreCase("honored")) {
+                    continue;
+                }
+                int exttype = oneOf(name, extSupported);
+                switch (exttype) {
+                    case 0:     // BC
+                        int pathLen = -1;
+                        boolean isCA = false;
+                        if (value == null) {
+                            isCA = true;
+                        } else {
+                            try {   // the abbr format
+                                pathLen = Integer.parseInt(value);
+                                isCA = true;
+                            } catch (NumberFormatException ufe) {
+                                // ca:true,pathlen:1
+                                for (String part: value.split(",")) {
+                                    String[] nv = part.split(":");
+                                    if (nv.length != 2) {
+                                        throw new Exception(rb.getString
+                                                ("Illegal value: ") + extstr);
+                                    } else {
+                                        if (nv[0].equalsIgnoreCase("ca")) {
+                                            isCA = Boolean.parseBoolean(nv[1]);
+                                        } else if (nv[0].equalsIgnoreCase("pathlen")) {
+                                            pathLen = Integer.parseInt(nv[1]);
+                                        } else {
+                                            throw new Exception(rb.getString
+                                                ("Illegal value: ") + extstr);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        ext.set(BasicConstraintsExtension.NAME,
+                                new BasicConstraintsExtension(isCritical, isCA,
+                                pathLen));
+                        break;
+                    case 1:     // KU
+                        if(value != null) {
+                            boolean[] ok = new boolean[9];
+                            for (String s: value.split(",")) {
+                                int p = oneOf(s,
+                                       "digitalSignature",  // (0),
+                                       "nonRepudiation",    // (1)
+                                       "keyEncipherment",   // (2),
+                                       "dataEncipherment",  // (3),
+                                       "keyAgreement",      // (4),
+                                       "keyCertSign",       // (5),
+                                       "cRLSign",           // (6),
+                                       "encipherOnly",      // (7),
+                                       "decipherOnly",      // (8)
+                                       "contentCommitment"  // also (1)
+                                       );
+                                if (p < 0) {
+                                    throw new Exception(rb.getString("Unknown keyUsage type: ") + s);
+                                }
+                                if (p == 9) p = 1;
+                                ok[p] = true;
+                            }
+                            KeyUsageExtension kue = new KeyUsageExtension(ok);
+                            // The above KeyUsageExtension constructor does not
+                            // allow isCritical value, so...
+                            ext.set(KeyUsageExtension.NAME, Extension.newExtension(
+                                    kue.getExtensionId(),
+                                    isCritical,
+                                    kue.getExtensionValue()));
+                        } else {
+                            throw new Exception(rb.getString
+                                    ("Illegal value: ") + extstr);
+                        }
+                        break;
+                    case 2:     // EKU
+                        if(value != null) {
+                            Vector <ObjectIdentifier> v =
+                                    new Vector <ObjectIdentifier>();
+                            for (String s: value.split(",")) {
+                                int p = oneOf(s,
+                                        "anyExtendedKeyUsage",
+                                        "serverAuth",       //1
+                                        "clientAuth",       //2
+                                        "codeSigning",      //3
+                                        "emailProtection",  //4
+                                        "",                 //5
+                                        "",                 //6
+                                        "",                 //7
+                                        "timeStamping",     //8
+                                        "OCSPSigning"       //9
+                                       );
+                                if (p < 0) {
+                                    try {
+                                        v.add(new ObjectIdentifier(s));
+                                    } catch (Exception e) {
+                                        throw new Exception(rb.getString(
+                                                "Unknown extendedkeyUsage type: ") + s);
+                                    }
+                                } else if (p == 0) {
+                                    v.add(new ObjectIdentifier("2.5.29.37.0"));
+                                } else {
+                                    v.add(new ObjectIdentifier("1.3.6.1.5.5.7.3." + p));
+                                }
+                            }
+                            ext.set(ExtendedKeyUsageExtension.NAME,
+                                    new ExtendedKeyUsageExtension(isCritical, v));
+                        } else {
+                            throw new Exception(rb.getString
+                                    ("Illegal value: ") + extstr);
+                        }
+                        break;
+                    case 3:     // SAN
+                    case 4:     // IAN
+                        if(value != null) {
+                            String[] ps = value.split(",");
+                            GeneralNames gnames = new GeneralNames();
+                            for(String item: ps) {
+                                colonpos = item.indexOf(':');
+                                if (colonpos < 0) {
+                                    throw new Exception("Illegal item " + item + " in " + extstr);
+                                }
+                                String t = item.substring(0, colonpos);
+                                String v = item.substring(colonpos+1);
+                                gnames.add(createGeneralName(t, v));
+                            }
+                            if (exttype == 3) {
+                                ext.set(SubjectAlternativeNameExtension.NAME,
+                                        new SubjectAlternativeNameExtension(
+                                            isCritical, gnames));
+                            } else {
+                                ext.set(IssuerAlternativeNameExtension.NAME,
+                                        new IssuerAlternativeNameExtension(
+                                            isCritical, gnames));
+                            }
+                        } else {
+                            throw new Exception(rb.getString
+                                    ("Illegal value: ") + extstr);
+                        }
+                        break;
+                    case 5:     // SIA, always non-critical
+                    case 6:     // AIA, always non-critical
+                        if (isCritical) {
+                            throw new Exception(rb.getString(
+                                    "This extension cannot be marked as critical. ") + extstr);
+                        }
+                        if(value != null) {
+                            List<AccessDescription> accessDescriptions =
+                                    new ArrayList<AccessDescription>();
+                            String[] ps = value.split(",");
+                            for(String item: ps) {
+                                colonpos = item.indexOf(':');
+                                int colonpos2 = item.indexOf(':', colonpos+1);
+                                if (colonpos < 0 || colonpos2 < 0) {
+                                    throw new Exception(rb.getString
+                                            ("Illegal value: ") + extstr);
+                                }
+                                String m = item.substring(0, colonpos);
+                                String t = item.substring(colonpos+1, colonpos2);
+                                String v = item.substring(colonpos2+1);
+                                int p = oneOf(m,
+                                        "",
+                                        "ocsp",         //1
+                                        "caIssuers",    //2
+                                        "timeStamping", //3
+                                        "",
+                                        "caRepository"  //5
+                                        );
+                                ObjectIdentifier oid;
+                                if (p < 0) {
+                                    try {
+                                        oid = new ObjectIdentifier(m);
+                                    } catch (Exception e) {
+                                        throw new Exception(rb.getString(
+                                                "Unknown AccessDescription type: ") + m);
+                                    }
+                                } else {
+                                    oid = new ObjectIdentifier("1.3.6.1.5.5.7.48." + p);
+                                }
+                                accessDescriptions.add(new AccessDescription(
+                                        oid, createGeneralName(t, v)));
+                            }
+                            if (exttype == 5) {
+                                ext.set(SubjectInfoAccessExtension.NAME,
+                                        new SubjectInfoAccessExtension(accessDescriptions));
+                            } else {
+                                ext.set(AuthorityInfoAccessExtension.NAME,
+                                        new AuthorityInfoAccessExtension(accessDescriptions));
+                            }
+                        } else {
+                            throw new Exception(rb.getString
+                                    ("Illegal value: ") + extstr);
+                        }
+                        break;
+                    case -1:
+                        ObjectIdentifier oid = new ObjectIdentifier(name);
+                        byte[] data = null;
+                        if (value != null) {
+                            data = new byte[value.length() / 2 + 1];
+                            int pos = 0;
+                            for (char c: value.toCharArray()) {
+                                int hex;
+                                if (c >= '0' && c <= '9') {
+                                    hex = c - '0' ;
+                                } else if (c >= 'A' && c <= 'F') {
+                                    hex = c - 'A' + 10;
+                                } else if (c >= 'a' && c <= 'f') {
+                                    hex = c - 'a' + 10;
+                                } else {
+                                    continue;
+                                }
+                                if (pos % 2 == 0) {
+                                    data[pos/2] = (byte)(hex << 4);
+                                } else {
+                                    data[pos/2] += hex;
+                                }
+                                pos++;
+                            }
+                            if (pos % 2 != 0) {
+                                throw new Exception(rb.getString(
+                                        "Odd number of hex digits found: ") + extstr);
+                            }
+                            data = Arrays.copyOf(data, pos/2);
+                        } else {
+                            data = new byte[0];
+                        }
+                        ext.set(oid.toString(), new Extension(oid, isCritical,
+                                new DerValue(DerValue.tag_OctetString, data)
+                                        .toByteArray()));
+                        break;
+                }
+            }
+            // always non-critical
+            ext.set(SubjectKeyIdentifierExtension.NAME,
+                    new SubjectKeyIdentifierExtension(
+                        new KeyIdentifier(pkey).getIdentifier()));
+            if (akey != null && !pkey.equals(akey)) {
+                ext.set(AuthorityKeyIdentifierExtension.NAME,
+                        new AuthorityKeyIdentifierExtension(
+                        new KeyIdentifier(akey), null, null));
+            }
+        } catch(IOException e) {
+            throw new RuntimeException(e);
+        }
+        return ext;
+    }
+
+    /**
      * Prints the usage of this tool.
      */
     private void usage() {
@@ -3099,6 +3738,32 @@
         System.err.println(rb.getString
                 ("\t     [-startdate <startdate>]"));
         System.err.println(rb.getString
+                ("\t     [-ext <key>[:critical][=<value>]]..."));
+        System.err.println(rb.getString
+                ("\t     [-validity <valDays>] [-keypass <keypass>]"));
+        System.err.println(rb.getString
+                ("\t     [-keystore <keystore>] [-storepass <storepass>]"));
+        System.err.println(rb.getString
+                ("\t     [-storetype <storetype>] [-providername <name>]"));
+        System.err.println(rb.getString
+                ("\t     [-providerclass <provider_class_name> [-providerarg <arg>]] ..."));
+        System.err.println(rb.getString
+                ("\t     [-providerpath <pathlist>]"));
+        System.err.println();
+
+        System.err.println(rb.getString
+                ("-gencert     [-v] [-rfc] [-protected]"));
+        System.err.println(rb.getString
+                ("\t     [-infile <infile>] [-outfile <outfile>]"));
+        System.err.println(rb.getString
+                ("\t     [-alias <alias>]"));
+        System.err.println(rb.getString
+                ("\t     [-sigalg <sigalg>]"));
+        System.err.println(rb.getString
+                ("\t     [-startdate <startdate>]"));
+        System.err.println(rb.getString
+                ("\t     [-ext <key>[:critical][=<value>]]..."));
+        System.err.println(rb.getString
                 ("\t     [-validity <valDays>] [-keypass <keypass>]"));
         System.err.println(rb.getString
                 ("\t     [-keystore <keystore>] [-storepass <storepass>]"));
@@ -3202,6 +3867,10 @@
         System.err.println();
 
         System.err.println(rb.getString
+                ("-printcertreq   [-v] [-file <cert_file>]"));
+        System.err.println();
+
+        System.err.println(rb.getString
                 ("-storepasswd [-v] [-new <new_storepass>]"));
         System.err.println(rb.getString
                 ("\t     [-keystore <keystore>] [-storepass <storepass>]"));
@@ -3211,12 +3880,6 @@
                 ("\t     [-providerclass <provider_class_name> [-providerarg <arg>]] ..."));
         System.err.println(rb.getString
                 ("\t     [-providerpath <pathlist>]"));
-
-        if (debug) {
-            throw new RuntimeException("NO ERROR, SORRY");
-        } else {
-            System.exit(1);
-        }
     }
 
     private void tinyHelp() {
@@ -3270,4 +3933,8 @@
         else if (snd == null) return fst.hashCode() + 2;
         else return fst.hashCode() * 17 + snd.hashCode();
     }
+
+    public static <A,B> Pair<A,B> of(A a, B b) {
+        return new Pair<A,B>(a,b);
+    }
 }