1/*
2 * Copyright (c) 2017, 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 8171319 8177569
27 * @summary keytool should print out warnings when reading or generating
28  *         cert/cert req using weak algorithms
29 * @library /test/lib
30 * @modules java.base/sun.security.tools.keytool
31 *          java.base/sun.security.tools
32 *          java.base/sun.security.util
33 * @build jdk.test.lib.SecurityTools
34 *        jdk.test.lib.Utils
35 *        jdk.test.lib.Asserts
36 *        jdk.test.lib.JDKToolFinder
37 *        jdk.test.lib.JDKToolLauncher
38 *        jdk.test.lib.Platform
39 *        jdk.test.lib.process.*
40 * @run main/othervm/timeout=600 -Duser.language=en -Duser.country=US WeakAlg
41 */
42
43import jdk.test.lib.SecurityTools;
44import jdk.test.lib.process.OutputAnalyzer;
45import sun.security.tools.KeyStoreUtil;
46import sun.security.util.DisabledAlgorithmConstraints;
47
48import java.io.ByteArrayInputStream;
49import java.io.ByteArrayOutputStream;
50import java.io.IOException;
51import java.io.InputStream;
52import java.io.PrintStream;
53import java.nio.file.Files;
54import java.nio.file.Paths;
55import java.nio.file.StandardCopyOption;
56import java.security.CryptoPrimitive;
57import java.security.KeyStore;
58import java.security.cert.X509Certificate;
59import java.util.Collections;
60import java.util.EnumSet;
61import java.util.Set;
62import java.util.stream.Collectors;
63import java.util.stream.Stream;
64
65public class WeakAlg {
66
67    public static void main(String[] args) throws Throwable {
68
69        rm("ks");
70
71        // -genkeypair, and -printcert, -list -alias, -exportcert
72        // (w/ different formats)
73        checkGenKeyPair("a", "-keyalg RSA -sigalg MD5withRSA", "MD5withRSA");
74        checkGenKeyPair("b", "-keyalg RSA -keysize 512", "512-bit RSA key");
75        checkGenKeyPair("c", "-keyalg RSA", null);
76
77        kt("-list")
78                .shouldContain("Warning:")
79                .shouldMatch("<a>.*MD5withRSA.*risk")
80                .shouldMatch("<b>.*512-bit RSA key.*risk");
81        kt("-list -v")
82                .shouldContain("Warning:")
83                .shouldMatch("<a>.*MD5withRSA.*risk")
84                .shouldContain("MD5withRSA (weak)")
85                .shouldMatch("<b>.*512-bit RSA key.*risk")
86                .shouldContain("512-bit RSA key (weak)");
87
88        // Multiple warnings for multiple cert in -printcert
89        // or -list or -exportcert
90
91        // -certreq, -printcertreq, -gencert
92        checkCertReq("a", "", null);
93        gencert("c-a", "")
94                .shouldNotContain("Warning"); // new sigalg is not weak
95        gencert("c-a", "-sigalg MD2withRSA")
96                .shouldContain("Warning:")
97                .shouldMatch("The generated certificate.*MD2withRSA.*risk");
98
99        checkCertReq("a", "-sigalg MD5withRSA", "MD5withRSA");
100        gencert("c-a", "")
101                .shouldContain("Warning:")
102                .shouldMatch("The certificate request.*MD5withRSA.*risk");
103        gencert("c-a", "-sigalg MD2withRSA")
104                .shouldContain("Warning:")
105                .shouldMatch("The certificate request.*MD5withRSA.*risk")
106                .shouldMatch("The generated certificate.*MD2withRSA.*risk");
107
108        checkCertReq("b", "", "512-bit RSA key");
109        gencert("c-b", "")
110                .shouldContain("Warning:")
111                .shouldMatch("The certificate request.*512-bit RSA key.*risk")
112                .shouldMatch("The generated certificate.*512-bit RSA key.*risk");
113
114        checkCertReq("c", "", null);
115        gencert("a-c", "")
116                .shouldContain("Warning:")
117                .shouldMatch("The issuer.*MD5withRSA.*risk");
118
119        // but the new cert is not weak
120        kt("-printcert -file a-c.cert")
121                .shouldNotContain("Warning")
122                .shouldNotContain("weak");
123
124        gencert("b-c", "")
125                .shouldContain("Warning:")
126                .shouldMatch("The issuer.*512-bit RSA key.*risk");
127
128        // -importcert
129        checkImport();
130
131        // -importkeystore
132        checkImportKeyStore();
133
134        // -gencrl, -printcrl
135
136        checkGenCRL("a", "", null);
137        checkGenCRL("a", "-sigalg MD5withRSA", "MD5withRSA");
138        checkGenCRL("b", "", "512-bit RSA key");
139        checkGenCRL("c", "", null);
140
141        kt("-delete -alias b");
142        kt("-printcrl -file b.crl")
143                .shouldContain("WARNING: not verified");
144    }
145
146    static void checkImportKeyStore() throws Exception {
147
148        saveStore();
149
150        rm("ks");
151        kt("-importkeystore -srckeystore ks2 -srcstorepass changeit")
152                .shouldContain("3 entries successfully imported")
153                .shouldContain("Warning")
154                .shouldMatch("<b>.*512-bit RSA key.*risk")
155                .shouldMatch("<a>.*MD5withRSA.*risk");
156
157        rm("ks");
158        kt("-importkeystore -srckeystore ks2 -srcstorepass changeit -srcalias a")
159                .shouldContain("Warning")
160                .shouldMatch("<a>.*MD5withRSA.*risk");
161
162        reStore();
163    }
164
165    static void checkImport() throws Exception {
166
167        saveStore();
168
169        // add trusted cert
170
171        // cert already in
172        kt("-importcert -alias d -file a.cert", "no")
173                .shouldContain("Certificate already exists in keystore")
174                .shouldContain("Warning")
175                .shouldMatch("The input.*MD5withRSA.*risk")
176                .shouldContain("Do you still want to add it?");
177        kt("-importcert -alias d -file a.cert -noprompt")
178                .shouldContain("Warning")
179                .shouldMatch("The input.*MD5withRSA.*risk")
180                .shouldNotContain("[no]");
181
182        // cert is self-signed
183        kt("-delete -alias a");
184        kt("-delete -alias d");
185        kt("-importcert -alias d -file a.cert", "no")
186                .shouldContain("Warning")
187                .shouldContain("MD5withRSA (weak)")
188                .shouldMatch("The input.*MD5withRSA.*risk")
189                .shouldContain("Trust this certificate?");
190        kt("-importcert -alias d -file a.cert -noprompt")
191                .shouldContain("Warning")
192                .shouldMatch("The input.*MD5withRSA.*risk")
193                .shouldNotContain("[no]");
194
195        // JDK-8177569: no warning for sigalg of trusted cert
196        String weakSigAlgCA = null;
197        KeyStore ks = KeyStoreUtil.getCacertsKeyStore();
198        if (ks != null) {
199            DisabledAlgorithmConstraints disabledCheck =
200                    new DisabledAlgorithmConstraints(
201                            DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
202            Set<CryptoPrimitive> sigPrimitiveSet = Collections
203                    .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
204
205            for (String s : Collections.list(ks.aliases())) {
206                if (ks.isCertificateEntry(s)) {
207                    X509Certificate c = (X509Certificate)ks.getCertificate(s);
208                    String sigAlg = c.getSigAlgName();
209                    if (!disabledCheck.permits(sigPrimitiveSet, sigAlg, null)) {
210                        weakSigAlgCA = sigAlg;
211                        Files.write(Paths.get("ca.cert"),
212                                ks.getCertificate(s).getEncoded());
213                        break;
214                    }
215                }
216            }
217        }
218        if (weakSigAlgCA != null) {
219            // The following 2 commands still have a warning on why not using
220            // the -cacerts option directly.
221            kt("-list -keystore " + KeyStoreUtil.getCacerts())
222                    .shouldNotContain("risk");
223            kt("-list -v -keystore " + KeyStoreUtil.getCacerts())
224                    .shouldNotContain("risk");
225
226            // -printcert will always show warnings
227            kt("-printcert -file ca.cert")
228                    .shouldContain("name: " + weakSigAlgCA + " (weak)")
229                    .shouldContain("Warning")
230                    .shouldMatch("The certificate.*" + weakSigAlgCA + ".*risk");
231            kt("-printcert -file ca.cert -trustcacerts") // -trustcacerts useless
232                    .shouldContain("name: " + weakSigAlgCA + " (weak)")
233                    .shouldContain("Warning")
234                    .shouldMatch("The certificate.*" + weakSigAlgCA + ".*risk");
235
236            // Importing with -trustcacerts ignore CA cert's sig alg
237            kt("-delete -alias d");
238            kt("-importcert -alias d -trustcacerts -file ca.cert", "no")
239                    .shouldContain("Certificate already exists in system-wide CA")
240                    .shouldNotContain("risk")
241                    .shouldContain("Do you still want to add it to your own keystore?");
242            kt("-importcert -alias d -trustcacerts -file ca.cert -noprompt")
243                    .shouldNotContain("risk")
244                    .shouldNotContain("[no]");
245
246            // but not without -trustcacerts
247            kt("-delete -alias d");
248            kt("-importcert -alias d -file ca.cert", "no")
249                    .shouldContain("name: " + weakSigAlgCA + " (weak)")
250                    .shouldContain("Warning")
251                    .shouldMatch("The input.*" + weakSigAlgCA + ".*risk")
252                    .shouldContain("Trust this certificate?");
253            kt("-importcert -alias d -file ca.cert -noprompt")
254                    .shouldContain("Warning")
255                    .shouldMatch("The input.*" + weakSigAlgCA + ".*risk")
256                    .shouldNotContain("[no]");
257        }
258
259        // a non self-signed weak cert
260        reStore();
261        certreq("b", "");
262        gencert("c-b", "");
263        kt("-importcert -alias d -file c-b.cert")   // weak only, no prompt
264                .shouldContain("Warning")
265                .shouldNotContain("512-bit RSA key (weak)")
266                .shouldMatch("The input.*512-bit RSA key.*risk")
267                .shouldNotContain("[no]");
268
269        kt("-delete -alias b");
270        kt("-delete -alias c");
271        kt("-delete -alias d");
272
273        kt("-importcert -alias d -file c-b.cert", "no") // weak and not trusted
274                .shouldContain("Warning")
275                .shouldContain("512-bit RSA key (weak)")
276                .shouldMatch("The input.*512-bit RSA key.*risk")
277                .shouldContain("Trust this certificate?");
278        kt("-importcert -alias d -file c-b.cert -noprompt")
279                .shouldContain("Warning")
280                .shouldMatch("The input.*512-bit RSA key.*risk")
281                .shouldNotContain("[no]");
282
283        // a non self-signed strong cert
284        reStore();
285        certreq("a", "");
286        gencert("c-a", "");
287        kt("-importcert -alias d -file c-a.cert") // trusted
288                .shouldNotContain("Warning")
289                .shouldNotContain("[no]");
290
291        kt("-delete -alias a");
292        kt("-delete -alias c");
293        kt("-delete -alias d");
294
295        kt("-importcert -alias d -file c-a.cert", "no") // not trusted
296                .shouldNotContain("Warning")
297                .shouldContain("Trust this certificate?");
298        kt("-importcert -alias d -file c-a.cert -noprompt")
299                .shouldNotContain("Warning")
300                .shouldNotContain("[no]");
301
302        // install reply
303
304        reStore();
305        certreq("c", "");
306        gencert("a-c", "");
307        kt("-importcert -alias c -file a-c.cert")
308                .shouldContain("Warning")
309                .shouldMatch("Issuer <a>.*MD5withRSA.*risk");
310
311        // JDK-8177569: no warning for sigalg of trusted cert
312        reStore();
313        // Change a into a TrustedCertEntry
314        kt("-exportcert -alias a -file a.cert");
315        kt("-delete -alias a");
316        kt("-importcert -alias a -file a.cert -noprompt");
317        kt("-list -alias a -v")
318                .shouldNotContain("weak")
319                .shouldNotContain("Warning");
320        // This time a is trusted and no warning on its weak sig alg
321        kt("-importcert -alias c -file a-c.cert")
322                .shouldNotContain("Warning");
323
324        reStore();
325
326        gencert("a-b", "");
327        gencert("b-c", "");
328
329        // Full chain with root
330        cat("a-a-b-c.cert", "b-c.cert", "a-b.cert", "a.cert");
331        kt("-importcert -alias c -file a-a-b-c.cert")   // only weak
332                .shouldContain("Warning")
333                .shouldMatch("Reply #2 of 3.*512-bit RSA key.*risk")
334                .shouldMatch("Reply #3 of 3.*MD5withRSA.*risk")
335                .shouldNotContain("[no]");
336
337        // Without root
338        cat("a-b-c.cert", "b-c.cert", "a-b.cert");
339        kt("-importcert -alias c -file a-b-c.cert")     // only weak
340                .shouldContain("Warning")
341                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
342                .shouldMatch("Issuer <a>.*MD5withRSA.*risk")
343                .shouldNotContain("[no]");
344
345        reStore();
346        gencert("b-a", "");
347
348        kt("-importcert -alias a -file b-a.cert")
349                .shouldContain("Warning")
350                .shouldMatch("Issuer <b>.*512-bit RSA key.*risk")
351                .shouldNotContain("[no]");
352
353        kt("-importcert -alias a -file c-a.cert")
354                .shouldNotContain("Warning");
355
356        kt("-importcert -alias b -file c-b.cert")
357                .shouldContain("Warning")
358                .shouldMatch("The input.*512-bit RSA key.*risk")
359                .shouldNotContain("[no]");
360
361        reStore();
362        gencert("b-a", "");
363
364        cat("c-b-a.cert", "b-a.cert", "c-b.cert");
365
366        kt("-printcert -file c-b-a.cert")
367                .shouldContain("Warning")
368                .shouldMatch("The certificate #2 of 2.*512-bit RSA key.*risk");
369
370        kt("-delete -alias b");
371
372        kt("-importcert -alias a -file c-b-a.cert")
373                .shouldContain("Warning")
374                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
375                .shouldNotContain("[no]");
376
377        kt("-delete -alias c");
378        kt("-importcert -alias a -file c-b-a.cert", "no")
379                .shouldContain("Top-level certificate in reply:")
380                .shouldContain("512-bit RSA key (weak)")
381                .shouldContain("Warning")
382                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
383                .shouldContain("Install reply anyway?");
384        kt("-importcert -alias a -file c-b-a.cert -noprompt")
385                .shouldContain("Warning")
386                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
387                .shouldNotContain("[no]");
388
389        reStore();
390    }
391
392    private static void cat(String dest, String... src) throws IOException {
393        System.out.println("---------------------------------------------");
394        System.out.printf("$ cat ");
395
396        ByteArrayOutputStream bout = new ByteArrayOutputStream();
397        for (String s : src) {
398            System.out.printf(s + " ");
399            bout.write(Files.readAllBytes(Paths.get(s)));
400        }
401        Files.write(Paths.get(dest), bout.toByteArray());
402        System.out.println("> " + dest);
403    }
404
405    static void checkGenCRL(String alias, String options, String bad) {
406
407        OutputAnalyzer oa = kt("-gencrl -alias " + alias
408                + " -id 1 -file " + alias + ".crl " + options);
409        if (bad == null) {
410            oa.shouldNotContain("Warning");
411        } else {
412            oa.shouldContain("Warning")
413                    .shouldMatch("The generated CRL.*" + bad + ".*risk");
414        }
415
416        oa = kt("-printcrl -file " + alias + ".crl");
417        if (bad == null) {
418            oa.shouldNotContain("Warning")
419                    .shouldContain("Verified by " + alias + " in keystore")
420                    .shouldNotContain("(weak");
421        } else {
422            oa.shouldContain("Warning:")
423                    .shouldMatch("The CRL.*" + bad + ".*risk")
424                    .shouldContain("Verified by " + alias + " in keystore")
425                    .shouldContain(bad + " (weak)");
426        }
427    }
428
429    static void checkCertReq(
430            String alias, String options, String bad) {
431
432        OutputAnalyzer oa = certreq(alias, options);
433        if (bad == null) {
434            oa.shouldNotContain("Warning");
435        } else {
436            oa.shouldContain("Warning")
437                    .shouldMatch("The generated certificate request.*" + bad + ".*risk");
438        }
439
440        oa = kt("-printcertreq -file " + alias + ".req");
441        if (bad == null) {
442            oa.shouldNotContain("Warning")
443                    .shouldNotContain("(weak)");
444        } else {
445            oa.shouldContain("Warning")
446                    .shouldMatch("The certificate request.*" + bad + ".*risk")
447                    .shouldContain(bad + " (weak)");
448        }
449    }
450
451    static void checkGenKeyPair(
452            String alias, String options, String bad) {
453
454        OutputAnalyzer oa = genkeypair(alias, options);
455        if (bad == null) {
456            oa.shouldNotContain("Warning");
457        } else {
458            oa.shouldContain("Warning")
459                    .shouldMatch("The generated certificate.*" + bad + ".*risk");
460        }
461
462        oa = kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
463        if (bad == null) {
464            oa.shouldNotContain("Warning");
465        } else {
466            oa.shouldContain("Warning")
467                    .shouldMatch("The certificate.*" + bad + ".*risk");
468        }
469
470        oa = kt("-exportcert -rfc -alias " + alias + " -file " + alias + ".cert");
471        if (bad == null) {
472            oa.shouldNotContain("Warning");
473        } else {
474            oa.shouldContain("Warning")
475                    .shouldMatch("The certificate.*" + bad + ".*risk");
476        }
477
478        oa = kt("-printcert -rfc -file " + alias + ".cert");
479        if (bad == null) {
480            oa.shouldNotContain("Warning");
481        } else {
482            oa.shouldContain("Warning")
483                    .shouldMatch("The certificate.*" + bad + ".*risk");
484        }
485
486        oa = kt("-list -alias " + alias);
487        if (bad == null) {
488            oa.shouldNotContain("Warning");
489        } else {
490            oa.shouldContain("Warning")
491                    .shouldMatch("The certificate.*" + bad + ".*risk");
492        }
493
494        // With cert content
495
496        oa = kt("-printcert -file " + alias + ".cert");
497        if (bad == null) {
498            oa.shouldNotContain("Warning");
499        } else {
500            oa.shouldContain("Warning")
501                    .shouldContain(bad + " (weak)")
502                    .shouldMatch("The certificate.*" + bad + ".*risk");
503        }
504
505        oa = kt("-list -v -alias " + alias);
506        if (bad == null) {
507            oa.shouldNotContain("Warning");
508        } else {
509            oa.shouldContain("Warning")
510                    .shouldContain(bad + " (weak)")
511                    .shouldMatch("The certificate.*" + bad + ".*risk");
512        }
513    }
514
515    // This is slow, but real keytool process is launched.
516    static OutputAnalyzer kt1(String cmd, String... input) {
517        cmd = "-keystore ks -storepass changeit " +
518                "-keypass changeit " + cmd;
519        System.out.println("---------------------------------------------");
520        try {
521            SecurityTools.setResponse(input);
522            return SecurityTools.keytool(cmd);
523        } catch (Throwable e) {
524            throw new RuntimeException(e);
525        }
526    }
527
528    // Fast keytool execution by directly calling its main() method
529    static OutputAnalyzer kt(String cmd, String... input) {
530        PrintStream out = System.out;
531        PrintStream err = System.err;
532        InputStream ins = System.in;
533        ByteArrayOutputStream bout = new ByteArrayOutputStream();
534        ByteArrayOutputStream berr = new ByteArrayOutputStream();
535        boolean succeed = true;
536        try {
537            cmd = "-keystore ks -storepass changeit " +
538                    "-keypass changeit " + cmd;
539            System.out.println("---------------------------------------------");
540            System.out.println("$ keytool " + cmd);
541            System.out.println();
542            String feed = "";
543            if (input.length > 0) {
544                feed = Stream.of(input).collect(Collectors.joining("\n")) + "\n";
545            }
546            System.setIn(new ByteArrayInputStream(feed.getBytes()));
547            System.setOut(new PrintStream(bout));
548            System.setErr(new PrintStream(berr));
549            sun.security.tools.keytool.Main.main(
550                    cmd.trim().split("\\s+"));
551        } catch (Exception e) {
552            // Might be a normal exception when -debug is on or
553            // SecurityException (thrown by jtreg) when System.exit() is called
554            if (!(e instanceof SecurityException)) {
555                e.printStackTrace();
556            }
557            succeed = false;
558        } finally {
559            System.setOut(out);
560            System.setErr(err);
561            System.setIn(ins);
562        }
563        String sout = new String(bout.toByteArray());
564        String serr = new String(berr.toByteArray());
565        System.out.println("STDOUT:\n" + sout + "\nSTDERR:\n" + serr);
566        if (!succeed) {
567            throw new RuntimeException();
568        }
569        return new OutputAnalyzer(sout, serr);
570    }
571
572    static OutputAnalyzer genkeypair(String alias, String options) {
573        return kt("-genkeypair -alias " + alias + " -dname CN=" + alias
574                + " -storetype JKS " + options);
575    }
576
577    static OutputAnalyzer certreq(String alias, String options) {
578        return kt("-certreq -alias " + alias
579                + " -file " + alias + ".req " + options);
580    }
581
582    static OutputAnalyzer exportcert(String alias) {
583        return kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
584    }
585
586    static OutputAnalyzer gencert(String relation, String options) {
587        int pos = relation.indexOf("-");
588        String issuer = relation.substring(0, pos);
589        String subject = relation.substring(pos + 1);
590        return kt(" -gencert -alias " + issuer + " -infile " + subject
591                + ".req -outfile " + relation + ".cert " + options);
592    }
593
594    static void saveStore() throws IOException {
595        System.out.println("---------------------------------------------");
596        System.out.println("$ cp ks ks2");
597        Files.copy(Paths.get("ks"), Paths.get("ks2"),
598                StandardCopyOption.REPLACE_EXISTING);
599    }
600
601    static void reStore() throws IOException {
602        System.out.println("---------------------------------------------");
603        System.out.println("$ cp ks2 ks");
604        Files.copy(Paths.get("ks2"), Paths.get("ks"),
605                StandardCopyOption.REPLACE_EXISTING);
606    }
607
608    static void rm(String s) throws IOException {
609        System.out.println("---------------------------------------------");
610        System.out.println("$ rm " + s);
611        Files.deleteIfExists(Paths.get(s));
612    }
613}
614