changeset 902:90ab7b4891e3

6780416: New keytool commands/options: -gencert, -printcertreq, -ext Reviewed-by: xuelei, mullan
author weijun
date Mon, 23 Feb 2009 10:05:55 +0800
parents 8edcd68fb6ac
children 2a7c1a997102
files src/share/classes/sun/security/tools/KeyTool.java src/share/classes/sun/security/util/Resources.java src/share/classes/sun/security/x509/AccessDescription.java src/share/classes/sun/security/x509/AuthorityInfoAccessExtension.java src/share/classes/sun/security/x509/AuthorityKeyIdentifierExtension.java src/share/classes/sun/security/x509/CertAndKeyGen.java src/share/classes/sun/security/x509/CertificateExtensions.java src/share/classes/sun/security/x509/IssuerAlternativeNameExtension.java src/share/classes/sun/security/x509/OIDMap.java src/share/classes/sun/security/x509/SubjectInfoAccessExtension.java test/sun/security/tools/keytool/KeyToolTest.java test/sun/security/tools/keytool/autotest.sh test/sun/security/tools/keytool/standard.sh
diffstat 13 files changed, 1550 insertions(+), 149 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/tools/KeyTool.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/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);
+    }
 }
--- a/src/share/classes/sun/security/util/Resources.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/util/Resources.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2000-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
@@ -49,6 +49,7 @@
         // keytool
         {"keytool error: ", "keytool error: "},
         {"Illegal option:  ", "Illegal option:  "},
+        {"Illegal value: ", "Illegal value: "},
         {"Try keytool -help","Try keytool -help"},
         {"Command option <flag> needs an argument.", "Command option {0} needs an argument."},
         {"Warning:  Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified <command> value.",
@@ -281,6 +282,20 @@
         {"keytool usage:\n", "keytool usage:\n"},
 
         {"Extensions: ", "Extensions: "},
+        {"(Empty value)", "(Empty value)"},
+        {"Extension Request:", "Extension Request:"},
+        {"PKCS #10 Certificate Request (Version 1.0)\n" +
+                "Subject: %s\nPublic Key: %s format %s key\n",
+                "PKCS #10 Certificate Request (Version 1.0)\n" +
+                "Subject: %s\nPublic Key: %s format %s key\n"},
+        {"Unknown keyUsage type: ", "Unknown keyUsage type: "},
+        {"Unknown extendedkeyUsage type: ", "Unknown extendedkeyUsage type: "},
+        {"Unknown AccessDescription type: ", "Unknown AccessDescription type: "},
+        {"Unrecognized GeneralName type: ", "Unrecognized GeneralName type: "},
+        {"This extension cannot be marked as critical. ",
+                 "This extension cannot be marked as critical. "},
+        {"Odd number of hex digits found: ", "Odd number of hex digits found: "},
+        {"command {0} is ambiguous:", "command {0} is ambiguous:"},
 
         {"-certreq     [-v] [-protected]",
                 "-certreq     [-v] [-protected]"},
@@ -322,6 +337,14 @@
         {"\t     [-validity <valDays>] [-keypass <keypass>]",
                 "\t     [-validity <valDays>] [-keypass <keypass>]"},
         /** rest is same as -certreq starting from -keystore **/
+        {"-gencert     [-v] [-rfc] [-protected]",
+                "-gencert     [-v] [-rfc] [-protected]"},
+        {"\t     [-infile <infile>] [-outfile <outfile>]",
+                 "\t     [-infile <infile>] [-outfile <outfile>]"},
+        {"\t     [-sigalg <sigalg>]",
+                "\t     [-sigalg <sigalg>]"},
+        {"\t     [-ext <key>[:critical][=<value>]]...",
+                "\t     [-ext <key>[:critical][=<value>]]..."},
 
         {"-genseckey   [-v] [-protected]",
                 "-genseckey   [-v] [-protected]"},
@@ -388,6 +411,8 @@
 
         {"-printcert   [-v] [-rfc] [-file <cert_file> | -sslserver <host[:port]>]",
                 "-printcert   [-v] [-rfc] [-file <cert_file> | -sslserver <host[:port]>]"},
+        {"-printcertreq   [-v] [-file <cert_file>]",
+                 "-printcertreq   [-v] [-file <cert_file>]"},
         {"No certificate from the SSL server",
                 "No certificate from the SSL server"},
 
--- a/src/share/classes/sun/security/x509/AccessDescription.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/x509/AccessDescription.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2003-2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2003-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
@@ -48,6 +48,17 @@
     public static final ObjectIdentifier Ad_CAISSUERS_Id =
         ObjectIdentifier.newInternal(new int[] {1, 3, 6, 1, 5, 5, 7, 48, 2});
 
+    public static final ObjectIdentifier Ad_TIMESTAMPING_Id =
+        ObjectIdentifier.newInternal(new int[] {1, 3, 6, 1, 5, 5, 7, 48, 3});
+
+    public static final ObjectIdentifier Ad_CAREPOSITORY_Id =
+        ObjectIdentifier.newInternal(new int[] {1, 3, 6, 1, 5, 5, 7, 48, 5});
+
+    public AccessDescription(ObjectIdentifier accessMethod, GeneralName accessLocation) {
+        this.accessMethod = accessMethod;
+        this.accessLocation = accessLocation;
+    }
+
     public AccessDescription(DerValue derValue) throws IOException {
         DerInputStream derIn = derValue.getData();
         accessMethod = derIn.getOID();
@@ -90,7 +101,19 @@
     }
 
     public String toString() {
-        return ("accessMethod: " + accessMethod.toString() +
-                "\n   accessLocation: " + accessLocation.toString());
+        String method = null;
+        if (accessMethod.equals(Ad_CAISSUERS_Id)) {
+            method = "caIssuers";
+        } else if (accessMethod.equals(Ad_CAREPOSITORY_Id)) {
+            method = "caRepository";
+        } else if (accessMethod.equals(Ad_TIMESTAMPING_Id)) {
+            method = "timeStamping";
+        } else if (accessMethod.equals(Ad_OCSP_Id)) {
+            method = "ocsp";
+        } else {
+            method = accessMethod.toString();
+        }
+        return ("accessMethod: " + method +
+                "\n   accessLocation: " + accessLocation.toString() + "\n");
     }
 }
