1 /*
2  * Copyright (c) 2018, 2020, 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
27  * @library /test/lib
28  * @modules java.base/sun.security.pkcs
29  *          java.base/sun.security.util
30  * @summary Customizing the generation of a PKCS12 keystore
31  */
32 
33 import jdk.test.lib.Asserts;
34 import jdk.test.lib.SecurityTools;
35 import jdk.test.lib.process.OutputAnalyzer;
36 
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.OutputStream;
43 import java.io.UncheckedIOException;
44 import java.nio.file.Files;
45 import java.nio.file.Path;
46 import java.security.KeyStore;
47 import java.util.Base64;
48 import java.util.Objects;
49 
50 import static jdk.test.lib.security.DerUtils.*;
51 import sun.security.util.ObjectIdentifier;
52 import sun.security.util.KnownOIDs;
53 import static sun.security.pkcs.ContentInfo.*;
54 
55 public class ParamsTest  {
56 
main(String[] args)57     public static void main(String[] args) throws Throwable {
58 
59         // De-BASE64 textual files in ./params to `pwd`
60         Files.newDirectoryStream(Path.of(System.getProperty("test.src"), "params"))
61                 .forEach(p -> {
62                     try (InputStream is = Files.newInputStream(p);
63                          OutputStream os = Files.newOutputStream(p.getFileName())){
64                         Base64.getMimeDecoder().wrap(is).transferTo(os);
65                     } catch (IOException e) {
66                         throw new UncheckedIOException(e);
67                     }
68                 });
69 
70         byte[] data;
71 
72         // openssl -> keytool interop check
73 
74         // os2. no cert pbe, no mac.
75         check("os2", "a", null, "changeit", true, true, true);
76         check("os2", "a", "changeit", "changeit", true, true, true);
77         // You can even load it with a wrong storepass, controversial
78         check("os2", "a", "wrongpass", "changeit", true, true, true);
79 
80         // os3. no cert pbe, has mac. just like JKS
81         check("os3", "a", null, "changeit", true, true, true);
82         check("os3", "a", "changeit", "changeit", true, true, true);
83         // Cannot load with a wrong storepass, same as JKS
84         check("os3", "a", "wrongpass", "-", IOException.class, "-", "-");
85 
86         // os4. non default algs
87         check("os4", "a", "changeit", "changeit", true, true, true);
88         check("os4", "a", "wrongpass", "-", IOException.class, "-", "-");
89         // no storepass no cert
90         check("os4", "a", null, "changeit", true, false, true);
91 
92         // os5. strong non default algs
93         check("os5", "a", "changeit", "changeit", true, true, true);
94         check("os5", "a", "wrongpass", "-", IOException.class, "-", "-");
95         // no storepass no cert
96         check("os5", "a", null, "changeit", true, false, true);
97 
98         // keytool
99 
100         // Current default pkcs12 setting
101         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
102                 + "-destkeystore ksnormal -deststorepass changeit");
103         data = Files.readAllBytes(Path.of("ksnormal"));
104         checkInt(data, "22", 100000); // Mac ic
105         checkAlg(data, "2000", oid(KnownOIDs.SHA_1)); // Mac alg
106         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // key alg
107         checkInt(data, "110c010c010011", 50000); // key ic
108         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
109         checkAlg(data, "110c110110", oid(KnownOIDs.PBEWithSHA1AndRC2_40)); // cert alg
110         checkInt(data, "110c1101111", 50000); // cert ic
111 
112         check("ksnormal", "a", "changeit", "changeit", true, true, true);
113         check("ksnormal", "a", null, "changeit", true, false, true);
114         check("ksnormal", "a", "wrongpass", "-", IOException.class, "-", "-");
115 
116         // Add a new entry with password-less settings, still has a storepass
117         keytool("-keystore ksnormal -genkeypair -keyalg DSA "
118                 + "-storepass changeit -alias b -dname CN=b "
119                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
120                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE");
121         data = Files.readAllBytes(Path.of("ksnormal"));
122         checkInt(data, "22", 100000); // Mac ic
123         checkAlg(data, "2000", oid(KnownOIDs.SHA_1)); // Mac alg
124         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // key alg
125         checkInt(data, "110c010c010011", 50000); // key ic
126         checkAlg(data, "110c010c11000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // new key alg
127         checkInt(data, "110c010c110011", 50000); // new key ic
128         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
129         checkAlg(data, "110c110110", oid(KnownOIDs.PBEWithSHA1AndRC2_40)); // cert alg
130         checkInt(data, "110c1101111", 50000); // cert ic
131         check("ksnormal", "b", null, "changeit", true, false, true);
132         check("ksnormal", "b", "changeit", "changeit", true, true, true);
133 
134         // Different keypbe alg, no cert pbe and no mac
135         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
136                 + "-destkeystore ksnopass -deststorepass changeit "
137                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128 "
138                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
139                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE");
140         data = Files.readAllBytes(Path.of("ksnopass"));
141         shouldNotExist(data, "2"); // no Mac
142         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndRC4_128));
143         checkInt(data, "110c010c010011", 50000);
144         checkAlg(data, "110c10", DATA_OID);
145         check("ksnopass", "a", null, "changeit", true, true, true);
146         check("ksnopass", "a", "changeit", "changeit", true, true, true);
147         check("ksnopass", "a", "wrongpass", "changeit", true, true, true);
148 
149         // Add a new entry with normal settings, still password-less
150         keytool("-keystore ksnopass -genkeypair -keyalg DSA "
151                 + "-storepass changeit -alias b -dname CN=B");
152         data = Files.readAllBytes(Path.of("ksnopass"));
153         shouldNotExist(data, "2"); // no Mac
154         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndRC4_128));
155         checkInt(data, "110c010c010011", 50000);
156         checkAlg(data, "110c010c11000", oid(KnownOIDs.PBEWithSHA1AndDESede));
157         checkInt(data, "110c010c110011", 50000);
158         checkAlg(data, "110c10", DATA_OID);
159         check("ksnopass", "a", null, "changeit", true, true, true);
160         check("ksnopass", "b", null, "changeit", true, true, true);
161 
162         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
163                 + "-destkeystore ksnewic -deststorepass changeit "
164                 + "-J-Dkeystore.pkcs12.macIterationCount=5555 "
165                 + "-J-Dkeystore.pkcs12.certPbeIterationCount=6666 "
166                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=7777");
167         data = Files.readAllBytes(Path.of("ksnewic"));
168         checkInt(data, "22", 5555); // Mac ic
169         checkAlg(data, "2000", oid(KnownOIDs.SHA_1)); // Mac alg
170         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // key alg
171         checkInt(data, "110c010c010011", 7777); // key ic
172         checkAlg(data, "110c110110", oid(KnownOIDs.PBEWithSHA1AndRC2_40)); // cert alg
173         checkInt(data, "110c1101111", 6666); // cert ic
174 
175         // keypbe alg cannot be NONE
176         keytool("-keystore ksnewic -genkeypair -keyalg DSA "
177                 + "-storepass changeit -alias b -dname CN=B "
178                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=NONE")
179                 .shouldContain("NONE AlgorithmParameters not available")
180                 .shouldHaveExitValue(1);
181 
182         // new entry new keypbe alg (and default ic), else unchanged
183         keytool("-keystore ksnewic -genkeypair -keyalg DSA "
184                 + "-storepass changeit -alias b -dname CN=B "
185                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128");
186         data = Files.readAllBytes(Path.of("ksnewic"));
187         checkInt(data, "22", 5555); // Mac ic
188         checkAlg(data, "2000", oid(KnownOIDs.SHA_1)); // Mac alg
189         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // key alg
190         checkInt(data, "110c010c010011", 7777); // key ic
191         checkAlg(data, "110c010c11000", oid(KnownOIDs.PBEWithSHA1AndRC4_128)); // new key alg
192         checkInt(data, "110c010c110011", 50000); // new key ic
193         checkAlg(data, "110c110110", oid(KnownOIDs.PBEWithSHA1AndRC2_40)); // cert alg
194         checkInt(data, "110c1101111", 6666); // cert ic
195 
196         // Check KeyStore loading multiple keystores
197         KeyStore ks = KeyStore.getInstance("pkcs12");
198         try (FileInputStream fis = new FileInputStream("ksnormal");
199                 FileOutputStream fos = new FileOutputStream("ksnormaldup")) {
200             ks.load(fis, "changeit".toCharArray());
201             ks.store(fos, "changeit".toCharArray());
202         }
203         data = Files.readAllBytes(Path.of("ksnormaldup"));
204         checkInt(data, "22", 100000); // Mac ic
205         checkAlg(data, "2000", oid(KnownOIDs.SHA_1)); // Mac alg
206         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // key alg
207         checkInt(data, "110c010c010011", 50000); // key ic
208         checkAlg(data, "110c010c11000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // new key alg
209         checkInt(data, "110c010c110011", 50000); // new key ic
210         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
211         checkAlg(data, "110c110110", oid(KnownOIDs.PBEWithSHA1AndRC2_40)); // cert alg
212         checkInt(data, "110c1101111", 50000); // cert ic
213 
214         try (FileInputStream fis = new FileInputStream("ksnopass");
215              FileOutputStream fos = new FileOutputStream("ksnopassdup")) {
216             ks.load(fis, "changeit".toCharArray());
217             ks.store(fos, "changeit".toCharArray());
218         }
219         data = Files.readAllBytes(Path.of("ksnopassdup"));
220         shouldNotExist(data, "2"); // no Mac
221         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndRC4_128));
222         checkInt(data, "110c010c010011", 50000);
223         checkAlg(data, "110c010c11000", oid(KnownOIDs.PBEWithSHA1AndDESede));
224         checkInt(data, "110c010c110011", 50000);
225         checkAlg(data, "110c10", DATA_OID);
226 
227         try (FileInputStream fis = new FileInputStream("ksnewic");
228              FileOutputStream fos = new FileOutputStream("ksnewicdup")) {
229             ks.load(fis, "changeit".toCharArray());
230             ks.store(fos, "changeit".toCharArray());
231         }
232         data = Files.readAllBytes(Path.of("ksnewicdup"));
233         checkInt(data, "22", 5555); // Mac ic
234         checkAlg(data, "2000", oid(KnownOIDs.SHA_1)); // Mac alg
235         checkAlg(data, "110c010c01000", oid(KnownOIDs.PBEWithSHA1AndDESede)); // key alg
236         checkInt(data, "110c010c010011", 7777); // key ic
237         checkAlg(data, "110c010c11000", oid(KnownOIDs.PBEWithSHA1AndRC4_128)); // new key alg
238         checkInt(data, "110c010c110011", 50000); // new key ic
239         checkAlg(data, "110c110110", oid(KnownOIDs.PBEWithSHA1AndRC2_40)); // cert alg
240         checkInt(data, "110c1101111", 6666); // cert ic
241 
242         // Check keytool behavior
243 
244         // ksnormal has password
245 
246         keytool("-list -keystore ksnormal")
247                 .shouldContain("WARNING WARNING WARNING")
248                 .shouldContain("Certificate chain length: 0");
249 
250         SecurityTools.setResponse("changeit");
251         keytool("-list -keystore ksnormal")
252                 .shouldNotContain("WARNING WARNING WARNING")
253                 .shouldContain("Certificate fingerprint");
254 
255         // ksnopass is password-less
256 
257         keytool("-list -keystore ksnopass")
258                 .shouldNotContain("WARNING WARNING WARNING")
259                 .shouldContain("Certificate fingerprint");
260 
261         // -certreq prompts for keypass
262         SecurityTools.setResponse("changeit");
263         keytool("-certreq -alias a -keystore ksnopass")
264                 .shouldContain("Enter key password for <a>")
265                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
266                 .shouldHaveExitValue(0);
267 
268         // -certreq -storepass works fine
269         keytool("-certreq -alias a -keystore ksnopass -storepass changeit")
270                 .shouldNotContain("Enter key password for <a>")
271                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
272                 .shouldHaveExitValue(0);
273 
274         // -certreq -keypass also works fine
275         keytool("-certreq -alias a -keystore ksnopass -keypass changeit")
276                 .shouldNotContain("Enter key password for <a>")
277                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
278                 .shouldHaveExitValue(0);
279 
280         // -importkeystore prompts for srckeypass
281         SecurityTools.setResponse("changeit", "changeit");
282         keytool("-importkeystore -srckeystore ksnopass "
283                 + "-destkeystore jks3 -deststorepass changeit")
284                 .shouldContain("Enter key password for <a>")
285                 .shouldContain("Enter key password for <b>")
286                 .shouldContain("2 entries successfully imported");
287 
288         // ksnopass2 is ksnopass + 2 cert entries
289 
290         ks = KeyStore.getInstance(new File("ksnopass"), (char[])null);
291         ks.setCertificateEntry("aa", ks.getCertificate("a"));
292         ks.setCertificateEntry("bb", ks.getCertificate("b"));
293         try (FileOutputStream fos = new FileOutputStream("ksnopass2")) {
294             ks.store(fos, null);
295         }
296 
297         // -importkeystore prompts for srckeypass for private keys
298         // and no prompt for certs
299         SecurityTools.setResponse("changeit", "changeit");
300         keytool("-importkeystore -srckeystore ksnopass2 "
301                 + "-destkeystore jks5 -deststorepass changeit")
302                 .shouldContain("Enter key password for <a>")
303                 .shouldContain("Enter key password for <b>")
304                 .shouldNotContain("Enter key password for <aa>")
305                 .shouldNotContain("Enter key password for <bb>")
306                 .shouldContain("4 entries successfully imported");
307 
308         // ksonlycert has only cert entries
309 
310         ks.deleteEntry("a");
311         ks.deleteEntry("b");
312         try (FileOutputStream fos = new FileOutputStream("ksonlycert")) {
313             ks.store(fos, null);
314         }
315 
316         // -importkeystore does not prompt at all
317         keytool("-importkeystore -srckeystore ksonlycert "
318                 + "-destkeystore jks6 -deststorepass changeit")
319                 .shouldNotContain("Enter key password for <aa>")
320                 .shouldNotContain("Enter key password for <bb>")
321                 .shouldContain("2 entries successfully imported");
322 
323         // create a new password-less keystore
324         keytool("-keystore ksnopass -exportcert -alias a -file a.cert -rfc");
325 
326         // Normally storepass is prompted for
327         keytool("-keystore kscert1 -importcert -alias a -file a.cert -noprompt")
328                 .shouldContain("Enter keystore password:");
329         keytool("-keystore kscert2 -importcert -alias a -file a.cert -noprompt "
330                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE")
331                 .shouldContain("Enter keystore password:");
332         keytool("-keystore kscert3 -importcert -alias a -file a.cert -noprompt "
333                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
334                 .shouldContain("Enter keystore password:");
335         // ... but not if it's password-less
336         keytool("-keystore kscert4 -importcert -alias a -file a.cert -noprompt "
337                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
338                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
339                 .shouldNotContain("Enter keystore password:");
340 
341         // still prompt for keypass for genkeypair and certreq
342         SecurityTools.setResponse("changeit", "changeit");
343         keytool("-keystore ksnopassnew -genkeypair -keyalg DSA "
344                 + "-alias a -dname CN=A "
345                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
346                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
347                 .shouldNotContain("Enter keystore password:")
348                 .shouldContain("Enter key password for <a>");
349         keytool("-keystore ksnopassnew -certreq -alias a")
350                 .shouldNotContain("Enter keystore password:")
351                 .shouldContain("Enter key password for <a>");
352         keytool("-keystore ksnopassnew -list -v -alias a")
353                 .shouldNotContain("Enter keystore password:")
354                 .shouldNotContain("Enter key password for <a>");
355 
356         // params only read on demand
357 
358         // keyPbeIterationCount is used by -genkeypair
359         keytool("-keystore ksgenbadkeyic -genkeypair -keyalg DSA "
360                 + "-alias a -dname CN=A "
361                 + "-storepass changeit "
362                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc")
363                 .shouldContain("keyPbeIterationCount is not a number: abc")
364                 .shouldHaveExitValue(1);
365 
366         keytool("-keystore ksnopassnew -exportcert -alias a -file a.cert");
367 
368         // but not used by -importcert
369         keytool("-keystore ksimpbadkeyic -importcert -alias a -file a.cert "
370                 + "-noprompt -storepass changeit "
371                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc")
372                 .shouldHaveExitValue(0);
373 
374         // None is used by -list
375         keytool("-keystore ksnormal -storepass changeit -list "
376                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc "
377                 + "-J-Dkeystore.pkcs12.certPbeIterationCount=abc "
378                 + "-J-Dkeystore.pkcs12.macIterationCount=abc")
379                 .shouldHaveExitValue(0);
380     }
381 
382     /**
383      * Check keystore loading and key/cert reading.
384      *
385      * @param keystore the file name of keystore
386      * @param alias the key/cert to read
387      * @param storePass store pass to try out, can be null
388      * @param keypass key pass to try, can not be null
389      * @param expectedLoad expected result of keystore loading, true if non
390      *                     null, false if null, exception class if exception
391      * @param expectedCert expected result of cert reading
392      * @param expectedKey expected result of key reading
393      */
check( String keystore, String alias, String storePass, String keypass, Object expectedLoad, Object expectedCert, Object expectedKey)394     private static void check(
395             String keystore,
396             String alias,
397             String storePass,
398             String keypass,
399             Object expectedLoad,
400             Object expectedCert,
401             Object expectedKey) {
402         KeyStore ks = null;
403         Object actualLoad, actualCert, actualKey;
404         String label = keystore + "-" + alias + "-" + storePass + "-" + keypass;
405         try {
406             ks = KeyStore.getInstance(new File(keystore),
407                     storePass == null ? null : storePass.toCharArray());
408             actualLoad = ks != null;
409         } catch (Exception e) {
410             e.printStackTrace(System.out);
411             actualLoad = e.getClass();
412         }
413         Asserts.assertEQ(expectedLoad, actualLoad, label + "-load");
414 
415         // If not loaded correctly, skip cert/key reading
416         if (!Objects.equals(actualLoad, true)) {
417             return;
418         }
419 
420         try {
421             actualCert = (ks.getCertificate(alias) != null);
422         } catch (Exception e) {
423             e.printStackTrace(System.out);
424             actualCert = e.getClass();
425         }
426         Asserts.assertEQ(expectedCert, actualCert, label + "-cert");
427 
428         try {
429             actualKey = (ks.getKey(alias, keypass.toCharArray()) != null);
430         } catch (Exception e) {
431             e.printStackTrace(System.out);
432             actualKey = e.getClass();
433         }
434         Asserts.assertEQ(expectedKey, actualKey, label + "-key");
435     }
436 
oid(KnownOIDs o)437     private static ObjectIdentifier oid(KnownOIDs o) {
438         return ObjectIdentifier.of(o);
439     }
440 
keytool(String s)441     static OutputAnalyzer keytool(String s) throws Throwable {
442         return SecurityTools.keytool(s);
443     }
444 }
445