8008296: keytool utility doesn't support '-importpassword' command
Reviewed-by: weijun
--- 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