--- a/src/share/classes/sun/security/x509/AuthorityInfoAccessExtension.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/x509/AuthorityInfoAccessExtension.java	Mon Feb 23 10:05:55 2009 +0800
@@ -43,8 +43,9 @@
  * certificate that identifies the specific OCSP Responder to use when
  * performing on-line validation of that certificate.
  * <p>
- * This extension is defined in
- * <a href="http://www.ietf.org/rfc/rfc3280.txt">Internet X.509 PKI Certificate and Certificate Revocation List (CRL) Profile</a>. The profile permits
+ * This extension is defined in <a href="http://www.ietf.org/rfc/rfc3280.txt">
+ * Internet X.509 PKI Certificate and Certificate Revocation List
+ * (CRL) Profile</a>. The profile permits
  * the extension to be included in end-entity or CA certificates,
  * and it must be marked as non-critical. Its ASN.1 definition is as follows:
  * <pre>
--- a/src/share/classes/sun/security/x509/AuthorityKeyIdentifierExtension.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/x509/AuthorityKeyIdentifierExtension.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1997-2006 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
@@ -198,7 +198,7 @@
     public String toString() {
         String s = super.toString() + "AuthorityKeyIdentifier [\n";
         if (id != null) {
-            s += id.toString() + "\n";
+            s += id.toString();     // id already has a newline
         }
         if (names != null) {
             s += names.toString() + "\n";
--- a/src/share/classes/sun/security/x509/CertAndKeyGen.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/x509/CertAndKeyGen.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1996-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 1996-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
@@ -276,12 +276,6 @@
             info.set(X509CertInfo.ISSUER,
                      new CertificateIssuerName(issuer.getSigner()));
 
-            CertificateExtensions ext = new CertificateExtensions();
-                ext.set(SubjectKeyIdentifierExtension.NAME,
-                        new SubjectKeyIdentifierExtension(
-                            new KeyIdentifier(publicKey).getIdentifier()));
-            info.set(X509CertInfo.EXTENSIONS, ext);
-
             cert = new X509CertImpl(info);
             cert.sign(privateKey, this.sigAlg);
 
--- a/src/share/classes/sun/security/x509/CertificateExtensions.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/x509/CertificateExtensions.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1997-2006 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
@@ -231,6 +231,15 @@
         map.remove(name);
     }
 
+    public String getNameByOid(ObjectIdentifier oid) throws IOException {
+        for (String name: map.keySet()) {
+            if (map.get(name).getExtensionId().equals(oid)) {
+                return name;
+            }
+        }
+        return null;
+    }
+
     /**
      * Return an enumeration of names of attributes existing within this
      * attribute.
--- a/src/share/classes/sun/security/x509/IssuerAlternativeNameExtension.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/x509/IssuerAlternativeNameExtension.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1997-2006 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
@@ -90,6 +90,22 @@
     }
 
     /**
+     * Create a IssuerAlternativeNameExtension with the passed criticality
+     * and GeneralNames.
+     *
+     * @param critical true if the extension is to be treated as critical.
+     * @param names the GeneralNames for the issuer.
+     * @exception IOException on error.
+     */
+    public IssuerAlternativeNameExtension(Boolean critical, GeneralNames names)
+    throws IOException {
+        this.names = names;
+        this.extensionId = PKIXExtensions.IssuerAlternativeName_Id;
+        this.critical = critical.booleanValue();
+        encodeThis();
+    }
+
+    /**
      * Create a default IssuerAlternativeNameExtension.
      */
     public IssuerAlternativeNameExtension() {
--- a/src/share/classes/sun/security/x509/OIDMap.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/src/share/classes/sun/security/x509/OIDMap.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1997-2005 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
@@ -90,6 +90,8 @@
 
     private static final String CERT_ISSUER = ROOT + "." +
                                         CertificateIssuerExtension.NAME;
+    private static final String SUBJECT_INFO_ACCESS = ROOT + "." +
+                                          SubjectInfoAccessExtension.NAME;
     private static final String AUTH_INFO_ACCESS = ROOT + "." +
                                           AuthorityInfoAccessExtension.NAME;
     private static final String ISSUING_DIST_POINT = ROOT + "." +
@@ -148,6 +150,8 @@
                     "sun.security.x509.CRLDistributionPointsExtension");
         addInternal(CERT_ISSUER, PKIXExtensions.CertificateIssuer_Id,
                     "sun.security.x509.CertificateIssuerExtension");
+        addInternal(SUBJECT_INFO_ACCESS, PKIXExtensions.SubjectInfoAccess_Id,
+                    "sun.security.x509.SubjectInfoAccessExtension");
         addInternal(AUTH_INFO_ACCESS, PKIXExtensions.AuthInfoAccess_Id,
                     "sun.security.x509.AuthorityInfoAccessExtension");
         addInternal(ISSUING_DIST_POINT,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/x509/SubjectInfoAccessExtension.java	Mon Feb 23 10:05:55 2009 +0800
@@ -0,0 +1,244 @@
+/*
+ * Copyright 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.security.x509;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.util.*;
+
+import sun.security.util.DerOutputStream;
+import sun.security.util.DerValue;
+
+/**
+ * The Subject Information Access Extension (OID = 1.3.6.1.5.5.7.1.11).
+ * <p>
+ * The subject information access extension indicates how to access
+ * information and services for the subject of the certificate in which
+ * the extension appears.  When the subject is a CA, information and
+ * services may include certificate validation services and CA policy
+ * data.  When the subject is an end entity, the information describes
+ * the type of services offered and how to access them.  In this case,
+ * the contents of this extension are defined in the protocol
+ * specifications for the supported services.  This extension may be
+ * included in end entity or CA certificates.  Conforming CAs MUST mark
+ * this extension as non-critical.
+ * <p>
+ * This extension is defined in <a href="http://www.ietf.org/rfc/rfc3280.txt">
+ * Internet X.509 PKI Certificate and Certificate Revocation List
+ * (CRL) Profile</a>. The profile permits
+ * the extension to be included in end-entity or CA certificates,
+ * and it must be marked as non-critical. Its ASN.1 definition is as follows:
+ * <pre>
+ *   id-pe-subjectInfoAccess OBJECT IDENTIFIER ::= { id-pe 11 }
+ *
+ *   SubjectInfoAccessSyntax  ::=
+ *          SEQUENCE SIZE (1..MAX) OF AccessDescription
+ *
+ *   AccessDescription  ::=  SEQUENCE {
+ *          accessMethod          OBJECT IDENTIFIER,
+ *          accessLocation        GeneralName  }
+ * </pre>
+ * <p>
+ * @see Extension
+ * @see CertAttrSet
+ */
+
+public class SubjectInfoAccessExtension extends Extension
+        implements CertAttrSet<String> {
+
+    /**
+     * Identifier for this attribute, to be used with the
+     * get, set, delete methods of Certificate, x509 type.
+     */
+    public static final String IDENT =
+                                "x509.info.extensions.SubjectInfoAccess";
+
+    /**
+     * Attribute name.
+     */
+    public static final String NAME = "SubjectInfoAccess";
+    public static final String DESCRIPTIONS = "descriptions";
+
+    /**
+     * The List of AccessDescription objects.
+     */
+    private List<AccessDescription> accessDescriptions;
+
+    /**
+     * Create an SubjectInfoAccessExtension from a List of
+     * AccessDescription; the criticality is set to false.
+     *
+     * @param accessDescriptions the List of AccessDescription
+     * @throws IOException on error
+     */
+    public SubjectInfoAccessExtension(
+            List<AccessDescription> accessDescriptions) throws IOException {
+        this.extensionId = PKIXExtensions.SubjectInfoAccess_Id;
+        this.critical = false;
+        this.accessDescriptions = accessDescriptions;
+        encodeThis();
+    }
+
+    /**
+     * Create the extension from the passed DER encoded value of the same.
+     *
+     * @param critical true if the extension is to be treated as critical.
+     * @param value Array of DER encoded bytes of the actual value.
+     * @exception IOException on error.
+     */
+    public SubjectInfoAccessExtension(Boolean critical, Object value)
+            throws IOException {
+        this.extensionId = PKIXExtensions.SubjectInfoAccess_Id;
+        this.critical = critical.booleanValue();
+
+        if (!(value instanceof byte[])) {
+            throw new IOException("Illegal argument type");
+        }
+
+        extensionValue = (byte[])value;
+        DerValue val = new DerValue(extensionValue);
+        if (val.tag != DerValue.tag_Sequence) {
+            throw new IOException("Invalid encoding for " +
+                                  "SubjectInfoAccessExtension.");
+        }
+        accessDescriptions = new ArrayList<AccessDescription>();
+        while (val.data.available() != 0) {
+            DerValue seq = val.data.getDerValue();
+            AccessDescription accessDescription = new AccessDescription(seq);
+            accessDescriptions.add(accessDescription);
+        }
+    }
+
+    /**
+     * Return the list of AccessDescription objects.
+     */
+    public List<AccessDescription> getAccessDescriptions() {
+        return accessDescriptions;
+    }
+
+    /**
+     * Return the name of this attribute.
+     */
+    public String getName() {
+        return NAME;
+    }
+
+    /**
+     * Write the extension to the DerOutputStream.
+     *
+     * @param out the DerOutputStream to write the extension to.
+     * @exception IOException on encoding errors.
+     */
+    public void encode(OutputStream out) throws IOException {
+        DerOutputStream tmp = new DerOutputStream();
+        if (this.extensionValue == null) {
+            this.extensionId = PKIXExtensions.SubjectInfoAccess_Id;
+            this.critical = false;
+            encodeThis();
+        }
+        super.encode(tmp);
+        out.write(tmp.toByteArray());
+    }
+
+    /**
+     * Set the attribute value.
+     */
+    public void set(String name, Object obj) throws IOException {
+        if (name.equalsIgnoreCase(DESCRIPTIONS)) {
+            if (!(obj instanceof List)) {
+                throw new IOException("Attribute value should be of type List.");
+            }
+            accessDescriptions = (List<AccessDescription>)obj;
+        } else {
+            throw new IOException("Attribute name [" + name +
+                                "] not recognized by " +
+                                "CertAttrSet:SubjectInfoAccessExtension.");
+        }
+        encodeThis();
+    }
+
+    /**
+     * Get the attribute value.
+     */
+    public Object get(String name) throws IOException {
+        if (name.equalsIgnoreCase(DESCRIPTIONS)) {
+            return accessDescriptions;
+        } else {
+            throw new IOException("Attribute name [" + name +
+                                "] not recognized by " +
+                                "CertAttrSet:SubjectInfoAccessExtension.");
+        }
+    }
+
+    /**
+     * Delete the attribute value.
+     */
+    public void delete(String name) throws IOException {
+        if (name.equalsIgnoreCase(DESCRIPTIONS)) {
+            accessDescriptions = new ArrayList<AccessDescription>();
+        } else {
+            throw new IOException("Attribute name [" + name +
+                                "] not recognized by " +
+                                "CertAttrSet:SubjectInfoAccessExtension.");
+        }
+        encodeThis();
+    }
+
+    /**
+     * Return an enumeration of names of attributes existing within this
+     * attribute.
+     */
+    public Enumeration<String> getElements() {
+        AttributeNameEnumeration elements = new AttributeNameEnumeration();
+        elements.addElement(DESCRIPTIONS);
+        return elements.elements();
+    }
+
+     // Encode this extension value
+    private void encodeThis() throws IOException {
+        if (accessDescriptions.isEmpty()) {
+            this.extensionValue = null;
+        } else {
+            DerOutputStream ads = new DerOutputStream();
+            for (AccessDescription accessDescription : accessDescriptions) {
+                accessDescription.encode(ads);
+            }
+            DerOutputStream seq = new DerOutputStream();
+            seq.write(DerValue.tag_Sequence, ads);
+            this.extensionValue = seq.toByteArray();
+        }
+    }
+
+    /**
+     * Return the extension as user readable string.
+     */
+    public String toString() {
+        return super.toString() + "SubjectInfoAccess [\n  "
+               + accessDescriptions + "\n]\n";
+    }
+
+}
--- a/test/sun/security/tools/keytool/KeyToolTest.java	Mon Feb 23 10:05:41 2009 +0800
+++ b/test/sun/security/tools/keytool/KeyToolTest.java	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2005-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2005-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
@@ -56,11 +56,14 @@
  */
 
 import java.security.KeyStore;
-import java.util.Locale;
-import java.util.MissingResourceException;
 import sun.security.tools.KeyTool;
 import sun.security.x509.*;
 import java.io.*;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.security.cert.X509Certificate;
+import sun.security.util.ObjectIdentifier;
 
 public class KeyToolTest {
 
@@ -118,7 +121,7 @@
         lastInput = input;
         lastCommand = cmd;
 
-        // "X" is appened so that we can precisely test how input is consumed
+        // "X" is appended so that we can precisely test how input is consumed
         HumanInputStream in = new HumanInputStream(input+"X");
         test(in, cmd);
         // make sure the input string is no more no less
@@ -264,12 +267,21 @@
     }
 
     void assertTrue(boolean bool, String msg) {
+        if (debug) {
+            System.err.println("If not " + bool + ", " + msg);
+        } else {
+            System.err.print("v");
+        }
         if(!bool) {
             afterFail(lastInput, lastCommand, "TRUE");
+                System.err.println(msg);
             throw new RuntimeException(msg);
         }
     }
 
+    void assertTrue(boolean bool) {
+        assertTrue(bool, "well...");
+    }
     /**
      * Helper method, load a keystore
      * @param file file for keystore, null or "NONE" for PKCS11
@@ -827,32 +839,363 @@
         remove("mykey.cert");
     }
 
+    void v3extTest(String keyAlg) throws Exception {
+        KeyStore ks;
+        remove("x.jks");
+        String simple = "-keystore x.jks -storepass changeit -keypass changeit -noprompt -keyalg " + keyAlg + " ";
+        String pre = simple + "-genkeypair -dname CN=Olala -alias ";
+
+        // Version and SKID
+        testOK("", pre + "o1");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        assertTrue(((X509Certificate)ks.getCertificate("o1")).getVersion() == 3);
+        assertTrue(((X509CertImpl)ks.getCertificate("o1")).getSubjectKeyIdentifierExtension() != null);
+
+        // BC
+        testOK("", pre + "b1 -ext BC:critical");
+        testOK("", pre + "b2 -ext BC");
+        testOK("", pre + "b3 -ext bc");
+        testOK("", pre + "b4 -ext BasicConstraints");
+        testOK("", pre + "b5 -ext basicconstraints");
+        testOK("", pre + "b6 -ext BC=ca:true,pathlen:12");
+        testOK("", pre + "b7 -ext BC=ca:false");
+        testOK("", pre + "b8 -ext BC:critical=ca:false");
+        testOK("", pre + "b9 -ext BC=12");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        assertTrue(((X509CertImpl)ks.getCertificate("b1")).getBasicConstraintsExtension().isCritical());
+        assertTrue(!((X509CertImpl)ks.getCertificate("b2")).getBasicConstraintsExtension().isCritical());
+        assertTrue(((X509CertImpl)ks.getCertificate("b8")).getBasicConstraintsExtension().isCritical());
+        assertTrue(((X509Certificate)ks.getCertificate("b1")).getBasicConstraints() == Integer.MAX_VALUE);
+        assertTrue(((X509Certificate)ks.getCertificate("b2")).getBasicConstraints() == Integer.MAX_VALUE);
+        assertTrue(((X509Certificate)ks.getCertificate("b3")).getBasicConstraints() == Integer.MAX_VALUE);
+        assertTrue(((X509Certificate)ks.getCertificate("b4")).getBasicConstraints() == Integer.MAX_VALUE);
+        assertTrue(((X509Certificate)ks.getCertificate("b5")).getBasicConstraints() == Integer.MAX_VALUE);
+        assertTrue(((X509Certificate)ks.getCertificate("b6")).getBasicConstraints() == 12);
+        assertTrue(((X509Certificate)ks.getCertificate("b7")).getBasicConstraints() == -1);
+        assertTrue(((X509Certificate)ks.getCertificate("b9")).getBasicConstraints() == 12);
+
+        // KU
+        testOK("", pre + "ku1 -ext KeyUsage:critical=digitalsignature");
+        testOK("", pre + "ku2 -ext KU=digitalSignature");
+        testOK("", pre + "ku3 -ext KU=ds");
+        testOK("", pre + "ku4 -ext KU=dig");
+        testFail("", pre + "ku5 -ext KU=d");    // ambigous value
+        testFail("", pre + "ku6 -ext KU=cs");   // cRLSign cannot be cs
+        testOK("", pre + "ku11 -ext KU=nr");
+        testFail("", pre + "ku12 -ext KU=ke");  // ke also means keyAgreement
+        testOK("", pre + "ku12 -ext KU=keyE");
+        testFail("", pre + "ku13 -ext KU=de");  // de also means decipherOnly
+        testOK("", pre + "ku13 -ext KU=dataE");
+        testOK("", pre + "ku14 -ext KU=ka");
+        testOK("", pre + "ku15 -ext KU=kcs");
+        testOK("", pre + "ku16 -ext KU=crls");
+        testOK("", pre + "ku17 -ext KU=eo");
+        testOK("", pre + "ku18 -ext KU=do");
+        testOK("", pre + "ku19 -ext KU=cc");
+
+        testOK("", pre + "ku017 -ext KU=ds,cc,eo");
+        testOK("", pre + "ku135 -ext KU=nr,dataEncipherment,keyCertSign");
+        testOK("", pre + "ku246 -ext KU=keyEnc,cRL,keyA");
+        testOK("", pre + "ku1234 -ext KU=ka,da,keyE,nonR");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        class CheckKU {
+            void check(KeyStore ks, String alias, int... pos) throws Exception {
+                System.err.print("x");
+                boolean[] bs = ((X509Certificate)ks.getCertificate(alias)).getKeyUsage();
+                bs = Arrays.copyOf(bs, 9);
+                for (int i=0; i<bs.length; i++) {
+                    boolean found = false;
+                    for (int p: pos) {
+                        if (p == i) found = true;
+                    }
+                    if (!found ^ bs[i]) {
+                        // OK
+                    } else {
+                        throw new RuntimeException("KU not match at " + i +
+                                ": " + found + " vs " + bs[i]);
+                    }
+                }
+            }
+        }
+        CheckKU c = new CheckKU();
+        assertTrue(((X509CertImpl)ks.getCertificate("ku1")).getExtension(PKIXExtensions.KeyUsage_Id).isCritical());
+        assertTrue(!((X509CertImpl)ks.getCertificate("ku2")).getExtension(PKIXExtensions.KeyUsage_Id).isCritical());
+        c.check(ks, "ku1", 0);
+        c.check(ks, "ku2", 0);
+        c.check(ks, "ku3", 0);
+        c.check(ks, "ku4", 0);
+        c.check(ks, "ku11", 1);
+        c.check(ks, "ku12", 2);
+        c.check(ks, "ku13", 3);
+        c.check(ks, "ku14", 4);
+        c.check(ks, "ku15", 5);
+        c.check(ks, "ku16", 6);
+        c.check(ks, "ku17", 7);
+        c.check(ks, "ku18", 8);
+        c.check(ks, "ku19", 1);
+        c.check(ks, "ku11", 1);
+        c.check(ks, "ku11", 1);
+        c.check(ks, "ku11", 1);
+        c.check(ks, "ku017", 0, 1, 7);
+        c.check(ks, "ku135", 1, 3, 5);
+        c.check(ks, "ku246", 6, 2, 4);
+        c.check(ks, "ku1234", 1, 2, 3, 4);
+
+        // EKU
+        testOK("", pre + "eku1 -ext EKU:critical=sa");
+        testOK("", pre + "eku2 -ext ExtendedKeyUsage=ca");
+        testOK("", pre + "eku3 -ext EKU=cs");
+        testOK("", pre + "eku4 -ext EKU=ep");
+        testOK("", pre + "eku8 -ext EKU=ts");
+        testFail("", pre + "eku9 -ext EKU=os");
+        testOK("", pre + "eku9 -ext EKU=ocsps");
+        testOK("", pre + "eku10 -ext EKU=any");
+        testOK("", pre + "eku11 -ext EKU=1.2.3.4,1.3.5.7,ep");
+        testFail("", pre + "eku12 -ext EKU=c");
+        testFail("", pre + "eku12 -ext EKU=nothing");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        class CheckEKU {
+            void check(KeyStore ks, String alias, String... pos) throws Exception {
+                System.err.print("x");
+                List<String> bs = ((X509Certificate)ks.getCertificate(alias)).getExtendedKeyUsage();
+                int found = 0;
+                for (String p: pos) {
+                    if (bs.contains(p)) {
+                        found++;
+                    } else {
+                        throw new RuntimeException("EKU: not included " + p);
+                    }
+                }
+                if (found != bs.size()) {
+                    throw new RuntimeException("EKU: more items than expected");
+                }
+            }
+        }
+        CheckEKU cx = new CheckEKU();
+        assertTrue(((X509CertImpl)ks.getCertificate("eku1")).getExtension(PKIXExtensions.ExtendedKeyUsage_Id).isCritical());
+        assertTrue(!((X509CertImpl)ks.getCertificate("eku2")).getExtension(PKIXExtensions.ExtendedKeyUsage_Id).isCritical());
+        cx.check(ks, "eku1", "1.3.6.1.5.5.7.3.1");
+        cx.check(ks, "eku2", "1.3.6.1.5.5.7.3.2");
+        cx.check(ks, "eku3", "1.3.6.1.5.5.7.3.3");
+        cx.check(ks, "eku4", "1.3.6.1.5.5.7.3.4");
+        cx.check(ks, "eku8", "1.3.6.1.5.5.7.3.8");
+        cx.check(ks, "eku9", "1.3.6.1.5.5.7.3.9");
+        cx.check(ks, "eku10", "2.5.29.37.0");
+        cx.check(ks, "eku11", "1.3.6.1.5.5.7.3.4", "1.2.3.4", "1.3.5.7");
+
+        // SAN
+        testOK("", pre+"san1 -ext san:critical=email:me@me.org");
+        testOK("", pre+"san2 -ext san=uri:http://me.org");
+        testOK("", pre+"san3 -ext san=dns:me.org");
+        testOK("", pre+"san4 -ext san=ip:192.168.0.1");
+        testOK("", pre+"san5 -ext san=oid:1.2.3.4");
+        testOK("", pre+"san235 -ext san=uri:http://me.org,dns:me.org,oid:1.2.3.4");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        class CheckSAN {
+            // Please sort items with name type
+            void check(KeyStore ks, String alias, int type, Object... items) throws Exception {
+                int pos = 0;
+                System.err.print("x");
+                Object[] names = null;
+                if (type == 0) names = ((X509Certificate)ks.getCertificate(alias)).getSubjectAlternativeNames().toArray();
+                else names = ((X509Certificate)ks.getCertificate(alias)).getIssuerAlternativeNames().toArray();
+                Arrays.sort(names, new Comparator() {
+                    public int compare(Object o1, Object o2) {
+                        int i1 = (Integer)((List)o1).get(0);
+                        int i2 = (Integer)((List)o2).get(0);
+                        return i1 - i2;
+                    }
+                });
+                for (Object o: names) {
+                    List l = (List)o;
+                    for (Object o2: l) {
+                        if (!items[pos++].equals(o2)) {
+                            throw new RuntimeException("Not equals at " + pos
+                                    + ": " + items[pos-1] + " vs " + o2);
+                        }
+                    }
+                }
+                if (pos != items.length) {
+                    throw new RuntimeException("Extra items, pos is " + pos);
+                }
+            }
+        }
+        CheckSAN csan = new CheckSAN();
+        assertTrue(((X509CertImpl)ks.getCertificate("san1")).getSubjectAlternativeNameExtension().isCritical());
+        assertTrue(!((X509CertImpl)ks.getCertificate("san2")).getSubjectAlternativeNameExtension().isCritical());
+        csan.check(ks, "san1", 0, 1, "me@me.org");
+        csan.check(ks, "san2", 0, 6, "http://me.org");
+        csan.check(ks, "san3", 0, 2, "me.org");
+        csan.check(ks, "san4", 0, 7, "192.168.0.1");
+        csan.check(ks, "san5", 0, 8, "1.2.3.4");
+        csan.check(ks, "san235", 0, 2, "me.org", 6, "http://me.org", 8, "1.2.3.4");
+
+        // IAN
+        testOK("", pre+"ian1 -ext ian:critical=email:me@me.org");
+        testOK("", pre+"ian2 -ext ian=uri:http://me.org");
+        testOK("", pre+"ian3 -ext ian=dns:me.org");
+        testOK("", pre+"ian4 -ext ian=ip:192.168.0.1");
+        testOK("", pre+"ian5 -ext ian=oid:1.2.3.4");
+        testOK("", pre+"ian235 -ext ian=uri:http://me.org,dns:me.org,oid:1.2.3.4");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        assertTrue(((X509CertImpl)ks.getCertificate("ian1")).getIssuerAlternativeNameExtension().isCritical());
+        assertTrue(!((X509CertImpl)ks.getCertificate("ian2")).getIssuerAlternativeNameExtension().isCritical());
+        csan.check(ks, "ian1", 1, 1, "me@me.org");
+        csan.check(ks, "ian2", 1, 6, "http://me.org");
+        csan.check(ks, "ian3", 1, 2, "me.org");
+        csan.check(ks, "ian4", 1, 7, "192.168.0.1");
+        csan.check(ks, "ian5", 1, 8, "1.2.3.4");
+        csan.check(ks, "ian235", 1, 2, "me.org", 6, "http://me.org", 8, "1.2.3.4");
+
+        // SIA
+        testOK("", pre+"sia1 -ext sia=care:uri:ldap://ca.com/cn=CA");
+        testOK("", pre+"sia2 -ext sia=ts:email:ts@ca.com");
+        testFail("SIA never critical", pre+"sia3 -ext sia:critical=ts:email:ts@ca.com");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        class CheckSia {
+            void check(KeyStore ks, String alias, int type, Object... items) throws Exception {
+                int pos = 0;
+                System.err.print("x");
+                AccessDescription[] ads = null;
+                if (type == 0) {
+                    SubjectInfoAccessExtension siae = (SubjectInfoAccessExtension)((X509CertImpl)ks.getCertificate(alias)).getExtension(PKIXExtensions.SubjectInfoAccess_Id);
+                    ads = siae.getAccessDescriptions().toArray(new AccessDescription[0]);
+                } else {
+                    AuthorityInfoAccessExtension aiae = (AuthorityInfoAccessExtension)((X509CertImpl)ks.getCertificate(alias)).getExtension(PKIXExtensions.AuthInfoAccess_Id);
+                    ads = aiae.getAccessDescriptions().toArray(new AccessDescription[0]);
+                }
+                Arrays.sort(ads, new Comparator<AccessDescription>() {
+                    @Override
+                    public int compare(AccessDescription o1, AccessDescription o2) {
+                        return o1.getAccessMethod().toString().compareTo(o2.getAccessMethod().toString());
+                    }
+                });
+                for (AccessDescription ad: ads) {
+                    if (!ad.getAccessMethod().equals(items[pos++]) ||
+                            !new Integer(ad.getAccessLocation().getType()).equals(items[pos++])) {
+                        throw new RuntimeException("Not same type at " + pos);
+                    }
+                    String name = null;
+                    switch (ad.getAccessLocation().getType()) {
+                        case 1:
+                            name = ((RFC822Name)ad.getAccessLocation().getName()).getName();
+                            break;
+                        case 6:
+                            name = ((URIName)ad.getAccessLocation().getName()).getURI().toString();
+                            break;
+                        default:
+                            throw new RuntimeException("Not implemented: " + ad);
+                    }
+                    if (!name.equals(items[pos++])) {
+                        throw new Exception("Name not same for " + ad + " at pos " + pos);
+                    }
+                }
+            }
+        }
+        CheckSia csia = new CheckSia();
+        assertTrue(!((X509CertImpl)ks.getCertificate("sia1")).getExtension(PKIXExtensions.SubjectInfoAccess_Id).isCritical());
+        csia.check(ks, "sia1", 0, AccessDescription.Ad_CAREPOSITORY_Id, 6, "ldap://ca.com/cn=CA");
+        csia.check(ks, "sia2", 0, AccessDescription.Ad_TIMESTAMPING_Id, 1, "ts@ca.com");
+
+        // AIA
+        testOK("", pre+"aia1 -ext aia=cai:uri:ldap://ca.com/cn=CA");
+        testOK("", pre+"aia2 -ext aia=ocsp:email:ocsp@ca.com");
+        testFail("AIA never critical", pre+"aia3 -ext aia:critical=ts:email:ts@ca.com");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        assertTrue(!((X509CertImpl)ks.getCertificate("aia1")).getExtension(PKIXExtensions.AuthInfoAccess_Id).isCritical());
+        csia.check(ks, "aia1", 1, AccessDescription.Ad_CAISSUERS_Id, 6, "ldap://ca.com/cn=CA");
+        csia.check(ks, "aia2", 1, AccessDescription.Ad_OCSP_Id, 1, "ocsp@ca.com");
+
+        // OID
+        testOK("", pre+"oid1 -ext 1.2.3:critical=0102");
+        testOK("", pre+"oid2 -ext 1.2.3");
+        testOK("", pre+"oid12 -ext 1.2.3 -ext 1.2.4=01:02:03");
+
+        ks = loadStore("x.jks", "changeit", "JKS");
+        class CheckOid {
+            void check(KeyStore ks, String alias, String oid, byte[] value) throws Exception {
+                int pos = 0;
+                System.err.print("x");
+                Extension ex = ((X509CertImpl)ks.getCertificate(alias)).getExtension(new ObjectIdentifier(oid));
+                if (!Arrays.equals(value, ex.getValue())) {
+                    throw new RuntimeException("Not same content in " + alias + " for " + oid);
+                }
+            }
+        }
+        CheckOid coid = new CheckOid();
+        assertTrue(((X509CertImpl)ks.getCertificate("oid1")).getExtension(new ObjectIdentifier("1.2.3")).isCritical());
+        assertTrue(!((X509CertImpl)ks.getCertificate("oid2")).getExtension(new ObjectIdentifier("1.2.3")).isCritical());
+        coid.check(ks, "oid1", "1.2.3", new byte[]{1,2});
+        coid.check(ks, "oid2", "1.2.3", new byte[]{});
+        coid.check(ks, "oid12", "1.2.3", new byte[]{});
+        coid.check(ks, "oid12", "1.2.4", new byte[]{1,2,3});
+
+        // honored
+        testOK("", pre+"ca");
+        testOK("", pre+"a");
+        // request: BC,KU,1.2.3,1.2.4,1.2.5
+        testOK("", simple+"-alias a -certreq " +
+                "-ext BC=1 -ext KU=crl " +
+                "-ext 1.2.3=01 -ext 1.2.4:critical=0102 -ext 1.2.5=010203 " +
+                "-rfc -file test.req");
+        // printcertreq
+        testOK("", "-printcertreq -file test.req");
+        // issue: deny KU, change criticality of 1.2.3 and 1.2.4, change content of BC, add 2.3.4
+        testOK("", simple+"-gencert -alias ca -infile test.req -ext " +
+                "honored=all,-KU,1.2.3:critical,1.2.4:non-critical " +
+                "-ext BC=2 -ext 2.3.4=01020304 " +
+                "-debug -rfc -outfile test.cert");
+        testOK("", simple+"-importcert -file test.cert -alias a");
+        ks = loadStore("x.jks", "changeit", "JKS");
+        X509CertImpl a = (X509CertImpl)ks.getCertificate("a");
+        assertTrue(a.getAuthorityKeyIdentifierExtension() != null);
+        assertTrue(a.getSubjectKeyIdentifierExtension() != null);
+        assertTrue(a.getKeyUsage() == null);
+        assertTrue(a.getExtension(new ObjectIdentifier("1.2.3")).isCritical());
+        assertTrue(!a.getExtension(new ObjectIdentifier("1.2.4")).isCritical());
+        assertTrue(!a.getExtension(new ObjectIdentifier("1.2.5")).isCritical());
+        assertTrue(a.getExtensionValue("1.2.3").length == 3);
+        assertTrue(a.getExtensionValue("1.2.4").length == 4);
+        assertTrue(a.getExtensionValue("1.2.5").length == 5);
+        assertTrue(a.getBasicConstraints() == 2);
+        assertTrue(!a.getExtension(new ObjectIdentifier("2.3.4")).isCritical());
+        assertTrue(a.getExtensionValue("2.3.4").length == 6);
+
+        remove("x.jks");
+        remove("test.req");
+        remove("test.cert");
+    }
+
     void i18nTest() throws Exception {
         //   1.  keytool -help
         remove("x.jks");
-        try {
-            test("", "-help");
-            assertTrue(false, "Cannot come here");
-        } catch(RuntimeException e) {
-            assertTrue(e.getMessage().indexOf("NO ERROR, SORRY") != -1, "No error");
-        }
+        testOK("", "-help");
+
         //   2. keytool -genkey -v -keysize 512 Enter "a" for the keystore password. Check error (password too short). Enter "password" for the keystore password. Hit 'return' for "first and last name", "organizational unit", "City", "State", and "Country Code". Type "yes" when they ask you if everything is correct. Type 'return' for new key password.
         testOK("a\npassword\npassword\nMe\nHere\nNow\nPlace\nPlace\nUS\nyes\n\n", "-genkey -v -keysize 512 -keystore x.jks");
         //   3. keytool -list -v -storepass password
         testOK("", "-list -v -storepass password -keystore x.jks");
         //   4. keytool -list -v Type "a" for the keystore password. Check error (wrong keystore password).
         testFail("a\n", "-list -v -keystore x.jks");
-        assertTrue(ex.indexOf("password was incorrect") != -1, "");
+        assertTrue(ex.indexOf("password was incorrect") != -1);
         //   5. keytool -genkey -v -keysize 512 Enter "password" as the password. Check error (alias 'mykey' already exists).
         testFail("password\n", "-genkey -v -keysize 512 -keystore x.jks");
-        assertTrue(ex.indexOf("alias <mykey> already exists") != -1, "");
+        assertTrue(ex.indexOf("alias <mykey> already exists") != -1);
         //   6. keytool -genkey -v -keysize 512 -alias mykey2 -storepass password Hit 'return' for "first and last name", "organizational unit", "City", "State", and "Country Code". Type "yes" when they ask you if everything is correct. Type 'return' for new key password.
         testOK("\n\n\n\n\n\nyes\n\n", "-genkey -v -keysize 512 -alias mykey2 -storepass password -keystore x.jks");
         //   7. keytool -list -v Type 'password' for the store password.
         testOK("password\n", "-list -v -keystore x.jks");
         //   8. keytool -keypasswd -v -alias mykey2 -storepass password Type "a" for the new key password. Type "aaaaaa" for the new key password. Type "bbbbbb" when re-entering the new key password. Type "a" for the new key password. Check Error (too many failures).
         testFail("a\naaaaaa\nbbbbbb\na\n", "-keypasswd -v -alias mykey2 -storepass password -keystore x.jks");
-        assertTrue(ex.indexOf("Too many failures - try later") != -1, "");
+        assertTrue(ex.indexOf("Too many failures - try later") != -1);
         //   9. keytool -keypasswd -v -alias mykey2 -storepass password Type "aaaaaa" for the new key password. Type "aaaaaa" when re-entering the new key password.
         testOK("aaaaaa\naaaaaa\n", "-keypasswd -v -alias mykey2 -storepass password -keystore x.jks");
         //  10. keytool -selfcert -v -alias mykey -storepass password
@@ -864,7 +1207,7 @@
         testOK("", "-export -v -alias mykey -file cert -storepass password -keystore x.jks");
         //  13. keytool -import -v -file cert -storepass password Check error (Certificate reply and cert are the same)
         testFail("", "-import -v -file cert -storepass password -keystore x.jks");
-        assertTrue(ex.indexOf("Certificate reply and certificate in keystore are identical") != -1, "");
+        assertTrue(ex.indexOf("Certificate reply and certificate in keystore are identical") != -1);
         //  14. keytool -printcert -file cert
         testOK("", "-printcert -file cert -keystore x.jks");
         remove("cert");
@@ -875,26 +1218,26 @@
 
         //   1. keytool -storepasswd -storepass password -new abc Check error (password too short)
         testFail("", "-storepasswd -storepass password -new abc");
-        assertTrue(ex.indexOf("New password must be at least 6 characters") != -1, "");
+        assertTrue(ex.indexOf("New password must be at least 6 characters") != -1);
         // Changed, no NONE needed now
         //   2. keytool -list -storetype PKCS11 Check error (-keystore must be NONE)
         //testFail("", "-list -storetype PKCS11");
-        //assertTrue(err.indexOf("keystore must be NONE") != -1, "");
+        //assertTrue(err.indexOf("keystore must be NONE") != -1);
         //   3. keytool -storepasswd -storetype PKCS11 -keystore NONE Check error (unsupported operation)
         testFail("", "-storepasswd -storetype PKCS11 -keystore NONE");
-        assertTrue(ex.indexOf("UnsupportedOperationException") != -1, "");
+        assertTrue(ex.indexOf("UnsupportedOperationException") != -1);
         //   4. keytool -keypasswd -storetype PKCS11 -keystore NONE Check error (unsupported operation)
         testFail("", "-keypasswd -storetype PKCS11 -keystore NONE");
-        assertTrue(ex.indexOf("UnsupportedOperationException") != -1, "");
+        assertTrue(ex.indexOf("UnsupportedOperationException") != -1);
         //   5. keytool -list -protected -storepass password Check error (password can not be specified with -protected)
         testFail("", "-list -protected -storepass password -keystore x.jks");
-        assertTrue(ex.indexOf("if -protected is specified, then") != -1, "");
+        assertTrue(ex.indexOf("if -protected is specified, then") != -1);
         //   6. keytool -keypasswd -protected -keypass password Check error (password can not be specified with -protected)
         testFail("", "-keypasswd -protected -keypass password -keystore x.jks");
-        assertTrue(ex.indexOf("if -protected is specified, then") != -1, "");
+        assertTrue(ex.indexOf("if -protected is specified, then") != -1);
         //   7. keytool -keypasswd -protected -new password Check error (password can not be specified with -protected)
         testFail("", "-keypasswd -protected -new password -keystore x.jks");
-        assertTrue(ex.indexOf("if -protected is specified, then") != -1, "");
+        assertTrue(ex.indexOf("if -protected is specified, then") != -1);
         remove("x.jks");
     }
 
@@ -911,11 +1254,11 @@
         testOK("", "-printcert -file genkey.cert");
         testOK("", p11Arg + "-storepass test12 -selfcert -alias genkey -dname cn=selfCert");
         testOK("", p11Arg + "-storepass test12 -list -alias genkey -v");
-        assertTrue(out.indexOf("Owner: CN=selfCert") != -1, "");
+        assertTrue(out.indexOf("Owner: CN=selfCert") != -1);
         //(check that cert subject DN is [cn=selfCert])
         testOK("", p11Arg + "-storepass test12 -delete -alias genkey");
         testOK("", p11Arg + "-storepass test12 -list");
-        assertTrue(out.indexOf("Your keystore contains 0 entries") != -1, "");
+        assertTrue(out.indexOf("Your keystore contains 0 entries") != -1);
         //(check for empty database listing)
         //Runtime.getRuntime().exec("/usr/ccs/bin/sccs unedit cert8.db key3.db");
         remove("genkey.cert");
@@ -943,6 +1286,15 @@
             t.sqeTest();
             t.testAll();
             t.i18nTest();
+            t.v3extTest("RSA");
+            t.v3extTest("DSA");
+            boolean testEC = true;
+            try {
+                KeyPairGenerator.getInstance("EC");
+            } catch (NoSuchAlgorithmException nae) {
+                testEC = false;
+            }
+            if (testEC) t.v3extTest("EC");
         }
 
         if (System.getProperty("nss") != null) {
--- a/test/sun/security/tools/keytool/autotest.sh	Mon Feb 23 10:05:41 2009 +0800
+++ b/test/sun/security/tools/keytool/autotest.sh	Mon Feb 23 10:05:55 2009 +0800
@@ -1,5 +1,5 @@
 #
-# Copyright 2006-2008 Sun Microsystems, Inc.  All Rights Reserved.
+# Copyright 2006-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
@@ -25,6 +25,8 @@
 # @summary (almost) all keytool behaviors
 # @author Weijun Wang
 #
+# This test is only executed on several platforms
+#
 # set a few environment variables so that the shell-script can run stand-alone
 # in the source directory
 if [ "${TESTSRC}" = "" ] ; then
@@ -88,7 +90,7 @@
 chmod u+w key3.db
 chmod u+w cert8.db
 
-echo | ${TESTJAVA}${FS}bin${FS}java -Dfile -Dnss \
+echo | ${TESTJAVA}${FS}bin${FS}java -Dnss \
    -Dnss.lib=${NSS}${FS}lib${FS}${PF}${FS}${LIBNAME} \
    KeyToolTest
 status=$?
@@ -99,8 +101,8 @@
 rm -f secmod.db
 
 rm HumanInputStream*.class
-rm KeyToolTest.class
-rm TestException.class 
+rm KeyToolTest*.class
+rm TestException.class
 
 exit $status
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/keytool/standard.sh	Mon Feb 23 10:05:55 2009 +0800
@@ -0,0 +1,64 @@
+#
+# Copyright 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
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# @test
+# @summary (almost) all keytool behaviors
+# @author Weijun Wang
+#
+# This test is always excecuted.
+#
+# set a few environment variables so that the shell-script can run stand-alone
+# in the source directory
+if [ "${TESTSRC}" = "" ] ; then
+  TESTSRC="."
+fi
+if [ "${TESTCLASSES}" = "" ] ; then
+  TESTCLASSES="."
+fi
+if [ "${TESTJAVA}" = "" ] ; then
+  JAVAC_CMD=`which javac`
+  TESTJAVA=`dirname $JAVAC_CMD`/..
+fi
+
+# set platform-dependent variables
+OS=`uname -s`
+case "$OS" in
+  Windows_* )
+    FS="\\"
+    ;;
+  * )
+    FS="/"
+    ;;
+esac
+
+${TESTJAVA}${FS}bin${FS}javac -d . ${TESTSRC}${FS}KeyToolTest.java || exit 10
+
+echo | ${TESTJAVA}${FS}bin${FS}java -Dfile KeyToolTest
+status=$?
+
+rm HumanInputStream*.class
+rm KeyToolTest*.class
+rm TestException.class
+
+exit $status
+