1 /*
2  * Copyright (c) 2003, 2018, 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 import com.sun.net.httpserver.*;
25 
26 import java.io.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.math.BigInteger;
33 import java.net.InetSocketAddress;
34 import java.nio.file.Files;
35 import java.nio.file.Paths;
36 import java.security.KeyStore;
37 import java.security.PrivateKey;
38 import java.security.Signature;
39 import java.security.cert.Certificate;
40 import java.security.cert.CertificateException;
41 import java.security.cert.CertificateFactory;
42 import java.security.cert.X509Certificate;
43 import java.time.Instant;
44 import java.time.temporal.ChronoUnit;
45 import java.util.*;
46 import java.util.jar.JarEntry;
47 import java.util.jar.JarFile;
48 
49 import jdk.test.lib.SecurityTools;
50 import jdk.test.lib.process.OutputAnalyzer;
51 import jdk.test.lib.util.JarUtils;
52 import sun.security.pkcs.ContentInfo;
53 import sun.security.pkcs.PKCS7;
54 import sun.security.pkcs.PKCS9Attribute;
55 import sun.security.pkcs.SignerInfo;
56 import sun.security.timestamp.TimestampToken;
57 import sun.security.util.DerOutputStream;
58 import sun.security.util.DerValue;
59 import sun.security.util.ObjectIdentifier;
60 import sun.security.x509.AlgorithmId;
61 import sun.security.x509.X500Name;
62 
63 /*
64  * @test
65  * @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8180289
66  * @summary checking response of timestamp
67  * @modules java.base/sun.security.pkcs
68  *          java.base/sun.security.timestamp
69  *          java.base/sun.security.x509
70  *          java.base/sun.security.util
71  *          java.base/sun.security.tools.keytool
72  * @library /lib/testlibrary
73  * @library /test/lib
74  * @build jdk.test.lib.util.JarUtils
75  *        jdk.test.lib.SecurityTools
76  *        jdk.test.lib.Utils
77  *        jdk.test.lib.Asserts
78  *        jdk.test.lib.JDKToolFinder
79  *        jdk.test.lib.JDKToolLauncher
80  *        jdk.test.lib.Platform
81  *        jdk.test.lib.process.*
82  * @compile -XDignore.symbol.file TimestampCheck.java
83  * @run main/timeout=600 TimestampCheck
84  */
85 public class TimestampCheck {
86 
87     static final String defaultPolicyId = "2.3.4";
88     static String host = null;
89 
90     static class Handler implements HttpHandler, AutoCloseable {
91 
92         private final HttpServer httpServer;
93         private final String keystore;
94 
95         @Override
handle(HttpExchange t)96         public void handle(HttpExchange t) throws IOException {
97             int len = 0;
98             for (String h: t.getRequestHeaders().keySet()) {
99                 if (h.equalsIgnoreCase("Content-length")) {
100                     len = Integer.valueOf(t.getRequestHeaders().get(h).get(0));
101                 }
102             }
103             byte[] input = new byte[len];
104             t.getRequestBody().read(input);
105 
106             try {
107                 String path = t.getRequestURI().getPath().substring(1);
108                 byte[] output = sign(input, path);
109                 Headers out = t.getResponseHeaders();
110                 out.set("Content-Type", "application/timestamp-reply");
111 
112                 t.sendResponseHeaders(200, output.length);
113                 OutputStream os = t.getResponseBody();
114                 os.write(output);
115             } catch (Exception e) {
116                 e.printStackTrace();
117                 t.sendResponseHeaders(500, 0);
118             }
119             t.close();
120         }
121 
122         /**
123          * @param input The data to sign
124          * @param path different cases to simulate, impl on URL path
125          * @returns the signed
126          */
sign(byte[] input, String path)127         byte[] sign(byte[] input, String path) throws Exception {
128 
129             DerValue value = new DerValue(input);
130             System.out.println("#\n# Incoming Request\n===================");
131             System.out.println("# Version: " + value.data.getInteger());
132             DerValue messageImprint = value.data.getDerValue();
133             AlgorithmId aid = AlgorithmId.parse(
134                     messageImprint.data.getDerValue());
135             System.out.println("# AlgorithmId: " + aid);
136 
137             ObjectIdentifier policyId = new ObjectIdentifier(defaultPolicyId);
138             BigInteger nonce = null;
139             while (value.data.available() > 0) {
140                 DerValue v = value.data.getDerValue();
141                 if (v.tag == DerValue.tag_Integer) {
142                     nonce = v.getBigInteger();
143                     System.out.println("# nonce: " + nonce);
144                 } else if (v.tag == DerValue.tag_Boolean) {
145                     System.out.println("# certReq: " + v.getBoolean());
146                 } else if (v.tag == DerValue.tag_ObjectId) {
147                     policyId = v.getOID();
148                     System.out.println("# PolicyID: " + policyId);
149                 }
150             }
151 
152             System.out.println("#\n# Response\n===================");
153             KeyStore ks = KeyStore.getInstance(
154                     new File(keystore), "changeit".toCharArray());
155 
156             // If path starts with "ts", use the TSA it points to.
157             // Otherwise, always use "ts".
158             String alias = path.startsWith("ts") ? path : "ts";
159 
160             if (path.equals("diffpolicy")) {
161                 policyId = new ObjectIdentifier(defaultPolicyId);
162             }
163 
164             DerOutputStream statusInfo = new DerOutputStream();
165             statusInfo.putInteger(0);
166 
167             AlgorithmId[] algorithms = {aid};
168             Certificate[] chain = ks.getCertificateChain(alias);
169             X509Certificate[] signerCertificateChain;
170             X509Certificate signer = (X509Certificate)chain[0];
171 
172             if (path.equals("fullchain")) {   // Only case 5 uses full chain
173                 signerCertificateChain = new X509Certificate[chain.length];
174                 for (int i=0; i<chain.length; i++) {
175                     signerCertificateChain[i] = (X509Certificate)chain[i];
176                 }
177             } else if (path.equals("nocert")) {
178                 signerCertificateChain = new X509Certificate[0];
179             } else {
180                 signerCertificateChain = new X509Certificate[1];
181                 signerCertificateChain[0] = (X509Certificate)chain[0];
182             }
183 
184             DerOutputStream tst = new DerOutputStream();
185 
186             tst.putInteger(1);
187             tst.putOID(policyId);
188 
189             if (!path.equals("baddigest") && !path.equals("diffalg")) {
190                 tst.putDerValue(messageImprint);
191             } else {
192                 byte[] data = messageImprint.toByteArray();
193                 if (path.equals("diffalg")) {
194                     data[6] = (byte)0x01;
195                 } else {
196                     data[data.length-1] = (byte)0x01;
197                     data[data.length-2] = (byte)0x02;
198                     data[data.length-3] = (byte)0x03;
199                 }
200                 tst.write(data);
201             }
202 
203             tst.putInteger(1);
204 
205             Instant instant = Instant.now();
206             if (path.equals("tsold")) {
207                 instant = instant.minus(20, ChronoUnit.DAYS);
208             }
209             tst.putGeneralizedTime(Date.from(instant));
210 
211             if (path.equals("diffnonce")) {
212                 tst.putInteger(1234);
213             } else if (path.equals("nononce")) {
214                 // no noce
215             } else {
216                 tst.putInteger(nonce);
217             }
218 
219             DerOutputStream tstInfo = new DerOutputStream();
220             tstInfo.write(DerValue.tag_Sequence, tst);
221 
222             DerOutputStream tstInfo2 = new DerOutputStream();
223             tstInfo2.putOctetString(tstInfo.toByteArray());
224 
225             // Always use the same algorithm at timestamp signing
226             // so it is different from the hash algorithm.
227             Signature sig = Signature.getInstance("SHA1withRSA");
228             sig.initSign((PrivateKey)(ks.getKey(
229                     alias, "changeit".toCharArray())));
230             sig.update(tstInfo.toByteArray());
231 
232             ContentInfo contentInfo = new ContentInfo(new ObjectIdentifier(
233                     "1.2.840.113549.1.9.16.1.4"),
234                     new DerValue(tstInfo2.toByteArray()));
235 
236             System.out.println("# Signing...");
237             System.out.println("# " + new X500Name(signer
238                     .getIssuerX500Principal().getName()));
239             System.out.println("# " + signer.getSerialNumber());
240 
241             SignerInfo signerInfo = new SignerInfo(
242                     new X500Name(signer.getIssuerX500Principal().getName()),
243                     signer.getSerialNumber(),
244                     AlgorithmId.get("SHA-1"), AlgorithmId.get("RSA"), sig.sign());
245 
246             SignerInfo[] signerInfos = {signerInfo};
247             PKCS7 p7 = new PKCS7(algorithms, contentInfo,
248                     signerCertificateChain, signerInfos);
249             ByteArrayOutputStream p7out = new ByteArrayOutputStream();
250             p7.encodeSignedData(p7out);
251 
252             DerOutputStream response = new DerOutputStream();
253             response.write(DerValue.tag_Sequence, statusInfo);
254             response.putDerValue(new DerValue(p7out.toByteArray()));
255 
256             DerOutputStream out = new DerOutputStream();
257             out.write(DerValue.tag_Sequence, response);
258 
259             return out.toByteArray();
260         }
261 
Handler(HttpServer httpServer, String keystore)262         private Handler(HttpServer httpServer, String keystore) {
263             this.httpServer = httpServer;
264             this.keystore = keystore;
265         }
266 
267         /**
268          * Initialize TSA instance.
269          *
270          * Extended Key Info extension of certificate that is used for
271          * signing TSA responses should contain timeStamping value.
272          */
init(int port, String keystore)273         static Handler init(int port, String keystore) throws IOException {
274             HttpServer httpServer = HttpServer.create(
275                     new InetSocketAddress(port), 0);
276             Handler tsa = new Handler(httpServer, keystore);
277             httpServer.createContext("/", tsa);
278             return tsa;
279         }
280 
281         /**
282          * Start TSA service.
283          */
start()284         void start() {
285             httpServer.start();
286         }
287 
288         /**
289          * Stop TSA service.
290          */
stop()291         void stop() {
292             httpServer.stop(0);
293         }
294 
295         /**
296          * Return server port number.
297          */
getPort()298         int getPort() {
299             return httpServer.getAddress().getPort();
300         }
301 
302         @Override
close()303         public void close() throws Exception {
304             stop();
305         }
306     }
307 
main(String[] args)308     public static void main(String[] args) throws Throwable {
309 
310         try (Handler tsa = Handler.init(0, "ks");) {
311             tsa.start();
312             int port = tsa.getPort();
313             host = "http://localhost:" + port + "/";
314 
315             if (args.length == 0) {         // Run this test
316 
317                 prepare();
318 
319                 sign("normal")
320                         .shouldNotContain("Warning")
321                         .shouldContain("The signer certificate will expire on")
322                         .shouldContain("The timestamp will expire on")
323                         .shouldHaveExitValue(0);
324 
325                 verify("normal.jar")
326                         .shouldNotContain("Warning")
327                         .shouldHaveExitValue(0);
328 
329                 verify("normal.jar", "-verbose")
330                         .shouldNotContain("Warning")
331                         .shouldContain("The signer certificate will expire on")
332                         .shouldContain("The timestamp will expire on")
333                         .shouldHaveExitValue(0);
334 
335                 // Simulate signing at a previous date:
336                 // 1. tsold will create a timestamp of 20 days ago.
337                 // 2. oldsigner expired 10 days ago.
338                 signVerbose("tsold", "unsigned.jar", "tsold.jar", "oldsigner")
339                         .shouldNotContain("Warning")
340                         .shouldMatch("signer certificate expired on .*. "
341                                 + "However, the JAR will be valid")
342                         .shouldHaveExitValue(0);
343 
344                 // It verifies perfectly.
345                 verify("tsold.jar", "-verbose", "-certs")
346                         .shouldNotContain("Warning")
347                         .shouldMatch("signer certificate expired on .*. "
348                                 + "However, the JAR will be valid")
349                         .shouldHaveExitValue(0);
350 
351                 // No timestamp
352                 signVerbose(null, "unsigned.jar", "none.jar", "signer")
353                         .shouldContain("is not timestamped")
354                         .shouldContain("The signer certificate will expire on")
355                         .shouldHaveExitValue(0);
356 
357                 verify("none.jar", "-verbose")
358                         .shouldContain("do not include a timestamp")
359                         .shouldContain("The signer certificate will expire on")
360                         .shouldHaveExitValue(0);
361 
362                 // Error cases
363 
364                 signVerbose(null, "unsigned.jar", "badku.jar", "badku")
365                         .shouldContain("KeyUsage extension doesn't allow code signing")
366                         .shouldHaveExitValue(8);
367                 checkBadKU("badku.jar");
368 
369                 // 8180289: unvalidated TSA cert chain
370                 sign("tsnoca")
371                         .shouldContain("The TSA certificate chain is invalid. "
372                                 + "Reason: Path does not chain with any of the trust anchors")
373                         .shouldHaveExitValue(64);
374 
375                 verify("tsnoca.jar", "-verbose", "-certs")
376                         .shouldHaveExitValue(64)
377                         .shouldContain("jar verified")
378                         .shouldContain("Invalid TSA certificate chain: "
379                                 + "Path does not chain with any of the trust anchors")
380                         .shouldContain("TSA certificate chain is invalid."
381                                 + " Reason: Path does not chain with any of the trust anchors");
382 
383                 sign("nononce")
384                         .shouldContain("Nonce missing in timestamp token")
385                         .shouldHaveExitValue(1);
386                 sign("diffnonce")
387                         .shouldContain("Nonce changed in timestamp token")
388                         .shouldHaveExitValue(1);
389                 sign("baddigest")
390                         .shouldContain("Digest octets changed in timestamp token")
391                         .shouldHaveExitValue(1);
392                 sign("diffalg")
393                         .shouldContain("Digest algorithm not")
394                         .shouldHaveExitValue(1);
395 
396                 sign("fullchain")
397                         .shouldHaveExitValue(0);   // Success, 6543440 solved.
398 
399                 sign("tsbad1")
400                         .shouldContain("Certificate is not valid for timestamping")
401                         .shouldHaveExitValue(1);
402                 sign("tsbad2")
403                         .shouldContain("Certificate is not valid for timestamping")
404                         .shouldHaveExitValue(1);
405                 sign("tsbad3")
406                         .shouldContain("Certificate is not valid for timestamping")
407                         .shouldHaveExitValue(1);
408                 sign("nocert")
409                         .shouldContain("Certificate not included in timestamp token")
410                         .shouldHaveExitValue(1);
411 
412                 sign("policy", "-tsapolicyid",  "1.2.3")
413                         .shouldHaveExitValue(0);
414                 checkTimestamp("policy.jar", "1.2.3", "SHA-256");
415 
416                 sign("diffpolicy", "-tsapolicyid", "1.2.3")
417                         .shouldContain("TSAPolicyID changed in timestamp token")
418                         .shouldHaveExitValue(1);
419 
420                 sign("sha1alg", "-tsadigestalg", "SHA")
421                         .shouldHaveExitValue(0);
422                 checkTimestamp("sha1alg.jar", defaultPolicyId, "SHA-1");
423 
424                 sign("tsweak", "-digestalg", "MD5",
425                                 "-sigalg", "MD5withRSA", "-tsadigestalg", "MD5")
426                         .shouldHaveExitValue(68)
427                         .shouldContain("The timestamp is invalid. Without a valid timestamp")
428                         .shouldMatch("MD5.*-digestalg.*risk")
429                         .shouldMatch("MD5.*-tsadigestalg.*risk")
430                         .shouldMatch("MD5withRSA.*-sigalg.*risk");
431                 checkWeak("tsweak.jar");
432 
433                 signVerbose("tsweak", "unsigned.jar", "tsweak2.jar", "signer")
434                         .shouldHaveExitValue(64)
435                         .shouldContain("The timestamp is invalid. Without a valid timestamp")
436                         .shouldContain("TSA certificate chain is invalid");
437 
438                 // Weak timestamp is an error and jar treated unsigned
439                 verify("tsweak2.jar", "-verbose")
440                         .shouldHaveExitValue(16)
441                         .shouldContain("treated as unsigned")
442                         .shouldMatch("Timestamp.*512.*weak");
443 
444                 // Algorithm used in signing is weak
445                 signVerbose("normal", "unsigned.jar", "halfWeak.jar", "signer",
446                         "-digestalg", "MD5")
447                         .shouldContain("-digestalg option is considered a security risk")
448                         .shouldHaveExitValue(4);
449                 checkHalfWeak("halfWeak.jar");
450 
451                 // sign with DSA key
452                 signVerbose("normal", "unsigned.jar", "sign1.jar", "dsakey")
453                         .shouldHaveExitValue(0);
454 
455                 // sign with RSAkeysize < 1024
456                 signVerbose("normal", "sign1.jar", "sign2.jar", "weakkeysize")
457                         .shouldContain("Algorithm constraints check failed on keysize")
458                         .shouldHaveExitValue(4);
459                 checkMultiple("sign2.jar");
460 
461                 // 8191438: jarsigner should print when a timestamp will expire
462                 checkExpiration();
463 
464                 // When .SF or .RSA is missing or invalid
465                 checkMissingOrInvalidFiles("normal.jar");
466 
467                 if (Files.exists(Paths.get("ts2.cert"))) {
468                     checkInvalidTsaCertKeyUsage();
469                 }
470             } else {                        // Run as a standalone server
471                 System.out.println("TSA started at " + host
472                         + ". Press Enter to quit server");
473                 System.in.read();
474             }
475         }
476     }
477 
checkExpiration()478     private static void checkExpiration() throws Exception {
479 
480         // Warning when expired or expiring
481         signVerbose(null, "unsigned.jar", "expired.jar", "expired")
482                 .shouldContain("signer certificate has expired")
483                 .shouldHaveExitValue(4);
484         verify("expired.jar")
485                 .shouldContain("signer certificate has expired")
486                 .shouldHaveExitValue(4);
487         signVerbose(null, "unsigned.jar", "expiring.jar", "expiring")
488                 .shouldContain("signer certificate will expire within")
489                 .shouldHaveExitValue(0);
490         verify("expiring.jar")
491                 .shouldContain("signer certificate will expire within")
492                 .shouldHaveExitValue(0);
493         // Info for long
494         signVerbose(null, "unsigned.jar", "long.jar", "long")
495                 .shouldNotContain("signer certificate has expired")
496                 .shouldNotContain("signer certificate will expire within")
497                 .shouldContain("signer certificate will expire on")
498                 .shouldHaveExitValue(0);
499         verify("long.jar")
500                 .shouldNotContain("signer certificate has expired")
501                 .shouldNotContain("signer certificate will expire within")
502                 .shouldNotContain("The signer certificate will expire")
503                 .shouldHaveExitValue(0);
504         verify("long.jar", "-verbose")
505                 .shouldContain("The signer certificate will expire")
506                 .shouldHaveExitValue(0);
507 
508         // Both expired
509         signVerbose("tsexpired", "unsigned.jar",
510                 "tsexpired-expired.jar", "expired")
511                 .shouldContain("The signer certificate has expired.")
512                 .shouldContain("The timestamp has expired.")
513                 .shouldHaveExitValue(4);
514         verify("tsexpired-expired.jar")
515                 .shouldContain("signer certificate has expired")
516                 .shouldContain("timestamp has expired.")
517                 .shouldHaveExitValue(4);
518 
519         // TS expired but signer still good
520         signVerbose("tsexpired", "unsigned.jar",
521                 "tsexpired-long.jar", "long")
522                 .shouldContain("The timestamp expired on")
523                 .shouldHaveExitValue(0);
524         verify("tsexpired-long.jar")
525                 .shouldMatch("timestamp expired on.*However, the JAR will be valid")
526                 .shouldNotContain("Error")
527                 .shouldHaveExitValue(0);
528 
529         signVerbose("tsexpired", "unsigned.jar",
530                 "tsexpired-ca.jar", "ca")
531                 .shouldContain("The timestamp has expired.")
532                 .shouldHaveExitValue(4);
533         verify("tsexpired-ca.jar")
534                 .shouldNotContain("timestamp has expired")
535                 .shouldNotContain("Error")
536                 .shouldHaveExitValue(0);
537 
538         // Warning when expiring
539         sign("tsexpiring")
540                 .shouldContain("timestamp will expire within")
541                 .shouldHaveExitValue(0);
542         verify("tsexpiring.jar")
543                 .shouldContain("timestamp will expire within")
544                 .shouldNotContain("still valid")
545                 .shouldHaveExitValue(0);
546 
547         signVerbose("tsexpiring", "unsigned.jar",
548                 "tsexpiring-ca.jar", "ca")
549                 .shouldContain("self-signed")
550                 .stderrShouldNotMatch("The.*expir")
551                 .shouldHaveExitValue(4); // self-signed
552         verify("tsexpiring-ca.jar")
553                 .stderrShouldNotMatch("The.*expir")
554                 .shouldHaveExitValue(0);
555 
556         signVerbose("tsexpiringsoon", "unsigned.jar",
557                 "tsexpiringsoon-long.jar", "long")
558                 .shouldContain("The timestamp will expire")
559                 .shouldHaveExitValue(0);
560         verify("tsexpiringsoon-long.jar")
561                 .shouldMatch("timestamp will expire.*However, the JAR will be valid until")
562                 .shouldHaveExitValue(0);
563 
564         // Info for long
565         sign("tslong")
566                 .shouldNotContain("timestamp has expired")
567                 .shouldNotContain("timestamp will expire within")
568                 .shouldContain("timestamp will expire on")
569                 .shouldContain("signer certificate will expire on")
570                 .shouldHaveExitValue(0);
571         verify("tslong.jar")
572                 .shouldNotContain("timestamp has expired")
573                 .shouldNotContain("timestamp will expire within")
574                 .shouldNotContain("timestamp will expire on")
575                 .shouldNotContain("signer certificate will expire on")
576                 .shouldHaveExitValue(0);
577         verify("tslong.jar", "-verbose")
578                 .shouldContain("timestamp will expire on")
579                 .shouldContain("signer certificate will expire on")
580                 .shouldHaveExitValue(0);
581     }
582 
checkInvalidTsaCertKeyUsage()583     private static void checkInvalidTsaCertKeyUsage() throws Exception {
584 
585         // Hack: Rewrite the TSA cert inside normal.jar into ts2.jar.
586 
587         // Both the cert and the serial number must be rewritten.
588         byte[] tsCert = Files.readAllBytes(Paths.get("ts.cert"));
589         byte[] ts2Cert = Files.readAllBytes(Paths.get("ts2.cert"));
590         byte[] tsSerial = getCert(tsCert)
591                 .getSerialNumber().toByteArray();
592         byte[] ts2Serial = getCert(ts2Cert)
593                 .getSerialNumber().toByteArray();
594 
595         byte[] oldBlock;
596         try (JarFile normal = new JarFile("normal.jar")) {
597             oldBlock = normal.getInputStream(
598                     normal.getJarEntry("META-INF/SIGNER.RSA")).readAllBytes();
599         }
600 
601         JarUtils.updateJar("normal.jar", "ts2.jar",
602                 Map.of("META-INF/SIGNER.RSA",
603                         updateBytes(updateBytes(oldBlock, tsCert, ts2Cert),
604                                 tsSerial, ts2Serial)));
605 
606         verify("ts2.jar", "-verbose", "-certs")
607                 .shouldHaveExitValue(64)
608                 .shouldContain("jar verified")
609                 .shouldContain("Invalid TSA certificate chain: Extended key usage does not permit use for TSA server");
610     }
611 
getCert(byte[] data)612     public static X509Certificate getCert(byte[] data)
613             throws CertificateException, IOException {
614         return (X509Certificate)
615                 CertificateFactory.getInstance("X.509")
616                         .generateCertificate(new ByteArrayInputStream(data));
617     }
618 
updateBytes(byte[] old, byte[] from, byte[] to)619     private static byte[] updateBytes(byte[] old, byte[] from, byte[] to) {
620         int pos = 0;
621         while (true) {
622             if (pos + from.length > old.length) {
623                 return null;
624             }
625             if (Arrays.equals(Arrays.copyOfRange(old, pos, pos+from.length), from)) {
626                 byte[] result = old.clone();
627                 System.arraycopy(to, 0, result, pos, from.length);
628                 return result;
629             }
630             pos++;
631         }
632     }
633 
checkMissingOrInvalidFiles(String s)634     private static void checkMissingOrInvalidFiles(String s)
635             throws Throwable {
636 
637         JarUtils.updateJar(s, "1.jar", Map.of("META-INF/SIGNER.SF", Boolean.FALSE));
638         verify("1.jar", "-verbose")
639                 .shouldHaveExitValue(16)
640                 .shouldContain("treated as unsigned")
641                 .shouldContain("Missing signature-related file META-INF/SIGNER.SF");
642         JarUtils.updateJar(s, "2.jar", Map.of("META-INF/SIGNER.RSA", Boolean.FALSE));
643         verify("2.jar", "-verbose")
644                 .shouldHaveExitValue(16)
645                 .shouldContain("treated as unsigned")
646                 .shouldContain("Missing block file for signature-related file META-INF/SIGNER.SF");
647         JarUtils.updateJar(s, "3.jar", Map.of("META-INF/SIGNER.SF", "dummy"));
648         verify("3.jar", "-verbose")
649                 .shouldHaveExitValue(16)
650                 .shouldContain("treated as unsigned")
651                 .shouldContain("Unparsable signature-related file META-INF/SIGNER.SF");
652         JarUtils.updateJar(s, "4.jar", Map.of("META-INF/SIGNER.RSA", "dummy"));
653         verify("4.jar", "-verbose")
654                 .shouldHaveExitValue(16)
655                 .shouldContain("treated as unsigned")
656                 .shouldContain("Unparsable signature-related file META-INF/SIGNER.RSA");
657     }
658 
jarsigner(List<String> extra)659     static OutputAnalyzer jarsigner(List<String> extra)
660             throws Exception {
661         List<String> args = new ArrayList<>(
662                 List.of("-keystore", "ks", "-storepass", "changeit"));
663         args.addAll(extra);
664         return SecurityTools.jarsigner(args);
665     }
666 
verify(String file, String... extra)667     static OutputAnalyzer verify(String file, String... extra)
668             throws Exception {
669         List<String> args = new ArrayList<>();
670         args.add("-verify");
671         args.add("-strict");
672         args.add(file);
673         args.addAll(Arrays.asList(extra));
674         return jarsigner(args);
675     }
676 
checkBadKU(String file)677     static void checkBadKU(String file) throws Exception {
678         verify(file)
679                 .shouldHaveExitValue(16)
680                 .shouldContain("treated as unsigned")
681                 .shouldContain("re-run jarsigner with debug enabled");
682         verify(file, "-verbose")
683                 .shouldHaveExitValue(16)
684                 .shouldContain("Signed by")
685                 .shouldContain("treated as unsigned")
686                 .shouldContain("re-run jarsigner with debug enabled");
687         verify(file, "-J-Djava.security.debug=jar")
688                 .shouldHaveExitValue(16)
689                 .shouldContain("SignatureException: Key usage restricted")
690                 .shouldContain("treated as unsigned")
691                 .shouldContain("re-run jarsigner with debug enabled");
692     }
693 
checkWeak(String file)694     static void checkWeak(String file) throws Exception {
695         verify(file)
696                 .shouldHaveExitValue(16)
697                 .shouldContain("treated as unsigned")
698                 .shouldMatch("weak algorithm that is now disabled.")
699                 .shouldMatch("Re-run jarsigner with the -verbose option for more details");
700         verify(file, "-verbose")
701                 .shouldHaveExitValue(16)
702                 .shouldContain("treated as unsigned")
703                 .shouldMatch("weak algorithm that is now disabled by")
704                 .shouldMatch("Digest algorithm: .*weak")
705                 .shouldMatch("Signature algorithm: .*weak")
706                 .shouldMatch("Timestamp digest algorithm: .*weak")
707                 .shouldNotMatch("Timestamp signature algorithm: .*weak.*weak")
708                 .shouldMatch("Timestamp signature algorithm: .*key.*weak");
709         verify(file, "-J-Djava.security.debug=jar")
710                 .shouldHaveExitValue(16)
711                 .shouldMatch("SignatureException:.*disabled");
712 
713         // For 8171319: keytool should print out warnings when reading or
714         //              generating cert/cert req using weak algorithms.
715         // Must call keytool the command, otherwise doPrintCert() might not
716         // be able to reset "jdk.certpath.disabledAlgorithms".
717         String sout = SecurityTools.keytool("-printcert -jarfile " + file)
718                 .stderrShouldContain("The TSA certificate uses a 512-bit RSA key" +
719                         " which is considered a security risk.")
720                 .getStdout();
721         if (sout.indexOf("weak", sout.indexOf("Timestamp:")) < 0) {
722             throw new RuntimeException("timestamp not weak: " + sout);
723         }
724     }
725 
checkHalfWeak(String file)726     static void checkHalfWeak(String file) throws Exception {
727         verify(file)
728                 .shouldHaveExitValue(16)
729                 .shouldContain("treated as unsigned")
730                 .shouldMatch("weak algorithm that is now disabled.")
731                 .shouldMatch("Re-run jarsigner with the -verbose option for more details");
732         verify(file, "-verbose")
733                 .shouldHaveExitValue(16)
734                 .shouldContain("treated as unsigned")
735                 .shouldMatch("weak algorithm that is now disabled by")
736                 .shouldMatch("Digest algorithm: .*weak")
737                 .shouldNotMatch("Signature algorithm: .*weak")
738                 .shouldNotMatch("Timestamp digest algorithm: .*weak")
739                 .shouldNotMatch("Timestamp signature algorithm: .*weak.*weak")
740                 .shouldNotMatch("Timestamp signature algorithm: .*key.*weak");
741      }
742 
checkMultiple(String file)743     static void checkMultiple(String file) throws Exception {
744         verify(file)
745                 .shouldHaveExitValue(0)
746                 .shouldContain("jar verified");
747         verify(file, "-verbose", "-certs")
748                 .shouldHaveExitValue(0)
749                 .shouldContain("jar verified")
750                 .shouldMatch("X.509.*CN=dsakey")
751                 .shouldNotMatch("X.509.*CN=weakkeysize")
752                 .shouldMatch("Signed by .*CN=dsakey")
753                 .shouldMatch("Signed by .*CN=weakkeysize")
754                 .shouldMatch("Signature algorithm: .*key.*weak");
755      }
756 
checkTimestamp(String file, String policyId, String digestAlg)757     static void checkTimestamp(String file, String policyId, String digestAlg)
758             throws Exception {
759         try (JarFile jf = new JarFile(file)) {
760             JarEntry je = jf.getJarEntry("META-INF/SIGNER.RSA");
761             try (InputStream is = jf.getInputStream(je)) {
762                 byte[] content = is.readAllBytes();
763                 PKCS7 p7 = new PKCS7(content);
764                 SignerInfo[] si = p7.getSignerInfos();
765                 if (si == null || si.length == 0) {
766                     throw new Exception("Not signed");
767                 }
768                 PKCS9Attribute p9 = si[0].getUnauthenticatedAttributes()
769                         .getAttribute(PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
770                 PKCS7 tsToken = new PKCS7((byte[]) p9.getValue());
771                 TimestampToken tt =
772                         new TimestampToken(tsToken.getContentInfo().getData());
773                 if (!tt.getHashAlgorithm().toString().equals(digestAlg)) {
774                     throw new Exception("Digest alg different");
775                 }
776                 if (!tt.getPolicyID().equals(policyId)) {
777                     throw new Exception("policyId different");
778                 }
779             }
780         }
781     }
782 
783     static int which = 0;
784 
785     /**
786      * Sign with a TSA path. Always use alias "signer" to sign "unsigned.jar".
787      * The signed jar name is always path.jar.
788      *
789      * @param extra more args given to jarsigner
790      */
sign(String path, String... extra)791     static OutputAnalyzer sign(String path, String... extra)
792             throws Exception {
793         return signVerbose(
794                 path,
795                 "unsigned.jar",
796                 path + ".jar",
797                 "signer",
798                 extra);
799     }
800 
signVerbose( String path, String oldJar, String newJar, String alias, String...extra)801     static OutputAnalyzer signVerbose(
802             String path,    // TSA URL path
803             String oldJar,
804             String newJar,
805             String alias,   // signer
806             String...extra) throws Exception {
807         which++;
808         System.out.println("\n>> Test #" + which);
809         List<String> args = new ArrayList<>(List.of(
810                 "-strict", "-verbose", "-debug", "-signedjar", newJar, oldJar, alias));
811         if (path != null) {
812             args.add("-tsa");
813             args.add(host + path);
814         }
815         args.addAll(Arrays.asList(extra));
816         return jarsigner(args);
817     }
818 
prepare()819     static void prepare() throws Exception {
820         JarUtils.createJar("unsigned.jar", "A");
821         Files.deleteIfExists(Paths.get("ks"));
822         keytool("-alias signer -genkeypair -ext bc -dname CN=signer");
823         keytool("-alias oldsigner -genkeypair -dname CN=oldsigner");
824         keytool("-alias dsakey -genkeypair -keyalg DSA -dname CN=dsakey");
825         keytool("-alias weakkeysize -genkeypair -keysize 512 -dname CN=weakkeysize");
826         keytool("-alias badku -genkeypair -dname CN=badku");
827         keytool("-alias ts -genkeypair -dname CN=ts");
828         keytool("-alias tsold -genkeypair -dname CN=tsold");
829         keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsweak");
830         keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1");
831         keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2");
832         keytool("-alias tsbad3 -genkeypair -dname CN=tsbad3");
833         keytool("-alias tsnoca -genkeypair -dname CN=tsnoca");
834 
835         keytool("-alias expired -genkeypair -dname CN=expired");
836         keytool("-alias expiring -genkeypair -dname CN=expiring");
837         keytool("-alias long -genkeypair -dname CN=long");
838         keytool("-alias tsexpired -genkeypair -dname CN=tsexpired");
839         keytool("-alias tsexpiring -genkeypair -dname CN=tsexpiring");
840         keytool("-alias tsexpiringsoon -genkeypair -dname CN=tsexpiringsoon");
841         keytool("-alias tslong -genkeypair -dname CN=tslong");
842 
843         // tsnoca's issuer will be removed from keystore later
844         keytool("-alias ca -genkeypair -ext bc -dname CN=CA");
845         gencert("tsnoca", "-ext eku:critical=ts");
846         keytool("-delete -alias ca");
847         keytool("-alias ca -genkeypair -ext bc -dname CN=CA -startdate -40d");
848 
849         gencert("signer");
850         gencert("oldsigner", "-startdate -30d -validity 20");
851         gencert("dsakey");
852         gencert("weakkeysize");
853         gencert("badku", "-ext ku:critical=keyAgreement");
854         gencert("ts", "-ext eku:critical=ts -validity 500");
855 
856         gencert("expired", "-validity 10 -startdate -12d");
857         gencert("expiring", "-validity 178");
858         gencert("long", "-validity 182");
859         gencert("tsexpired", "-ext eku:critical=ts -validity 10 -startdate -12d");
860         gencert("tsexpiring", "-ext eku:critical=ts -validity 364");
861         gencert("tsexpiringsoon", "-ext eku:critical=ts -validity 170"); // earlier than expiring
862         gencert("tslong", "-ext eku:critical=ts -validity 367");
863 
864 
865         for (int i = 0; i < 5; i++) {
866             // Issue another cert for "ts" with a different EKU.
867             // Length might be different because serial number is
868             // random. Try several times until a cert with the same
869             // length is generated so we can substitute ts.cert
870             // embedded in the PKCS7 block with ts2.cert.
871             // If cannot create one, related test will be ignored.
872             keytool("-gencert -alias ca -infile ts.req -outfile ts2.cert " +
873                     "-ext eku:critical=1.3.6.1.5.5.7.3.9");
874             if (Files.size(Paths.get("ts.cert")) != Files.size(Paths.get("ts2.cert"))) {
875                 Files.delete(Paths.get("ts2.cert"));
876                 System.out.println("Warning: cannot create same length");
877             } else {
878                 break;
879             }
880         }
881 
882         gencert("tsold", "-ext eku:critical=ts -startdate -40d -validity 500");
883 
884         gencert("tsweak", "-ext eku:critical=ts");
885         gencert("tsbad1");
886         gencert("tsbad2", "-ext eku=ts");
887         gencert("tsbad3", "-ext eku:critical=cs");
888     }
889 
gencert(String alias, String... extra)890     static void gencert(String alias, String... extra) throws Exception {
891         keytool("-alias " + alias + " -certreq -file " + alias + ".req");
892         String genCmd = "-gencert -alias ca -infile " +
893                 alias + ".req -outfile " + alias + ".cert";
894         for (String s : extra) {
895             genCmd += " " + s;
896         }
897         keytool(genCmd);
898         keytool("-alias " + alias + " -importcert -file " + alias + ".cert");
899     }
900 
keytool(String cmd)901     static void keytool(String cmd) throws Exception {
902         cmd = "-keystore ks -storepass changeit -keypass changeit " +
903                 "-keyalg rsa -validity 200 " + cmd;
904         sun.security.tools.keytool.Main.main(cmd.split(" "));
905     }
906 }
907