--- /dev/null 2021-05-06 23:50:39.000000000 +0300 +++ new/test/sun/security/pkcs12/ParamsTest.java 2021-05-06 23:50:39.000000000 +0300 @@ -0,0 +1,442 @@ +/* + * 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 8076190 + * @library /lib/testlibrary /lib + * @modules java.base/sun.security.pkcs + * java.base/sun.security.x509 + * java.base/sun.security.util + * @summary Customizing the generation of a PKCS12 keystore + */ + +import jdk.test.lib.Asserts; +import jdk.test.lib.SecurityTools; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.util.Base64; +import java.util.Objects; + +import static jdk.testlibrary.security.DerUtils.*; +import static sun.security.x509.AlgorithmId.*; +import static sun.security.pkcs.ContentInfo.*; + +public class ParamsTest { + + public static void main(String[] args) throws Throwable { + + // De-BASE64 textual files in ./params to `pwd` + Files.newDirectoryStream(Paths.get(System.getProperty("test.src"), "params")) + .forEach(p -> { + try (InputStream is = Base64.getMimeDecoder().wrap(Files.newInputStream(p)); + OutputStream os = Files.newOutputStream(p.getFileName())){ + byte[] buffer = new byte[2048]; + int read; + while ((read = is.read(buffer, 0, 2048)) >= 0) { + os.write(buffer, 0, read); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + + byte[] data; + + // openssl -> keytool interop check + + // os2. no cert pbe, no mac. + check("os2", "a", null, "changeit", true, true, true); + check("os2", "a", "changeit", "changeit", true, true, true); + // You can even load it with a wrong storepass, controversial + check("os2", "a", "wrongpass", "changeit", true, true, true); + + // os3. no cert pbe, has mac. just like JKS + check("os3", "a", null, "changeit", true, true, true); + check("os3", "a", "changeit", "changeit", true, true, true); + // Cannot load with a wrong storepass, same as JKS + check("os3", "a", "wrongpass", "-", IOException.class, "-", "-"); + + // os4. non default algs + check("os4", "a", "changeit", "changeit", true, true, true); + check("os4", "a", "wrongpass", "-", IOException.class, "-", "-"); + // no storepass no cert + check("os4", "a", null, "changeit", true, false, true); + + // os5. strong non default algs + check("os5", "a", "changeit", "changeit", true, true, true); + check("os5", "a", "wrongpass", "-", IOException.class, "-", "-"); + // no storepass no cert + check("os5", "a", null, "changeit", true, false, true); + + // keytool + + // Current default pkcs12 setting + keytool("-importkeystore -srckeystore ks -srcstorepass changeit " + + "-deststoretype PKCS12 -destkeystore ksnormal -deststorepass changeit"); + data = Files.readAllBytes(Paths.get("ksnormal")); + checkInt(data, "22", 100000); // Mac ic + checkAlg(data, "2000", SHA_oid); // Mac alg + checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg + checkInt(data, "110c010c010011", 50000); // key ic + checkAlg(data, "110c10", ENCRYPTED_DATA_OID); + checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg + checkInt(data, "110c1101111", 50000); // cert ic + + check("ksnormal", "a", "changeit", "changeit", true, true, true); + check("ksnormal", "a", null, "changeit", true, false, true); + check("ksnormal", "a", "wrongpass", "-", IOException.class, "-", "-"); + + // Add a new entry with password-less settings, still has a storepass + keytool("-keystore ksnormal -genkeypair -storepass changeit -alias b -dname CN=b " + + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE " + + "-J-Dkeystore.pkcs12.macAlgorithm=NONE"); + data = Files.readAllBytes(Paths.get("ksnormal")); + checkInt(data, "22", 100000); // Mac ic + checkAlg(data, "2000", SHA_oid); // Mac alg + checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg + checkInt(data, "110c010c010011", 50000); // key ic + checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid); // new key alg + checkInt(data, "110c010c110011", 50000); // new key ic + checkAlg(data, "110c10", ENCRYPTED_DATA_OID); + checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg + checkInt(data, "110c1101111", 50000); // cert ic + check("ksnormal", "b", null, "changeit", true, false, true); + check("ksnormal", "b", "changeit", "changeit", true, true, true); + + // Different keypbe alg, no cert pbe and no mac + keytool("-importkeystore -srckeystore ks -srcstorepass changeit " + + "-deststoretype PKCS12 -destkeystore ksnopass -deststorepass changeit " + + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128 " + + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE " + + "-J-Dkeystore.pkcs12.macAlgorithm=NONE"); + data = Files.readAllBytes(Paths.get("ksnopass")); + shouldNotExist(data, "2"); // no Mac + checkAlg(data, "110c010c01000", pbeWithSHA1AndRC4_128_oid); + checkInt(data, "110c010c010011", 50000); + checkAlg(data, "110c10", DATA_OID); + check("ksnopass", "a", null, "changeit", true, true, true); + check("ksnopass", "a", "changeit", "changeit", true, true, true); + check("ksnopass", "a", "wrongpass", "changeit", true, true, true); + + // Add a new entry with normal settings, still password-less + keytool("-keystore ksnopass -genkeypair -storepass changeit -alias b -dname CN=B"); + data = Files.readAllBytes(Paths.get("ksnopass")); + shouldNotExist(data, "2"); // no Mac + checkAlg(data, "110c010c01000", pbeWithSHA1AndRC4_128_oid); + checkInt(data, "110c010c010011", 50000); + checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid); + checkInt(data, "110c010c110011", 50000); + checkAlg(data, "110c10", DATA_OID); + check("ksnopass", "a", null, "changeit", true, true, true); + check("ksnopass", "b", null, "changeit", true, true, true); + + keytool("-importkeystore -srckeystore ks -srcstorepass changeit " + + "-deststoretype PKCS12 -destkeystore ksnewic -deststorepass changeit " + + "-J-Dkeystore.pkcs12.macIterationCount=5555 " + + "-J-Dkeystore.pkcs12.certPbeIterationCount=6666 " + + "-J-Dkeystore.pkcs12.keyPbeIterationCount=7777"); + data = Files.readAllBytes(Paths.get("ksnewic")); + checkInt(data, "22", 5555); // Mac ic + checkAlg(data, "2000", SHA_oid); // Mac alg + checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg + checkInt(data, "110c010c010011", 7777); // key ic + checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg + checkInt(data, "110c1101111", 6666); // cert ic + + // keypbe alg cannot be NONE + keytool("-keystore ksnewic -genkeypair -storepass changeit -alias b -dname CN=B " + + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=NONE") + .shouldContain("NONE AlgorithmParameters not available") + .shouldHaveExitValue(1); + + // new entry new keypbe alg (and default ic), else unchanged + keytool("-keystore ksnewic -genkeypair -storepass changeit -alias b -dname CN=B " + + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128"); + data = Files.readAllBytes(Paths.get("ksnewic")); + checkInt(data, "22", 5555); // Mac ic + checkAlg(data, "2000", SHA_oid); // Mac alg + checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg + checkInt(data, "110c010c010011", 7777); // key ic + checkAlg(data, "110c010c11000", pbeWithSHA1AndRC4_128_oid); // new key alg + checkInt(data, "110c010c110011", 50000); // new key ic + checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg + checkInt(data, "110c1101111", 6666); // cert ic + + // Check KeyStore loading multiple keystores + KeyStore ks = KeyStore.getInstance("pkcs12"); + try (FileInputStream fis = new FileInputStream("ksnormal"); + FileOutputStream fos = new FileOutputStream("ksnormaldup")) { + ks.load(fis, "changeit".toCharArray()); + ks.store(fos, "changeit".toCharArray()); + } + data = Files.readAllBytes(Paths.get("ksnormaldup")); + checkInt(data, "22", 100000); // Mac ic + checkAlg(data, "2000", SHA_oid); // Mac alg + checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg + checkInt(data, "110c010c010011", 50000); // key ic + checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid); // new key alg + checkInt(data, "110c010c110011", 50000); // new key ic + checkAlg(data, "110c10", ENCRYPTED_DATA_OID); + checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg + checkInt(data, "110c1101111", 50000); // cert ic + + try (FileInputStream fis = new FileInputStream("ksnopass"); + FileOutputStream fos = new FileOutputStream("ksnopassdup")) { + ks.load(fis, "changeit".toCharArray()); + ks.store(fos, "changeit".toCharArray()); + } + data = Files.readAllBytes(Paths.get("ksnopassdup")); + shouldNotExist(data, "2"); // no Mac + checkAlg(data, "110c010c01000", pbeWithSHA1AndRC4_128_oid); + checkInt(data, "110c010c010011", 50000); + checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid); + checkInt(data, "110c010c110011", 50000); + checkAlg(data, "110c10", DATA_OID); + + try (FileInputStream fis = new FileInputStream("ksnewic"); + FileOutputStream fos = new FileOutputStream("ksnewicdup")) { + ks.load(fis, "changeit".toCharArray()); + ks.store(fos, "changeit".toCharArray()); + } + data = Files.readAllBytes(Paths.get("ksnewicdup")); + checkInt(data, "22", 5555); // Mac ic + checkAlg(data, "2000", SHA_oid); // Mac alg + checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg + checkInt(data, "110c010c010011", 7777); // key ic + checkAlg(data, "110c010c11000", pbeWithSHA1AndRC4_128_oid); // new key alg + checkInt(data, "110c010c110011", 50000); // new key ic + checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg + checkInt(data, "110c1101111", 6666); // cert ic + + // Check keytool behavior + + // ksnormal has password + + keytool("-list -keystore ksnormal") + .shouldContain("WARNING WARNING WARNING") + .shouldContain("Certificate chain length: 0"); + + SecurityTools.setResponse("changeit"); + keytool("-list -keystore ksnormal") + .shouldNotContain("WARNING WARNING WARNING") + .shouldContain("Certificate fingerprint"); + + // ksnopass is password-less + + keytool("-list -keystore ksnopass") + .shouldNotContain("WARNING WARNING WARNING") + .shouldContain("Certificate fingerprint"); + + // -certreq prompts for keypass + SecurityTools.setResponse("changeit"); + keytool("-certreq -alias a -keystore ksnopass") + .shouldContain("Enter key password for ") + .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----") + .shouldHaveExitValue(0); + + // -certreq -storepass works fine + keytool("-certreq -alias a -keystore ksnopass -storepass changeit") + .shouldNotContain("Enter key password for ") + .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----") + .shouldHaveExitValue(0); + + // -certreq -keypass also works fine + keytool("-certreq -alias a -keystore ksnopass -keypass changeit") + .shouldNotContain("Enter key password for ") + .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----") + .shouldHaveExitValue(0); + + // -importkeystore prompts for srckeypass + SecurityTools.setResponse("changeit", "changeit"); + keytool("-importkeystore -srckeystore ksnopass " + + "-destkeystore jks3 -deststoretype PKCS12 -deststorepass changeit") + .shouldContain("Enter key password for ") + .shouldContain("Enter key password for ") + .shouldContain("2 entries successfully imported"); + + // ksnopass2 is ksnopass + 2 cert entries + ks = KeyStore.getInstance("pkcs12"); + try (FileInputStream fis = new FileInputStream("ksnopass")) { + ks.load(fis, (char[])null); + } + ks.setCertificateEntry("aa", ks.getCertificate("a")); + ks.setCertificateEntry("bb", ks.getCertificate("b")); + try (FileOutputStream fos = new FileOutputStream("ksnopass2")) { + ks.store(fos, null); + } + + // -importkeystore prompts for srckeypass for private keys + // and no prompt for certs + SecurityTools.setResponse("changeit", "changeit"); + keytool("-importkeystore -srckeystore ksnopass2 " + + "-destkeystore jks5 -deststorepass changeit") + .shouldContain("Enter key password for ") + .shouldContain("Enter key password for ") + .shouldNotContain("Enter key password for ") + .shouldNotContain("Enter key password for ") + .shouldContain("4 entries successfully imported"); + + // ksonlycert has only cert entries + + ks.deleteEntry("a"); + ks.deleteEntry("b"); + try (FileOutputStream fos = new FileOutputStream("ksonlycert")) { + ks.store(fos, null); + } + + // -importkeystore does not prompt at all + keytool("-importkeystore -srckeystore ksonlycert " + + "-destkeystore jks6 -deststorepass changeit") + .shouldNotContain("Enter key password for ") + .shouldNotContain("Enter key password for ") + .shouldContain("2 entries successfully imported"); + + // create a new password-less keystore + keytool("-keystore ksnopass -exportcert -alias a -file a.cert -rfc"); + + // Normally storepass is prompted for + keytool("-keystore kscert1 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt") + .shouldContain("Enter keystore password:"); + keytool("-keystore kscert2 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt " + + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE") + .shouldContain("Enter keystore password:"); + keytool("-keystore kscert3 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt " + + "-J-Dkeystore.pkcs12.macAlgorithm=NONE") + .shouldContain("Enter keystore password:"); + // ... but not if it's password-less + keytool("-keystore kscert4 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt " + + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE " + + "-J-Dkeystore.pkcs12.macAlgorithm=NONE") + .shouldNotContain("Enter keystore password:"); + + // still prompt for keypass for genkeypair and certreq + SecurityTools.setResponse("changeit", "changeit"); + keytool("-keystore ksnopassnew -storetype PKCS12 -genkeypair -alias a -dname CN=A " + + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE " + + "-J-Dkeystore.pkcs12.macAlgorithm=NONE") + .shouldNotContain("Enter keystore password:") + .shouldContain("Enter key password for "); + keytool("-keystore ksnopassnew -certreq -alias a") + .shouldNotContain("Enter keystore password:") + .shouldContain("Enter key password for "); + keytool("-keystore ksnopassnew -list -v -alias a") + .shouldNotContain("Enter keystore password:") + .shouldNotContain("Enter key password for "); + + // params only read on demand + + // keyPbeIterationCount is used by -genkeypair + keytool("-keystore ksgenbadkeyic -storetype PKCS12 -genkeypair -alias a -dname CN=A " + + "-storepass changeit " + + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc") + .shouldContain("keyPbeIterationCount is not a number: abc") + .shouldHaveExitValue(1); + + keytool("-keystore ksnopassnew -exportcert -alias a -file a.cert"); + + // but not used by -importcert + keytool("-keystore ksimpbadkeyic -importcert -alias a -file a.cert " + + "-noprompt -storepass changeit " + + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc") + .shouldHaveExitValue(0); + + // None is used by -list + keytool("-keystore ksnormal -storepass changeit -list " + + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc " + + "-J-Dkeystore.pkcs12.certPbeIterationCount=abc " + + "-J-Dkeystore.pkcs12.macIterationCount=abc") + .shouldHaveExitValue(0); + } + + /** + * Check keystore loading and key/cert reading. + * + * @param keystore the file name of keystore + * @param alias the key/cert to read + * @param storePass store pass to try out, can be null + * @param keypass key pass to try, can not be null + * @param expectedLoad expected result of keystore loading, true if non + * null, false if null, exception class if exception + * @param expectedCert expected result of cert reading + * @param expectedKey expected result of key reading + */ + private static void check( + String keystore, + String alias, + String storePass, + String keypass, + Object expectedLoad, + Object expectedCert, + Object expectedKey) { + KeyStore ks = null; + Object actualLoad, actualCert, actualKey; + String label = keystore + "-" + alias + "-" + storePass + "-" + keypass; + try { + ks = KeyStore.getInstance("pkcs12"); + try (FileInputStream fis = new FileInputStream(keystore)) { + ks.load(fis, storePass == null ? null : storePass.toCharArray()); + } + actualLoad = ks != null; + } catch (Exception e) { + e.printStackTrace(System.out); + actualLoad = e.getClass(); + } + Asserts.assertEQ(expectedLoad, actualLoad, label + "-load"); + + // If not loaded correctly, skip cert/key reading + if (!Objects.equals(actualLoad, true)) { + return; + } + + try { + actualCert = (ks.getCertificate(alias) != null); + } catch (Exception e) { + e.printStackTrace(System.out); + actualCert = e.getClass(); + } + Asserts.assertEQ(expectedCert, actualCert, label + "-cert"); + + try { + actualKey = (ks.getKey(alias, keypass.toCharArray()) != null); + } catch (Exception e) { + e.printStackTrace(System.out); + actualKey = e.getClass(); + } + Asserts.assertEQ(expectedKey, actualKey, label + "-key"); + } + + static OutputAnalyzer keytool(String s) throws Throwable { + return SecurityTools.keytool(s); + } +}