8008296: keytool utility doesn't support '-importpassword' command
authorvinnie
Fri, 04 Oct 2013 16:05:55 +0100
changeset 20516 fa2edce67c48
parent 20515 2a838386800f
child 20517 1336a85b3d52
8008296: keytool utility doesn't support '-importpassword' command Reviewed-by: weijun
jdk/src/share/classes/sun/security/tools/keytool/Main.java
jdk/src/share/classes/sun/security/tools/keytool/Resources.java
jdk/test/sun/security/tools/keytool/StorePasswords.java
jdk/test/sun/security/tools/keytool/StorePasswordsByShell.sh
--- a/jdk/src/share/classes/sun/security/tools/keytool/Main.java	Fri Oct 04 15:00:42 2013 +0200
+++ b/jdk/src/share/classes/sun/security/tools/keytool/Main.java	Fri Oct 04 16:05:55 2013 +0100
@@ -72,6 +72,8 @@
 import sun.security.util.Password;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
 
 import sun.security.pkcs.PKCS9Attribute;
 import sun.security.tools.KeyStoreUtil;
@@ -190,6 +192,10 @@
             KEYPASS, KEYSTORE, STOREPASS, STORETYPE,
             PROVIDERNAME, PROVIDERCLASS, PROVIDERARG,
             PROVIDERPATH, V),
+        IMPORTPASS("Imports.a.password",
+            ALIAS, KEYPASS, KEYALG, KEYSIZE, KEYSTORE,
+            STOREPASS, STORETYPE, PROVIDERNAME, PROVIDERCLASS,
+            PROVIDERARG, PROVIDERPATH, V, PROTECTED),
         IMPORTKEYSTORE("Imports.one.or.all.entries.from.another.keystore",
             SRCKEYSTORE, DESTKEYSTORE, SRCSTORETYPE,
             DESTSTORETYPE, SRCSTOREPASS, DESTSTOREPASS,
@@ -409,6 +415,8 @@
                 command = GENKEYPAIR;
             } else if (collator.compare(flags, "-import") == 0) {
                 command = IMPORTCERT;
+            } else if (collator.compare(flags, "-importpassword") == 0) {
+                command = IMPORTPASS;
             }
             /*
              * Help
@@ -727,6 +735,7 @@
                         command != GENSECKEY &&
                         command != IDENTITYDB &&
                         command != IMPORTCERT &&
+                        command != IMPORTPASS &&
                         command != IMPORTKEYSTORE &&
                         command != PRINTCRL) {
                         throw new Exception(rb.getString
@@ -808,6 +817,7 @@
                         command == GENKEYPAIR ||
                         command == GENSECKEY ||
                         command == IMPORTCERT ||
+                        command == IMPORTPASS ||
                         command == IMPORTKEYSTORE ||
                         command == KEYCLONE ||
                         command == CHANGEALIAS ||
@@ -958,6 +968,13 @@
             }
             doGenSecretKey(alias, keyAlgName, keysize);
             kssave = true;
+        } else if (command == IMPORTPASS) {
+            if (keyAlgName == null) {
+                keyAlgName = "PBE";
+            }
+            // password is stored as a secret key
+            doGenSecretKey(alias, keyAlgName, keysize);
+            kssave = true;
         } else if (command == IDENTITYDB) {
             if (filename != null) {
                 try (InputStream inStream = new FileInputStream(filename)) {
@@ -1419,6 +1436,43 @@
         }
         return null;    // PKCS11, MSCAPI, or -protected
     }
+
+    /*
+     * Prompt the user for the password credential to be stored.
+     */
+    private char[] promptForCredential() throws Exception {
+        // Handle password supplied via stdin
+        if (System.console() == null) {
+            char[] importPass = Password.readPassword(System.in);
+            passwords.add(importPass);
+            return importPass;
+        }
+
+        int count;
+        for (count = 0; count < 3; count++) {
+            System.err.print(
+                rb.getString("Enter.the.password.to.be.stored."));
+            System.err.flush();
+            char[] entered = Password.readPassword(System.in);
+            passwords.add(entered);
+            System.err.print(rb.getString("Re.enter.password."));
+            char[] passAgain = Password.readPassword(System.in);
+            passwords.add(passAgain);
+            if (!Arrays.equals(entered, passAgain)) {
+                System.err.println(rb.getString("They.don.t.match.Try.again"));
+                continue;
+            }
+            return entered;
+        }
+
+        if (count == 3) {
+            throw new Exception(rb.getString
+                ("Too.many.failures.key.not.added.to.keystore"));
+        }
+
+        return null;
+    }
+
     /**
      * Creates a new secret key.
      */
