1 /*
2  * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @bug 8076190 8242151 8153005 8266182
27  * @summary This is java keytool <-> openssl interop test. This test generates
28  *          some openssl keystores on the fly, java operates on it and
29  *          vice versa.
30  *
31  *          Note: This test executes some openssl command, so need to set
32  *          openssl path using system property "test.openssl.path" or it should
33  *          be available in /usr/bin or /usr/local/bin
34  *          Required OpenSSL version : OpenSSL 1.1.*
35  *
36  * @modules java.base/sun.security.pkcs
37  *          java.base/sun.security.util
38  * @library /test/lib
39  * @library /sun/security/pkcs11/
40  * @run main/othervm/timeout=600 KeytoolOpensslInteropTest
41  */
42 
43 import jdk.test.lib.Asserts;
44 import jdk.test.lib.SecurityTools;
45 import jdk.test.lib.process.ProcessTools;
46 import jdk.test.lib.process.OutputAnalyzer;
47 import jdk.test.lib.security.OpensslArtifactFetcher;
48 
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.OutputStream;
55 import java.io.UncheckedIOException;
56 import java.nio.file.DirectoryStream;
57 import java.nio.file.Files;
58 import java.nio.file.Path;
59 import java.security.KeyStore;
60 import java.util.Base64;
61 import java.util.Objects;
62 
63 import static jdk.test.lib.security.DerUtils.*;
64 import static sun.security.util.KnownOIDs.*;
65 import static sun.security.pkcs.ContentInfo.*;
66 
67 public class KeytoolOpensslInteropTest {
68 
main(String[] args)69     public static void main(String[] args) throws Throwable {
70         String opensslPath = OpensslArtifactFetcher.getOpenssl1dot1dotStar();
71         if (opensslPath != null) {
72             // if preferred version of openssl is available perform all
73             // keytool <-> openssl interop tests
74             generateInitialKeystores(opensslPath);
75             testWithJavaCommands();
76             testWithOpensslCommands(opensslPath);
77         } else {
78             // since preferred version of openssl is not available skip all
79             // openssl command dependent tests with a warning
80             System.out.println("\n\u001B[31mWarning: Can't find openssl "
81                     + "(version 1.1.*) binary on this machine, please install"
82                     + " and set openssl path with property "
83                     + "'test.openssl.path'. Now running only half portion of "
84                     + "the test, skipping all tests which depends on openssl "
85                     + "commands.\u001B[0m\n");
86             // De-BASE64 textual files in ./params to `pwd`
87             try (DirectoryStream<Path> stream = Files.newDirectoryStream(
88                     Path.of(System.getProperty("test.src"), "params"),
89                     p -> !p.getFileName().toString().equals("README"))) {
90                 stream.forEach(p -> {
91                     try (InputStream is = Files.newInputStream(p);
92                         OutputStream os = Files.newOutputStream(
93                                 p.getFileName())) {
94                         Base64.getMimeDecoder().wrap(is).transferTo(os);
95                     } catch (IOException e) {
96                         throw new UncheckedIOException(e);
97                     }
98                 });
99             }
100             testWithJavaCommands();
101         }
102     }
103 
generateInitialKeystores(String opensslPath)104     private static void generateInitialKeystores(String opensslPath)
105             throws Throwable {
106         keytool("-keystore ks -keyalg ec -genkeypair -storepass"
107                 + " changeit -alias a -dname CN=A").shouldHaveExitValue(0);
108 
109         ProcessTools.executeCommand(opensslPath, "pkcs12", "-in", "ks",
110                 "-nodes", "-out", "kandc", "-passin", "pass:changeit")
111                 .shouldHaveExitValue(0);
112 
113         ProcessTools.executeCommand(opensslPath, "pkcs12", "-export", "-in",
114                 "kandc", "-out", "os2", "-name", "a", "-passout",
115                 "pass:changeit", "-certpbe", "NONE", "-nomac")
116                 .shouldHaveExitValue(0);
117 
118         ProcessTools.executeCommand(opensslPath, "pkcs12", "-export", "-in",
119                 "kandc", "-out", "os3", "-name", "a", "-passout",
120                 "pass:changeit", "-certpbe", "NONE")
121                 .shouldHaveExitValue(0);
122 
123         ProcessTools.executeCommand(opensslPath, "pkcs12", "-export", "-in",
124                 "kandc", "-out", "os4", "-name", "a", "-passout",
125                 "pass:changeit", "-certpbe", "PBE-SHA1-RC4-128", "-keypbe",
126                 "PBE-SHA1-RC4-128", "-macalg", "SHA224")
127                 .shouldHaveExitValue(0);
128 
129         ProcessTools.executeCommand(opensslPath, "pkcs12", "-export", "-in",
130                 "kandc", "-out", "os5", "-name", "a", "-passout",
131                 "pass:changeit", "-certpbe", "AES-256-CBC", "-keypbe",
132                 "AES-256-CBC", "-macalg", "SHA512")
133                 .shouldHaveExitValue(0);
134     }
135 
testWithJavaCommands()136     private static void testWithJavaCommands() throws Throwable {
137         byte[] data;
138 
139         // openssl -> keytool interop check
140         // os2. no cert pbe, no mac.
141         check("os2", "a", null, "changeit", true, true, true);
142         check("os2", "a", "changeit", "changeit", true, true, true);
143         // You can even load it with a wrong storepass, controversial
144         check("os2", "a", "wrongpass", "changeit", true, true, true);
145 
146         // os3. no cert pbe, has mac. just like JKS
147         check("os3", "a", null, "changeit", true, true, true);
148         check("os3", "a", "changeit", "changeit", true, true, true);
149         // Cannot load with a wrong storepass, same as JKS
150         check("os3", "a", "wrongpass", "-", IOException.class, "-", "-");
151 
152         // os4. non default algs
153         check("os4", "a", "changeit", "changeit", true, true, true);
154         check("os4", "a", "wrongpass", "-", IOException.class, "-", "-");
155         // no storepass no cert
156         check("os4", "a", null, "changeit", true, false, true);
157 
158         // os5. strong non default algs
159         check("os5", "a", "changeit", "changeit", true, true, true);
160         check("os5", "a", "wrongpass", "-", IOException.class, "-", "-");
161         // no storepass no cert
162         check("os5", "a", null, "changeit", true, false, true);
163 
164         // keytool
165 
166         // Current default pkcs12 setting
167         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
168                 + "-destkeystore ksnormal -deststorepass changeit");
169 
170         data = Files.readAllBytes(Path.of("ksnormal"));
171         checkInt(data, "22", 10000); // Mac ic
172         checkAlg(data, "2000", SHA_256); // Mac alg
173         checkAlg(data, "110c010c01000", PBES2); // key alg
174         checkInt(data, "110c010c01001011", 10000); // key ic
175         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
176         checkAlg(data, "110c110110", PBES2); // cert alg
177         check("ksnormal", "a", "changeit", "changeit", true, true, true);
178         check("ksnormal", "a", null, "changeit", true, false, true);
179         check("ksnormal", "a", "wrongpass", "-", IOException.class, "-", "-");
180 
181         // Import it into a new keystore with legacy algorithms
182         keytool("-importkeystore -srckeystore ksnormal -srcstorepass changeit "
183                 + "-destkeystore kslegacyimp -deststorepass changeit "
184                 + "-J-Dkeystore.pkcs12.legacy");
185         data = Files.readAllBytes(Path.of("kslegacyimp"));
186         checkInt(data, "22", 100000); // Mac ic
187         checkAlg(data, "2000", SHA_1); // Mac alg
188         checkAlg(data, "110c010c01000", PBEWithSHA1AndDESede); // key alg
189         checkInt(data, "110c010c010011", 50000); // key ic
190         checkAlg(data, "110c110110", PBEWithSHA1AndRC2_40); // cert alg
191         checkInt(data, "110c1101111", 50000); // cert ic
192 
193         // Add a new entry with password-less settings, still has a storepass
194         keytool("-keystore ksnormal -genkeypair -keyalg DSA "
195                 + "-storepass changeit -alias b -dname CN=b "
196                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
197                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE");
198         data = Files.readAllBytes(Path.of("ksnormal"));
199         checkInt(data, "22", 10000); // Mac ic
200         checkAlg(data, "2000", SHA_256); // Mac alg
201         checkAlg(data, "110c010c01000", PBES2); // key alg
202         checkInt(data, "110c010c01001011", 10000); // key ic
203         checkAlg(data, "110c010c11000", PBES2); // new key alg
204         checkInt(data, "110c010c11001011", 10000); // new key ic
205         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
206         checkAlg(data, "110c110110", PBES2); // cert alg
207         check("ksnormal", "b", null, "changeit", true, false, true);
208         check("ksnormal", "b", "changeit", "changeit", true, true, true);
209 
210         // Different keypbe alg, no cert pbe and no mac
211         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
212                 + "-destkeystore ksnopass -deststorepass changeit "
213                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128 "
214                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
215                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE");
216         data = Files.readAllBytes(Path.of("ksnopass"));
217         shouldNotExist(data, "2"); // no Mac
218         checkAlg(data, "110c010c01000", PBEWithSHA1AndRC4_128);
219         checkInt(data, "110c010c010011", 10000);
220         checkAlg(data, "110c10", DATA_OID);
221         check("ksnopass", "a", null, "changeit", true, true, true);
222         check("ksnopass", "a", "changeit", "changeit", true, true, true);
223         check("ksnopass", "a", "wrongpass", "changeit", true, true, true);
224 
225         // Add a new entry with normal settings, still password-less
226         keytool("-keystore ksnopass -genkeypair -keyalg DSA "
227                 + "-storepass changeit -alias b -dname CN=B");
228         data = Files.readAllBytes(Path.of("ksnopass"));
229         shouldNotExist(data, "2"); // no Mac
230         checkAlg(data, "110c010c01000", PBEWithSHA1AndRC4_128);
231         checkInt(data, "110c010c010011", 10000);
232         checkAlg(data, "110c010c11000", PBES2);
233         checkInt(data, "110c010c11001011", 10000);
234         checkAlg(data, "110c10", DATA_OID);
235         check("ksnopass", "a", null, "changeit", true, true, true);
236         check("ksnopass", "b", null, "changeit", true, true, true);
237 
238         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
239                 + "-destkeystore ksnewic -deststorepass changeit "
240                 + "-J-Dkeystore.pkcs12.macIterationCount=5555 "
241                 + "-J-Dkeystore.pkcs12.certPbeIterationCount=6666 "
242                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=7777");
243         data = Files.readAllBytes(Path.of("ksnewic"));
244         checkInt(data, "22", 5555); // Mac ic
245         checkAlg(data, "2000", SHA_256); // Mac alg
246         checkAlg(data, "110c010c01000", PBES2); // key alg
247         checkInt(data, "110c010c01001011", 7777); // key ic
248         checkAlg(data, "110c110110", PBES2); // cert alg
249         checkInt(data, "110c110111011", 6666); // cert ic
250 
251         // keypbe alg cannot be NONE
252         keytool("-keystore ksnewic -genkeypair -keyalg DSA "
253                 + "-storepass changeit -alias b -dname CN=B "
254                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=NONE")
255                 .shouldContain("NONE AlgorithmParameters not available")
256                 .shouldHaveExitValue(1);
257 
258         // new entry new keypbe alg (and default ic), else unchanged
259         keytool("-keystore ksnewic -genkeypair -keyalg DSA "
260                 + "-storepass changeit -alias b -dname CN=B "
261                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128");
262         data = Files.readAllBytes(Path.of("ksnewic"));
263         checkInt(data, "22", 5555); // Mac ic
264         checkAlg(data, "2000", SHA_256); // Mac alg
265         checkAlg(data, "110c010c01000", PBES2); // key alg
266         checkInt(data, "110c010c01001011", 7777); // key ic
267         checkAlg(data, "110c010c11000", PBEWithSHA1AndRC4_128); // new key alg
268         checkInt(data, "110c010c110011", 10000); // new key ic
269         checkAlg(data, "110c110110", PBES2); // cert alg
270         checkInt(data, "110c110111011", 6666); // cert ic
271 
272         // Check KeyStore loading multiple keystores
273         KeyStore ks = KeyStore.getInstance("pkcs12");
274         try (FileInputStream fis = new FileInputStream("ksnormal");
275                 FileOutputStream fos = new FileOutputStream("ksnormaldup")) {
276             ks.load(fis, "changeit".toCharArray());
277             ks.store(fos, "changeit".toCharArray());
278         }
279         data = Files.readAllBytes(Path.of("ksnormaldup"));
280         checkInt(data, "22", 10000); // Mac ic
281         checkAlg(data, "2000", SHA_256); // Mac alg
282         checkAlg(data, "110c010c01000", PBES2); // key alg
283         checkInt(data, "110c010c01001011", 10000); // key ic
284         checkAlg(data, "110c010c11000", PBES2); // new key alg
285         checkInt(data, "110c010c11001011", 10000); // new key ic
286         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
287         checkAlg(data, "110c110110", PBES2); // cert alg
288         checkInt(data, "110c110111011", 10000); // cert ic
289 
290         try (FileInputStream fis = new FileInputStream("ksnopass");
291              FileOutputStream fos = new FileOutputStream("ksnopassdup")) {
292             ks.load(fis, "changeit".toCharArray());
293             ks.store(fos, "changeit".toCharArray());
294         }
295         data = Files.readAllBytes(Path.of("ksnopassdup"));
296         shouldNotExist(data, "2"); // no Mac
297         checkAlg(data, "110c010c01000", PBEWithSHA1AndRC4_128);
298         checkInt(data, "110c010c010011", 10000);
299         checkAlg(data, "110c010c11000", PBES2);
300         checkInt(data, "110c010c11001011", 10000);
301         checkAlg(data, "110c10", DATA_OID);
302 
303         try (FileInputStream fis = new FileInputStream("ksnewic");
304              FileOutputStream fos = new FileOutputStream("ksnewicdup")) {
305             ks.load(fis, "changeit".toCharArray());
306             ks.store(fos, "changeit".toCharArray());
307         }
308         data = Files.readAllBytes(Path.of("ksnewicdup"));
309         checkInt(data, "22", 5555); // Mac ic
310         checkAlg(data, "2000", SHA_256); // Mac alg
311         checkAlg(data, "110c010c01000", PBES2); // key alg
312         checkInt(data, "110c010c01001011", 7777); // key ic
313         checkAlg(data, "110c010c11000", PBEWithSHA1AndRC4_128); // new key alg
314         checkInt(data, "110c010c110011", 10000); // new key ic
315         checkAlg(data, "110c110110", PBES2); // cert alg
316         checkInt(data, "110c110111011", 6666); // cert ic
317 
318         // Check keytool behavior
319 
320         // ksnormal has password
321 
322         keytool("-list -keystore ksnormal")
323                 .shouldContain("WARNING WARNING WARNING")
324                 .shouldContain("Certificate chain length: 0");
325 
326         SecurityTools.setResponse("changeit");
327         keytool("-list -keystore ksnormal")
328                 .shouldNotContain("WARNING WARNING WARNING")
329                 .shouldContain("Certificate fingerprint");
330 
331         // ksnopass is password-less
332 
333         keytool("-list -keystore ksnopass")
334                 .shouldNotContain("WARNING WARNING WARNING")
335                 .shouldContain("Certificate fingerprint");
336 
337         // -certreq prompts for keypass
338         SecurityTools.setResponse("changeit");
339         keytool("-certreq -alias a -keystore ksnopass")
340                 .shouldContain("Enter key password for <a>")
341                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
342                 .shouldHaveExitValue(0);
343 
344         // -certreq -storepass works fine
345         keytool("-certreq -alias a -keystore ksnopass -storepass changeit")
346                 .shouldNotContain("Enter key password for <a>")
347                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
348                 .shouldHaveExitValue(0);
349 
350         // -certreq -keypass also works fine
351         keytool("-certreq -alias a -keystore ksnopass -keypass changeit")
352                 .shouldNotContain("Enter key password for <a>")
353                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
354                 .shouldHaveExitValue(0);
355 
356         // -importkeystore prompts for srckeypass
357         SecurityTools.setResponse("changeit", "changeit");
358         keytool("-importkeystore -srckeystore ksnopass "
359                 + "-destkeystore jks3 -deststorepass changeit")
360                 .shouldContain("Enter key password for <a>")
361                 .shouldContain("Enter key password for <b>")
362                 .shouldContain("2 entries successfully imported");
363 
364         // ksnopass2 is ksnopass + 2 cert entries
365 
366         ks = KeyStore.getInstance(new File("ksnopass"), (char[])null);
367         ks.setCertificateEntry("aa", ks.getCertificate("a"));
368         ks.setCertificateEntry("bb", ks.getCertificate("b"));
369         try (FileOutputStream fos = new FileOutputStream("ksnopass2")) {
370             ks.store(fos, null);
371         }
372 
373         // -importkeystore prompts for srckeypass for private keys
374         // and no prompt for certs
375         SecurityTools.setResponse("changeit", "changeit");
376         keytool("-importkeystore -srckeystore ksnopass2 "
377                 + "-destkeystore jks5 -deststorepass changeit")
378                 .shouldContain("Enter key password for <a>")
379                 .shouldContain("Enter key password for <b>")
380                 .shouldNotContain("Enter key password for <aa>")
381                 .shouldNotContain("Enter key password for <bb>")
382                 .shouldContain("4 entries successfully imported");
383 
384         // ksonlycert has only cert entries
385 
386         ks.deleteEntry("a");
387         ks.deleteEntry("b");
388         try (FileOutputStream fos = new FileOutputStream("ksonlycert")) {
389             ks.store(fos, null);
390         }
391 
392         // -importkeystore does not prompt at all
393         keytool("-importkeystore -srckeystore ksonlycert "
394                 + "-destkeystore jks6 -deststorepass changeit")
395                 .shouldNotContain("Enter key password for <aa>")
396                 .shouldNotContain("Enter key password for <bb>")
397                 .shouldContain("2 entries successfully imported");
398 
399         // create a new password-less keystore
400         keytool("-keystore ksnopass -exportcert -alias a -file a.cert -rfc");
401 
402         // Normally storepass is prompted for
403         keytool("-keystore kscert1 -importcert -alias a -file a.cert -noprompt")
404                 .shouldContain("Enter keystore password:");
405         keytool("-keystore kscert2 -importcert -alias a -file a.cert -noprompt "
406                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE")
407                 .shouldContain("Enter keystore password:");
408         keytool("-keystore kscert3 -importcert -alias a -file a.cert -noprompt "
409                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
410                 .shouldContain("Enter keystore password:");
411         // ... but not if it's password-less
412         keytool("-keystore kscert4 -importcert -alias a -file a.cert -noprompt "
413                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
414                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
415                 .shouldNotContain("Enter keystore password:");
416 
417         // still prompt for keypass for genkeypair and certreq
418         SecurityTools.setResponse("changeit", "changeit");
419         keytool("-keystore ksnopassnew -genkeypair -keyalg DSA "
420                 + "-alias a -dname CN=A "
421                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
422                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
423                 .shouldNotContain("Enter keystore password:")
424                 .shouldContain("Enter key password for <a>");
425         keytool("-keystore ksnopassnew -certreq -alias a")
426                 .shouldNotContain("Enter keystore password:")
427                 .shouldContain("Enter key password for <a>");
428         keytool("-keystore ksnopassnew -list -v -alias a")
429                 .shouldNotContain("Enter keystore password:")
430                 .shouldNotContain("Enter key password for <a>");
431 
432         // params only read on demand
433 
434         // keyPbeIterationCount is used by -genkeypair
435         keytool("-keystore ksgenbadkeyic -genkeypair -keyalg DSA "
436                 + "-alias a -dname CN=A "
437                 + "-storepass changeit "
438                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc")
439                 .shouldContain("keyPbeIterationCount is not a number: abc")
440                 .shouldHaveExitValue(1);
441 
442         keytool("-keystore ksnopassnew -exportcert -alias a -file a.cert");
443 
444         // but not used by -importcert
445         keytool("-keystore ksimpbadkeyic -importcert -alias a -file a.cert "
446                 + "-noprompt -storepass changeit "
447                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc")
448                 .shouldHaveExitValue(0);
449 
450         // None is used by -list
451         keytool("-keystore ksnormal -storepass changeit -list "
452                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc "
453                 + "-J-Dkeystore.pkcs12.certPbeIterationCount=abc "
454                 + "-J-Dkeystore.pkcs12.macIterationCount=abc")
455                 .shouldHaveExitValue(0);
456     }
457 
testWithOpensslCommands(String opensslPath)458     private static void testWithOpensslCommands(String opensslPath)
459             throws Throwable {
460 
461         OutputAnalyzer output1 = ProcessTools.executeCommand(opensslPath,
462                 "pkcs12", "-in", "ksnormal", "-passin", "pass:changeit",
463                 "-info", "-nokeys", "-nocerts");
464         output1.shouldHaveExitValue(0)
465             .shouldContain("MAC: sha256, Iteration 10000")
466             .shouldContain("Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC,"
467                     + " Iteration 10000, PRF hmacWithSHA256")
468             .shouldContain("PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC,"
469                     + " Iteration 10000, PRF hmacWithSHA256");
470 
471         OutputAnalyzer output2 = ProcessTools.executeCommand(opensslPath,
472                 "pkcs12", "-in", "ksnormaldup", "-passin", "pass:changeit",
473                 "-info", "-nokeys", "-nocerts");
474         output2.shouldHaveExitValue(0);
475         if(!output1.getStderr().equals(output2.getStderr())) {
476             throw new RuntimeException("Duplicate pkcs12 keystores"
477                     + " ksnormal & ksnormaldup show different info");
478         }
479 
480         output1 = ProcessTools.executeCommand(opensslPath, "pkcs12", "-in",
481                 "ksnopass", "-passin", "pass:changeit", "-info", "-nokeys",
482                 "-nocerts");
483         output1.shouldNotHaveExitValue(0);
484 
485         output1 = ProcessTools.executeCommand(opensslPath, "pkcs12", "-in",
486                 "ksnopass", "-passin", "pass:changeit", "-info", "-nokeys",
487                 "-nocerts", "-nomacver");
488         output1.shouldHaveExitValue(0)
489             .shouldNotContain("PKCS7 Encrypted data:")
490             .shouldContain("Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC,"
491                     + " Iteration 10000, PRF hmacWithSHA256")
492             .shouldContain("Shrouded Keybag: pbeWithSHA1And128BitRC4,"
493                     + " Iteration 10000");
494 
495         output2 = ProcessTools.executeCommand(opensslPath, "pkcs12", "-in",
496                 "ksnopassdup", "-passin", "pass:changeit", "-info", "-nokeys",
497                 "-nocerts", "-nomacver");
498         output2.shouldHaveExitValue(0);
499         if(!output1.getStderr().equals(output2.getStderr())) {
500             throw new RuntimeException("Duplicate pkcs12 keystores"
501                     + " ksnopass & ksnopassdup show different info");
502         }
503 
504         output1 = ProcessTools.executeCommand(opensslPath, "pkcs12", "-in",
505                 "ksnewic", "-passin", "pass:changeit", "-info", "-nokeys",
506                 "-nocerts");
507         output1.shouldHaveExitValue(0)
508             .shouldContain("MAC: sha256, Iteration 5555")
509             .shouldContain("Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC,"
510                     + " Iteration 7777, PRF hmacWithSHA256")
511             .shouldContain("Shrouded Keybag: pbeWithSHA1And128BitRC4,"
512                     + " Iteration 10000")
513             .shouldContain("PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC,"
514                     + " Iteration 6666, PRF hmacWithSHA256");
515 
516         output2 = ProcessTools.executeCommand(opensslPath, "pkcs12", "-in",
517                 "ksnewicdup", "-passin", "pass:changeit", "-info", "-nokeys",
518                 "-nocerts");
519         output2.shouldHaveExitValue(0);
520         if(!output1.getStderr().equals(output2.getStderr())) {
521             throw new RuntimeException("Duplicate pkcs12 keystores"
522                     + " ksnewic & ksnewicdup show different info");
523         }
524     }
525 
526     /**
527      * Check keystore loading and key/cert reading.
528      *
529      * @param keystore the file name of keystore
530      * @param alias the key/cert to read
531      * @param storePass store pass to try out, can be null
532      * @param keypass key pass to try, can not be null
533      * @param expectedLoad expected result of keystore loading, true if non
534      *                     null, false if null, exception class if exception
535      * @param expectedCert expected result of cert reading
536      * @param expectedKey expected result of key reading
537      */
check( String keystore, String alias, String storePass, String keypass, Object expectedLoad, Object expectedCert, Object expectedKey)538     private static void check(
539             String keystore,
540             String alias,
541             String storePass,
542             String keypass,
543             Object expectedLoad,
544             Object expectedCert,
545             Object expectedKey) {
546         KeyStore ks = null;
547         Object actualLoad, actualCert, actualKey;
548         String label = keystore + "-" + alias + "-" + storePass + "-" + keypass;
549         try {
550             ks = KeyStore.getInstance(new File(keystore),
551                     storePass == null ? null : storePass.toCharArray());
552             actualLoad = ks != null;
553         } catch (Exception e) {
554             e.printStackTrace(System.out);
555             actualLoad = e.getClass();
556         }
557         Asserts.assertEQ(expectedLoad, actualLoad, label + "-load");
558 
559         // If not loaded correctly, skip cert/key reading
560         if (!Objects.equals(actualLoad, true)) {
561             return;
562         }
563 
564         try {
565             actualCert = (ks.getCertificate(alias) != null);
566         } catch (Exception e) {
567             e.printStackTrace(System.out);
568             actualCert = e.getClass();
569         }
570         Asserts.assertEQ(expectedCert, actualCert, label + "-cert");
571 
572         try {
573             actualKey = (ks.getKey(alias, keypass.toCharArray()) != null);
574         } catch (Exception e) {
575             e.printStackTrace(System.out);
576             actualKey = e.getClass();
577         }
578         Asserts.assertEQ(expectedKey, actualKey, label + "-key");
579     }
580 
keytool(String s)581     private static OutputAnalyzer keytool(String s) throws Throwable {
582         return SecurityTools.keytool(s);
583     }
584 }
585