8202299: Java Keystore fails to load PKCS12/PFX certificates created in WindowsServer2016
authorweijun
Tue, 26 Jun 2018 10:43:50 +0800
changeset 50778 bba1deda9216
parent 50777 11e7eb8cb583
child 50779 7284ce754713
8202299: Java Keystore fails to load PKCS12/PFX certificates created in WindowsServer2016 Reviewed-by: mullan, xuelei
src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
test/jdk/sun/security/pkcs12/EmptyPassword.java
--- a/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java	Tue Jun 26 10:43:43 2018 +0800
+++ b/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java	Tue Jun 26 10:43:50 2018 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2018, 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
@@ -282,6 +282,28 @@
     }
 
     /**
+     * Retries an action with password "\0" if "" fails.
+     * @param <T> the return type
+     */
+    @FunctionalInterface
+    private interface RetryWithZero<T> {
+
+        T tryOnce(char[] password) throws Exception;
+
+        static <S> S run(RetryWithZero<S> f, char[] password) throws Exception {
+            try {
+                return f.tryOnce(password);
+            } catch (Exception e) {
+                if (password.length == 0) {
+                    // Retry using an empty password with a NUL terminator.
+                    return f.tryOnce(new char[1]);
+                }
+                throw e;
+            }
+        }
+    }
+
+    /**
      * Private keys and certificates are stored in a map.
      * Map entries are keyed by alias names.
      */
@@ -370,26 +392,14 @@
                 }
             }
 
-            byte[] keyInfo;
-            while (true) {
-                try {
-                    // Use JCE
-                    SecretKey skey = getPBEKey(password);
-                    Cipher cipher = Cipher.getInstance(
+            byte[] keyInfo = RetryWithZero.run(pass -> {
+                // Use JCE
+                SecretKey skey = getPBEKey(pass);
+                Cipher cipher = Cipher.getInstance(
                         mapPBEParamsToAlgorithm(algOid, algParams));
-                    cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
-                    keyInfo = cipher.doFinal(encryptedKey);
-                    break;
-                } catch (Exception e) {
-                    if (password.length == 0) {
-                        // Retry using an empty password
-                        // without a NULL terminator.
-                        password = new char[1];
-                        continue;
-                    }
-                    throw e;
-                }
-            }
+                cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
+                return cipher.doFinal(encryptedKey);
+            }, password);
 
             /*
              * Parse the key algorithm and then use a JCA key factory
@@ -2079,25 +2089,19 @@
                         " iterations: " + ic + ")");
                 }
 
-                while (true) {
-                    try {
+                byte[] rawData = safeContentsData;
+                try {
+                    safeContentsData = RetryWithZero.run(pass -> {
                         // Use JCE
-                        SecretKey skey = getPBEKey(password);
+                        SecretKey skey = getPBEKey(pass);
                         Cipher cipher = Cipher.getInstance(algOid.toString());
                         cipher.init(Cipher.DECRYPT_MODE, skey, algParams);
-                        safeContentsData = cipher.doFinal(safeContentsData);
-                        break;
-                    } catch (Exception e) {
-                        if (password.length == 0) {
-                            // Retry using an empty password
-                            // without a NULL terminator.
-                            password = new char[1];
-                            continue;
-                        }
-                        throw new IOException("keystore password was incorrect",
+                        return cipher.doFinal(rawData);
+                    }, password);
+                } catch (Exception e) {
+                    throw new IOException("keystore password was incorrect",
                             new UnrecoverableKeyException(
-                                "failed to decrypt safe contents entry: " + e));
-                    }
+                                    "failed to decrypt safe contents entry: " + e));
                 }
             } else {
                 throw new IOException("public key protected PKCS12" +
@@ -2128,20 +2132,24 @@
                 Mac m = Mac.getInstance("HmacPBE" + algName);
                 PBEParameterSpec params =
                         new PBEParameterSpec(macData.getSalt(), ic);
-                SecretKey key = getPBEKey(password);
-                m.init(key, params);
-                m.update(authSafeData);
-                byte[] macResult = m.doFinal();
+
+                RetryWithZero.run(pass -> {
+                    SecretKey key = getPBEKey(pass);
+                    m.init(key, params);
+                    m.update(authSafeData);
+                    byte[] macResult = m.doFinal();
 
-                if (debug != null) {
-                    debug.println("Checking keystore integrity " +
-                        "(" + m.getAlgorithm() + " iterations: " + ic + ")");
-                }
+                    if (debug != null) {
+                        debug.println("Checking keystore integrity " +
+                                "(" + m.getAlgorithm() + " iterations: " + ic + ")");
+                    }
 
-                if (!MessageDigest.isEqual(macData.getDigest(), macResult)) {
-                   throw new UnrecoverableKeyException("Failed PKCS12" +
-                                        " integrity checking");
-                }
+                    if (!MessageDigest.isEqual(macData.getDigest(), macResult)) {
+                        throw new UnrecoverableKeyException("Failed PKCS12" +
+                                " integrity checking");
+                    }
+                    return (Void)null;
+                }, password);
             } catch (Exception e) {
                 throw new IOException("Integrity check failed: " + e, e);
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/pkcs12/EmptyPassword.java	Tue Jun 26 10:43:50 2018 +0800
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018, 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 8202299
+ * @modules java.base/sun.security.tools.keytool
+ *          java.base/sun.security.x509
+ * @library /test/lib
+ * @summary Java Keystore fails to load PKCS12/PFX certificates created in WindowsServer2016
+ */
+
+import jdk.test.lib.Asserts;
+import sun.security.tools.keytool.CertAndKeyGen;
+import sun.security.x509.X500Name;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+
+public class EmptyPassword {
+
+    public static void main(String[] args) throws Exception {
+
+        // KeyStore is protected with password "\0".
+        CertAndKeyGen gen = new CertAndKeyGen("RSA", "SHA256withRSA");
+        gen.generate(2048);
+        KeyStore ks = KeyStore.getInstance("PKCS12");
+        ks.load(null, null);
+        ks.setKeyEntry("a", gen.getPrivateKey(), new char[1],
+                new Certificate[] {
+                        gen.getSelfCertificate(new X500Name("CN=Me"), 100)
+                });
+        try (FileOutputStream fos = new FileOutputStream("p12")) {
+            ks.store(fos, new char[1]);
+        }
+
+        // It can be loaded with password "".
+        ks = KeyStore.getInstance(new File("p12"), new char[0]);
+        Asserts.assertTrue(ks.getKey("a", new char[0]) != null);
+        Asserts.assertTrue(ks.getCertificate("a") != null);
+    }
+}