@@ -1436,24 +1490,63 @@
             throw new Exception(form.format(source));
         }
 
+        // Use the keystore's default PBE algorithm for entry protection
+        boolean useDefaultPBEAlgorithm = true;
         SecretKey secKey = null;
-        KeyGenerator keygen = KeyGenerator.getInstance(keyAlgName);
-        if (keysize != -1) {
+
+        if (keyAlgName.toUpperCase().startsWith("PBE")) {
+            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
+
+            // User is prompted for PBE credential
+            secKey =
+                factory.generateSecret(new PBEKeySpec(promptForCredential()));
+
+            // Check whether a specific PBE algorithm was specified
+            if (!"PBE".equalsIgnoreCase(keyAlgName)) {
+                useDefaultPBEAlgorithm = false;
+            }
+
+            if (verbose) {
+                MessageFormat form = new MessageFormat(rb.getString(
+                    "Generated.keyAlgName.secret.key"));
+                Object[] source =
+                    {useDefaultPBEAlgorithm ? "PBE" : secKey.getAlgorithm()};
+                System.err.println(form.format(source));
+            }
+        } else {
+            KeyGenerator keygen = KeyGenerator.getInstance(keyAlgName);
+            if (keysize == -1) {
+                if ("DES".equalsIgnoreCase(keyAlgName)) {
+                    keysize = 56;
+                } else if ("DESede".equalsIgnoreCase(keyAlgName)) {
+                    keysize = 168;
+                } else {
+                    throw new Exception(rb.getString
+                        ("Please.provide.keysize.for.secret.key.generation"));
+                }
+            }
             keygen.init(keysize);
-        } else if ("DES".equalsIgnoreCase(keyAlgName)) {
-            keygen.init(56);
-        } else if ("DESede".equalsIgnoreCase(keyAlgName)) {
-            keygen.init(168);
-        } else {
-            throw new Exception(rb.getString
-                ("Please.provide.keysize.for.secret.key.generation"));
+            secKey = keygen.generateKey();
+
+            if (verbose) {
+                MessageFormat form = new MessageFormat(rb.getString
+                    ("Generated.keysize.bit.keyAlgName.secret.key"));
+                Object[] source = {new Integer(keysize),
+                                    secKey.getAlgorithm()};
+                System.err.println(form.format(source));
+            }
         }
 
-        secKey = keygen.generateKey();
         if (keyPass == null) {
             keyPass = promptForKeyPass(alias, null, storePass);
         }
-        keyStore.setKeyEntry(alias, secKey, keyPass, null);
+
+        if (useDefaultPBEAlgorithm) {
+            keyStore.setKeyEntry(alias, secKey, keyPass, null);
+        } else {
+            keyStore.setEntry(alias, new KeyStore.SecretKeyEntry(secKey),
+                new KeyStore.PasswordProtection(keyPass, keyAlgName, null));
+        }
     }
 
     /**
--- a/jdk/src/share/classes/sun/security/tools/keytool/Resources.java	Fri Oct 04 15:00:42 2013 +0200
+++ b/jdk/src/share/classes/sun/security/tools/keytool/Resources.java	Fri Oct 04 16:05:55 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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
@@ -65,10 +65,16 @@
         {"Generates.certificate.from.a.certificate.request",
                 "Generates certificate from a certificate request"}, //-gencert
         {"Generates.CRL", "Generates CRL"}, //-gencrl
+        {"Generated.keyAlgName.secret.key",
+                "Generated {0} secret key"}, //-genseckey
+        {"Generated.keysize.bit.keyAlgName.secret.key",
+                "Generated {0}-bit {1} secret key"}, //-genseckey
         {"Imports.entries.from.a.JDK.1.1.x.style.identity.database",
                 "Imports entries from a JDK 1.1.x-style identity database"}, //-identitydb
         {"Imports.a.certificate.or.a.certificate.chain",
                 "Imports a certificate or a certificate chain"}, //-importcert
+        {"Imports.a.password",
+                "Imports a password"}, //-importpass
         {"Imports.one.or.all.entries.from.another.keystore",
                 "Imports one or all entries from another keystore"}, //-importkeystore
         {"Clones.a.key.entry",
@@ -220,6 +226,8 @@
         {"Must.specify.alias", "Must specify alias"},
         {"Keystore.password.must.be.at.least.6.characters",
                 "Keystore password must be at least 6 characters"},
+        {"Enter.the.password.to.be.stored.",
+                "Enter the password to be stored:  "},
         {"Enter.keystore.password.", "Enter keystore password:  "},
         {"Enter.source.keystore.password.", "Enter source keystore password:  "},
         {"Enter.destination.keystore.password.", "Enter destination keystore password:  "},
@@ -328,6 +336,7 @@
         {"New.prompt.", "New {0}: "},
         {"Passwords.must.differ", "Passwords must differ"},
         {"Re.enter.new.prompt.", "Re-enter new {0}: "},
+        {"Re.enter.passpword.", "Re-enter password: "},
         {"Re.enter.new.password.", "Re-enter new password: "},
         {"They.don.t.match.Try.again", "They don't match. Try again"},
         {"Enter.prompt.alias.name.", "Enter {0} alias name:  "},
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/keytool/StorePasswords.java	Fri Oct 04 16:05:55 2013 +0100
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8008296
+ * @summary Store and retrieve user passwords using PKCS#12 keystore
+ */
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import javax.crypto.*;
+import javax.crypto.spec.*;
+
+/*
+ * Store and retrieve passwords protected by a selection of PBE algorithms,
+ * using a PKCS#12 keystore.
+ */
+public class StorePasswords {
+
+    private static final String[] PBE_ALGORITHMS = new String[] {
+        "default PBE algorithm",
+        "PBEWithMD5AndDES",
+        "PBEWithSHA1AndDESede",
+        "PBEWithSHA1AndRC2_40",
+        "PBEWithSHA1AndRC2_128",
+        "PBEWithSHA1AndRC4_40",
+        "PBEWithSHA1AndRC4_128",
+        "PBEWithHmacSHA1AndAES_128",
+        "PBEWithHmacSHA224AndAES_128",
+        "PBEWithHmacSHA256AndAES_128",
+        "PBEWithHmacSHA384AndAES_128",
+        "PBEWithHmacSHA512AndAES_128",
+        "PBEWithHmacSHA1AndAES_256",
+        "PBEWithHmacSHA224AndAES_256",
+        "PBEWithHmacSHA256AndAES_256",
+        "PBEWithHmacSHA384AndAES_256",
+        "PBEWithHmacSHA512AndAES_256"
+    };
+
+    private static final String KEYSTORE = "mykeystore.p12";
+    private static final char[] KEYSTORE_PWD = "changeit".toCharArray();
+    private static final char[] ENTRY_PWD = "protectit".toCharArray();
+    private static final char[] USER_PWD = "hello1".toCharArray();
+
+    public static void main(String[] args) throws Exception {
+
+        new File(KEYSTORE).delete();
+
+        int storeCount = store();
+        int recoverCount = recover();
+
+        if (recoverCount != storeCount) {
+            throw new Exception("Stored " + storeCount + " user passwords, " +
+                "recovered " + recoverCount + " user passwords");
+        }
+        System.out.println("\nStored " + storeCount + " user passwords, " +
+            "recovered " + recoverCount + " user passwords");
+    }
+
+    private static int store() throws Exception {
+        int count = 0;
+        // Load an empty PKCS#12 keystore
+        KeyStore keystore = KeyStore.getInstance("PKCS12");
+        System.out.println("\nLoading PKCS#12 keystore...");
+        keystore.load(null, null);
+
+        // Derive a PBE key from the password
+        PBEKeySpec keySpec = new PBEKeySpec(USER_PWD);
+        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
+        SecretKey key = factory.generateSecret(keySpec);
+        PBEParameterSpec specWithEightByteSalt =
+            new PBEParameterSpec("NaClNaCl".getBytes(), 1024);
+
+        // Store the user password in a keystore entry (for each algorithm)
+        for (String algorithm : PBE_ALGORITHMS) {
+
+            try {
+                System.out.println("Storing user password '" +
+                    new String(USER_PWD) + "' (protected by " + algorithm +
+                    ")");
+
+                if (algorithm.equals("default PBE algorithm")) {
+                     keystore.setKeyEntry(
+                         "this entry is protected by " + algorithm, key,
+                         ENTRY_PWD, null);
+                } else {
+                    keystore.setEntry(
+                        "this entry is protected by " + algorithm,
+                        new KeyStore.SecretKeyEntry(key),
+                        new KeyStore.PasswordProtection(ENTRY_PWD, algorithm,
+                            null));
+                }
+                count++;
+
+            } catch (KeyStoreException e) {
+                Throwable inner = e.getCause();
+                if (inner instanceof UnrecoverableKeyException) {
+                    Throwable inner2 = inner.getCause();
+                    if (inner2 instanceof InvalidAlgorithmParameterException) {
+                        System.out.println("...re-trying due to: " +
+                            inner2.getMessage());
+
+                        // Some PBE algorithms demand an 8-byte salt
+                        keystore.setEntry(
+                            "this entry is protected by " + algorithm,
+                            new KeyStore.SecretKeyEntry(key),
+                            new KeyStore.PasswordProtection(ENTRY_PWD,
+                                algorithm, specWithEightByteSalt));
+                        count++;
+
+                    } else if (inner2  instanceof InvalidKeyException) {
+                        System.out.println("...skipping due to: " +
+                            inner2.getMessage());
+                        // Unsupported crypto keysize
+                        continue;
+                    }
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        // Store the PKCS#12 keystore
+        System.out.println("Storing PKCS#12 keystore to: " + KEYSTORE);
+        keystore.store(new FileOutputStream(KEYSTORE), KEYSTORE_PWD);
+
+        return count;
+    }
+
+    private static int recover() throws Exception {
+        int count = 0;
+        // Load the PKCS#12 keystore
+        KeyStore keystore = KeyStore.getInstance("PKCS12");
+        System.out.println("\nLoading PKCS#12 keystore from: " + KEYSTORE);
+        keystore.load(new FileInputStream(KEYSTORE), KEYSTORE_PWD);
+
+        SecretKey key;
+        SecretKeyFactory factory;
+        PBEKeySpec keySpec;
+
+        // Retrieve each user password from the keystore
+        for (String algorithm : PBE_ALGORITHMS) {
+            key = (SecretKey) keystore.getKey("this entry is protected by " +
+                algorithm, ENTRY_PWD);
+
+            if (key != null) {
+                count++;
+                factory = SecretKeyFactory.getInstance(key.getAlgorithm());
+                keySpec =
+                    (PBEKeySpec) factory.getKeySpec(key, PBEKeySpec.class);
+                char[] pwd = keySpec.getPassword();
+                System.out.println("Recovered user password '" +
+                     new String(pwd) + "' (protected by " + algorithm + ")");
+
+                if (!Arrays.equals(USER_PWD, pwd)) {
+                    throw new Exception("Failed to recover the user password " +
+                        "protected by " + algorithm);
+                }
+            }
+        }
+
+        return count;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/keytool/StorePasswordsByShell.sh	Fri Oct 04 16:05:55 2013 +0100
@@ -0,0 +1,140 @@
+#
+# Copyright (c) 2013, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# @test
+# @bug 8008296
+# @summary confirm that keytool correctly imports user passwords
+#
+# @run shell StorePasswordsByShell.sh
+
+# 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
+   echo "TESTJAVA not set.  Test cannot execute."
+   echo "FAILED!!!"
+   exit 1
+fi
+
+# set platform-dependent variables
+OS=`uname -s`
+case "$OS" in
+  SunOS )
+    PATHSEP=":"
+    FILESEP="/"
+    ;;
+  Linux )
+    PATHSEP=":"
+    FILESEP="/"
+    ;;
+  Darwin ) 
+    PATHSEP=":"
+    FILESEP="/"
+    ;;
+  CYGWIN* )
+    PATHSEP=";"
+    FILESEP="/"
+    ;;
+  Windows* )
+    PATHSEP=";"
+    FILESEP="\\"
+    ;;
+  * )
+    echo "Unrecognized system!"
+    exit 1;
+    ;;
+esac
+
+PBE_ALGORITHMS="\
+ default-PBE-algorithm \
+ PBEWithMD5AndDES \
+ PBEWithSHA1AndDESede \
+ PBEWithSHA1AndRC2_40 \
+ PBEWithSHA1AndRC2_128 
+ PBEWithSHA1AndRC4_40 \
+ PBEWithSHA1AndRC4_128 \
+ PBEWithHmacSHA1AndAES_128 \
+ PBEWithHmacSHA224AndAES_128 \
+ PBEWithHmacSHA256AndAES_128 \
+ PBEWithHmacSHA384AndAES_128 \
+ PBEWithHmacSHA512AndAES_128 \
+ PBEWithHmacSHA1AndAES_256 \
+ PBEWithHmacSHA224AndAES_256 \
+ PBEWithHmacSHA256AndAES_256 \
+ PBEWithHmacSHA384AndAES_256 \
+ PBEWithHmacSHA512AndAES_256"
+
+USER_PWD="hello1\n"
+ALIAS_PREFIX="this entry is protected by "
+COUNTER=0
+
+# cleanup
+rm mykeystore.p12 > /dev/null 2>&1
+
+echo
+for i in $PBE_ALGORITHMS; do
+
+    if [ $i = "default-PBE-algorithm" ]; then
+        KEYALG=""
+    else
+        KEYALG="-keyalg ${i}"
+    fi
+
+    if [ $COUNTER -lt 5 ]; then
+        IMPORTPASSWORD="-importpassword"
+    else
+        IMPORTPASSWORD="-importpass"
+    fi
+
+    echo "Storing user password (protected by ${i})"
+    echo "${USER_PWD}" | \
+        ${TESTJAVA}${FILESEP}bin${FILESEP}keytool ${IMPORTPASSWORD} \
+            -storetype pkcs12 -keystore mykeystore.p12 -storepass changeit \
+            -alias "${ALIAS_PREFIX}${i}" ${KEYALG} > /dev/null 2>&1
+    if [ $? -ne 0 ]; then
+        echo Error
+    else
+        echo OK
+        COUNTER=`expr ${COUNTER} + 1`
+    fi
+done
+echo
+
+COUNTER2=`${TESTJAVA}${FILESEP}bin${FILESEP}keytool -list -storetype pkcs12 \
+  -keystore mykeystore.p12 -storepass changeit | grep -c "${ALIAS_PREFIX}"`
+
+RESULT="stored ${COUNTER} user passwords, detected ${COUNTER2} user passwords"
+if [ $COUNTER -ne $COUNTER2 -o $COUNTER -lt 11 ]; then
+    echo "ERROR: $RESULT"
+    exit 1
+else
+    echo "OK: $RESULT"
+    exit 0
+fi