--- old/src/java.base/share/classes/sun/security/tools/keytool/Main.java 2017-12-08 11:18:38.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/tools/keytool/Main.java 2017-12-08 11:18:37.000000000 +0800 @@ -1058,11 +1058,6 @@ System.err.println(form.format(source)); keyPass = storePass; } - if (newPass != null && !Arrays.equals(storePass, newPass)) { - Object[] source = {"-new"}; - System.err.println(form.format(source)); - newPass = storePass; - } if (destKeyPass != null && !Arrays.equals(storePass, destKeyPass)) { Object[] source = {"-destkeypass"}; System.err.println(form.format(source)); @@ -1243,10 +1238,7 @@ doSelfCert(alias, dname, sigAlgName); kssave = true; } else if (command == STOREPASSWD) { - storePassNew = newPass; - if (storePassNew == null) { - storePassNew = getNewPasswd("keystore password", storePass); - } + doChangeStorePasswd(); kssave = true; } else if (command == GENCERT) { if (alias == null) { @@ -2258,8 +2250,9 @@ newPass = destKeyPass; pp = new PasswordProtection(destKeyPass); } else if (objs.snd != null) { - newPass = objs.snd; - pp = new PasswordProtection(objs.snd); + newPass = P12KEYSTORE.equalsIgnoreCase(storetype) ? + storePass : objs.snd; + pp = new PasswordProtection(newPass); } try { @@ -2762,6 +2755,28 @@ } } } + + private void doChangeStorePasswd() throws Exception { + storePassNew = newPass; + if (storePassNew == null) { + storePassNew = getNewPasswd("keystore password", storePass); + } + if (P12KEYSTORE.equalsIgnoreCase(storetype)) { + // When storetype is PKCS12, we need to change all keypass as well + for (String alias : Collections.list(keyStore.aliases())) { + if (!keyStore.isCertificateEntry(alias)) { + // keyPass should be either null or same with storePass, + // but keep it in case one day we want to "normalize" + // a PKCS12 keystore having different passwords. + Pair objs + = recoverEntry(keyStore, alias, storePass, keyPass); + keyStore.setEntry(alias, objs.fst, + new PasswordProtection(storePassNew)); + } + } + } + } + /** * Creates a self-signed certificate, and stores it as a single-element * certificate chain. --- /dev/null 2017-12-08 11:18:40.000000000 +0800 +++ new/test/jdk/sun/security/tools/keytool/JKStoPKCS12.java 2017-12-08 11:18:39.000000000 +0800 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2017, 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 8192988 + * @summary keytool should support -storepasswd for pkcs12 keystores + * @library /test/lib + * @build jdk.test.lib.SecurityTools + * jdk.test.lib.Utils + * jdk.test.lib.Asserts + * jdk.test.lib.JDKToolFinder + * jdk.test.lib.JDKToolLauncher + * jdk.test.lib.Platform + * jdk.test.lib.process.* + * @run main JKStoPKCS12 + */ + +import jdk.test.lib.Asserts; +import jdk.test.lib.SecurityTools; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.util.Collections; + +public class JKStoPKCS12 { + + static String srcStorePass, srcKeyPass; + + public static void main(String[] args) throws Exception { + + // Part 1: JKS keystore with same storepass and keypass + genJKS("pass1111", "pass1111"); + + // Change storepass, keypass also changes + convert("pass2222", null); + // You can keep storepass unchanged + convert("pass1111", null); + // Or change storepass and keypass both, explicitly + convert("pass2222", "pass2222"); + + // Part 2: JKS keystore with different storepass and keypass + Files.delete(Paths.get("jks")); + genJKS("pass1111", "pass2222"); + + // Can use old keypass as new storepass so new storepass and keypass are same + convert("pass2222", null); + // Or specify both storepass and keypass to brand new ones + convert("pass3333", "pass3333"); + // Or change storepass, keypass also changes. Remember to provide srckeypass + convert("pass1111", null); + } + + // Generate JKS keystore with srcStorePass and srcKeyPass + static void genJKS(String storePass, String keyPass) + throws Exception { + srcStorePass = storePass; + srcKeyPass = keyPass; + kt("-genkeypair -keystore jks -storetype jks " + + "-alias me -dname CN=Me -keyalg rsa " + + "-storepass " + srcStorePass + " -keypass " + srcKeyPass) + .shouldHaveExitValue(0); + } + + // Convert JKS to PKCS12 with destStorePass and destKeyPass (optional) + static void convert(String destStorePass, String destKeyPass) + throws Exception { + + String cmd = "-importkeystore -noprompt" + + " -srcstoretype jks -srckeystore jks" + + " -destkeystore p12 -deststoretype pkcs12" + + " -srcstorepass " + srcStorePass + + " -deststorepass " + destStorePass; + + // Must import by alias (-srckeypass not available when importing all) + if (!srcStorePass.equals(srcKeyPass)) { + cmd += " -srcalias me"; + cmd += " -srckeypass " + srcKeyPass; + } + if (destKeyPass != null) { + cmd += " -destkeypass " + destKeyPass; + } + + kt(cmd).shouldHaveExitValue(0); + + // Confirms the storepass and keypass are all correct + KeyStore.getInstance(new File("p12"), destStorePass.toCharArray()) + .getKey("me", destStorePass.toCharArray()); + + Files.delete(Paths.get("p12")); + } + + static OutputAnalyzer kt(String arg) throws Exception { + return SecurityTools.keytool(arg); + } +} --- /dev/null 2017-12-08 11:18:42.000000000 +0800 +++ new/test/jdk/sun/security/tools/keytool/PKCS12Passwd.java 2017-12-08 11:18:41.000000000 +0800 @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2017, 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 8192988 + * @summary keytool should support -storepasswd for pkcs12 keystores + * @library /test/lib + * @build jdk.test.lib.SecurityTools + * jdk.test.lib.Utils + * jdk.test.lib.Asserts + * jdk.test.lib.JDKToolFinder + * jdk.test.lib.JDKToolLauncher + * jdk.test.lib.Platform + * jdk.test.lib.process.* + * @run main PKCS12Passwd + */ + +import jdk.test.lib.Asserts; +import jdk.test.lib.SecurityTools; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.File; +import java.security.KeyStore; +import java.util.Collections; + +public class PKCS12Passwd { + + public static void main(String[] args) throws Exception { + + // A PrivateKeyEntry + kt("-genkeypair -alias a -dname CN=A") + .shouldHaveExitValue(0); + + // A TrustedCertificateEntry (genkeypair, export, delete, import) + kt("-genkeypair -alias b -dname CN=B") + .shouldHaveExitValue(0); + kt("-exportcert -alias b -file b.cert") + .shouldHaveExitValue(0); + kt("-delete -alias b") + .shouldHaveExitValue(0); + kt("-list -alias b") + .shouldHaveExitValue(1); + kt("-importcert -alias b -file b.cert -noprompt") + .shouldHaveExitValue(0); + + // A SecretKeyEntry + kt("-genseckey -keyalg AES -keysize 256 -alias c") + .shouldHaveExitValue(0); + + // Change password + + // 1. Using -importkeystore + ktFull("-importkeystore -srckeystore ks -destkeystore ks2 " + + "-srcstoretype pkcs12 -deststoretype pkcs12 " + + "-srcstorepass changeit -deststorepass newpass") + .shouldHaveExitValue(0); + + check("ks2", "newpass", "newpass"); + + // 2. Using -storepasswd + kt("-storepasswd -new newpass") + .shouldHaveExitValue(0) + .shouldNotContain("Ignoring user-specified"); + + check("ks", "newpass", "newpass"); + + // Other facts. Not necessarily the correct thing. + + // A PKCS12 keystore can be loaded as a JKS, and it follows JKS rules + // which means the storepass and keypass can be changed separately! + + ktFull("-genkeypair -alias a -dname CN=A -storetype pkcs12 " + + "-storepass changeit -keypass changeit -keystore p12") + .shouldHaveExitValue(0); + + // Only storepass is changed + ktFull("-storepasswd -storepass changeit -new newpass " + + "-keystore p12 -storetype jks") + .shouldHaveExitValue(0); + + check("p12", "newpass", "changeit"); + + // Only keypass is changed + ktFull("-keypasswd -storepass newpass -keypass changeit -new newpass " + + "-keystore p12 -storetype jks -alias a") + .shouldHaveExitValue(0); + + check("p12", "newpass", "newpass"); + + // Conversely, a JKS keystore can be laoded as a PKCS12, and it follows + // PKCS12 rules that both passwords are changed at the same time and + // some commands are rejected. + + ktFull("-genkeypair -alias a -dname CN=A -storetype jks " + + "-storepass changeit -keypass changeit -keystore jks") + .shouldHaveExitValue(0); + + // Both storepass and keypass changed. + ktFull("-storepasswd -storepass changeit -new newpass " + + "-keystore jks -storetype pkcs12") + .shouldHaveExitValue(0); + + check("jks", "newpass", "newpass"); + + // -keypasswd is not available for pkcs12 + ktFull("-keypasswd -storepass newpass -keypass newpass -new newerpass " + + "-keystore jks -storetype pkcs12 -alias a") + .shouldHaveExitValue(1); + + // but available for JKS + ktFull("-keypasswd -storepass newpass -keypass newpass -new newerpass " + + "-keystore jks -alias a") + .shouldHaveExitValue(0); + + check("jks", "newpass", "newerpass"); + } + + // Makes sure we can load entries in a keystore + static void check(String file, String storePass, String keyPass) + throws Exception { + + KeyStore ks = KeyStore.getInstance( + new File(file), storePass.toCharArray()); + + for (String a : Collections.list(ks.aliases())) { + if (ks.isCertificateEntry(a)) { + ks.getCertificate(a); + } else { + ks.getEntry(a, + new KeyStore.PasswordProtection(keyPass.toCharArray())); + } + } + } + + static OutputAnalyzer kt(String arg) throws Exception { + return ktFull("-keystore ks -storepass changeit " + arg); + } + + static OutputAnalyzer ktFull(String arg) throws Exception { + return SecurityTools.keytool("-debug " + arg); + } +} --- old/test/jdk/sun/security/tools/keytool/p12importks.sh 2017-12-08 11:18:43.000000000 +0800 +++ /dev/null 2017-12-08 11:18:43.000000000 +0800 @@ -1,118 +0,0 @@ -# -# 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 8010125 -# @summary keytool -importkeystore could create a pkcs12 keystore with -# different storepass and keypass -# - -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 - -LANG=C -KT="$TESTJAVA${FS}bin${FS}keytool ${TESTTOOLVMOPTS}" - -# Part 1: JKS keystore with same storepass and keypass - -rm jks 2> /dev/null -$KT -genkeypair -keystore jks -storetype jks -alias me -dname CN=Me \ - -keyalg rsa -storepass pass1111 -keypass pass1111 || exit 11 - -# Cannot only change storepass -rm p12 2> /dev/null -$KT -importkeystore -noprompt \ - -srcstoretype jks -srckeystore jks -destkeystore p12 -deststoretype pkcs12 \ - -srcstorepass pass1111 \ - -deststorepass pass2222 \ - && exit 12 - -# You can keep storepass unchanged -rm p12 2> /dev/null -$KT -importkeystore -noprompt \ - -srcstoretype jks -srckeystore jks -destkeystore p12 -deststoretype pkcs12 \ - -srcstorepass pass1111 \ - -deststorepass pass1111 \ - || exit 13 -$KT -certreq -storetype pkcs12 -keystore p12 -alias me \ - -storepass pass1111 -keypass pass1111 || exit 14 - -# Or change storepass and keypass both -rm p12 2> /dev/null -$KT -importkeystore -noprompt \ - -srcstoretype jks -srckeystore jks -destkeystore p12 -deststoretype pkcs12 \ - -srcstorepass pass1111 \ - -deststorepass pass2222 -destkeypass pass2222 \ - || exit 15 -$KT -certreq -storetype pkcs12 -keystore p12 -alias me \ - -storepass pass2222 -keypass pass2222 || exit 16 - -# Part 2: JKS keystore with different storepass and keypass -# Must import by alias (-srckeypass is not available when importing all) - -rm jks 2> /dev/null -$KT -genkeypair -keystore jks -storetype jks -alias me -dname CN=Me \ - -keyalg rsa -storepass pass1111 -keypass pass2222 || exit 21 - -# Can use old keypass as new storepass so new storepass and keypass are same -rm p12 2> /dev/null -$KT -importkeystore -noprompt -srcalias me \ - -srcstoretype jks -srckeystore jks -destkeystore p12 -deststoretype pkcs12 \ - -srcstorepass pass1111 -srckeypass pass2222 \ - -deststorepass pass2222 \ - || exit 22 -$KT -certreq -storetype pkcs12 -keystore p12 -alias me \ - -storepass pass2222 -keypass pass2222 || exit 23 - -# Or specify both storepass and keypass to brand new ones -rm p12 2> /dev/null -$KT -importkeystore -noprompt -srcalias me \ - -srcstoretype jks -srckeystore jks -destkeystore p12 -deststoretype pkcs12 \ - -srcstorepass pass1111 -srckeypass pass2222 \ - -deststorepass pass3333 -destkeypass pass3333 \ - || exit 24 -$KT -certreq -storetype pkcs12 -keystore p12 -alias me \ - -storepass pass3333 -keypass pass3333 || exit 25 - -# Anyway you cannot make new storepass and keypass different -rm p12 2> /dev/null -$KT -importkeystore -noprompt -srcalias me \ - -srcstoretype jks -srckeystore jks -destkeystore p12 -deststoretype pkcs12 \ - -srcstorepass pass1111 -srckeypass pass2222 \ - -deststorepass pass1111 \ - && exit 26 - -exit 0