# HG changeset patch # User abakhtin # Date 1622132224 -10800 # Node ID 219c9107171bfd3fc7521212c2ec6744a70e4815 # Parent c6da1ce5c68015f7a3c111539df04fa8f667ea87 8202299: Java Keystore fails to load PKCS12/PFX certificates created in WindowsServer2016 Reviewed-by: phh, andrew diff -r c6da1ce5c680 -r 219c9107171b src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java --- a/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java Tue May 18 22:34:27 2021 +0000 +++ b/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java Thu May 27 19:17:04 2021 +0300 @@ -272,6 +272,28 @@ } /** + * Retries an action with password "\0" if "" fails. + * @param the return type + */ + @FunctionalInterface + private interface RetryWithZero { + + T tryOnce(char[] password) throws Exception; + + static S run(RetryWithZero 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. */ @@ -360,26 +382,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 @@ -2050,25 +2060,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" + @@ -2099,20 +2103,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); } diff -r c6da1ce5c680 -r 219c9107171b test/sun/security/pkcs12/EmptyPassword.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/sun/security/pkcs12/EmptyPassword.java Thu May 27 19:17:04 2021 +0300 @@ -0,0 +1,66 @@ +/* + * 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 /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.FileInputStream; +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 "". + try (FileInputStream inStream = new FileInputStream("p12")) { + ks.load(inStream, new char[0]); + } + Asserts.assertTrue(ks.getKey("a", new char[0]) != null); + Asserts.assertTrue(ks.getCertificate("a") != null); + } +}