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