21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 |
23 |
24 /* |
24 /* |
25 * @test |
25 * @test |
26 * @summary This test is used to verify the compatibility on jarsigner cross |
26 * @bug 8217375 |
|
27 * @summary This test is used to verify the compatibility of jarsigner across |
27 * different JDK releases. It also can be used to check jar signing (w/ |
28 * different JDK releases. It also can be used to check jar signing (w/ |
28 * and w/o TSA) and verifying on some specific key algorithms and digest |
29 * and w/o TSA) and to verify some specific signing and digest algorithms. |
29 * algorithms. |
30 * Note that this is a manual test. For more details about the test and |
30 * Note that, this is a manual test. For more details about the test and |
31 * its usages, please look through the README. |
31 * its usages, please look through README. |
|
32 * |
32 * |
33 * @modules java.base/sun.security.pkcs |
33 * @library /test/lib ../warnings |
34 * java.base/sun.security.timestamp |
|
35 * java.base/sun.security.tools.keytool |
|
36 * java.base/sun.security.util |
|
37 * java.base/sun.security.x509 |
|
38 * @library /test/lib /lib/testlibrary ../warnings |
|
39 * @compile -source 1.7 -target 1.7 JdkUtils.java |
34 * @compile -source 1.7 -target 1.7 JdkUtils.java |
40 * @run main/manual/othervm Compatibility |
35 * @run main/manual/othervm Compatibility |
41 */ |
36 */ |
42 |
37 |
|
38 import static java.nio.charset.StandardCharsets.UTF_8; |
|
39 |
43 import java.io.BufferedReader; |
40 import java.io.BufferedReader; |
44 import java.io.File; |
41 import java.io.File; |
|
42 import java.io.FileOutputStream; |
45 import java.io.FileReader; |
43 import java.io.FileReader; |
46 import java.io.FileWriter; |
44 import java.io.FileWriter; |
47 import java.io.IOException; |
45 import java.io.IOException; |
|
46 import java.io.OutputStream; |
48 import java.io.PrintStream; |
47 import java.io.PrintStream; |
|
48 import java.nio.file.Files; |
|
49 import java.nio.file.Path; |
49 import java.text.DateFormat; |
50 import java.text.DateFormat; |
50 import java.text.SimpleDateFormat; |
51 import java.text.SimpleDateFormat; |
51 import java.util.ArrayList; |
52 import java.util.ArrayList; |
|
53 import java.util.Arrays; |
52 import java.util.Calendar; |
54 import java.util.Calendar; |
53 import java.util.Date; |
55 import java.util.Date; |
54 import java.util.HashMap; |
56 import java.util.HashMap; |
55 import java.util.HashSet; |
57 import java.util.HashSet; |
56 import java.util.List; |
58 import java.util.List; |
|
59 import java.util.Locale; |
57 import java.util.Map; |
60 import java.util.Map; |
58 import java.util.Set; |
61 import java.util.Set; |
59 import java.util.concurrent.TimeUnit; |
62 import java.util.concurrent.TimeUnit; |
60 import java.util.regex.Matcher; |
63 import java.util.function.Consumer; |
61 import java.util.regex.Pattern; |
64 import java.util.function.Function; |
|
65 import java.util.jar.Attributes.Name; |
|
66 import java.util.jar.Manifest; |
|
67 import java.util.stream.Collectors; |
|
68 import java.util.stream.IntStream; |
62 |
69 |
63 import jdk.test.lib.process.OutputAnalyzer; |
70 import jdk.test.lib.process.OutputAnalyzer; |
64 import jdk.test.lib.process.ProcessTools; |
71 import jdk.test.lib.process.ProcessTools; |
65 import jdk.test.lib.util.JarUtils; |
72 import jdk.test.lib.util.JarUtils; |
66 |
73 |
67 public class Compatibility { |
74 public class Compatibility { |
68 |
75 |
69 private static final String TEST_JAR_NAME = "test.jar"; |
|
70 |
|
71 private static final String TEST_SRC = System.getProperty("test.src"); |
76 private static final String TEST_SRC = System.getProperty("test.src"); |
72 private static final String TEST_CLASSES = System.getProperty("test.classes"); |
77 private static final String TEST_CLASSES = System.getProperty("test.classes"); |
73 private static final String TEST_JDK = System.getProperty("test.jdk"); |
78 private static final String TEST_JDK = System.getProperty("test.jdk"); |
74 private static final String TEST_JARSIGNER = jarsignerPath(TEST_JDK); |
79 private static JdkInfo TEST_JDK_INFO; |
75 |
80 |
76 private static final String PROXY_HOST = System.getProperty("proxyHost"); |
81 private static final String PROXY_HOST = System.getProperty("proxyHost"); |
77 private static final String PROXY_PORT = System.getProperty("proxyPort", "80"); |
82 private static final String PROXY_PORT = System.getProperty("proxyPort", "80"); |
78 |
83 |
79 // An alternative security properties file. |
84 // An alternative security properties file. |
165 + "Please check the failed row(s) in report.html " |
191 + "Please check the failed row(s) in report.html " |
166 + "or failedReport.html."); |
192 + "or failedReport.html."); |
167 } |
193 } |
168 } |
194 } |
169 |
195 |
170 // Creates a jar file that contains an empty file. |
196 private static SignItem createJarFile(String jar, Manifest m, |
171 private static void createJar() throws IOException { |
197 String... files) throws IOException { |
172 String testFile = "test"; |
198 JarUtils.createJarFile(Path.of(jar), m, Path.of("."), |
173 new File(testFile).createNewFile(); |
199 Arrays.stream(files).map(Path::of).toArray(Path[]::new)); |
174 JarUtils.createJar(TEST_JAR_NAME, testFile); |
200 return SignItem.build() |
|
201 .signedJar(jar.replaceAll("[.]jar$", "")) |
|
202 .addContentFiles(Arrays.stream(files).collect(Collectors.toList())); |
|
203 } |
|
204 |
|
205 private static String createDummyFile(String name) throws IOException { |
|
206 if (name.contains("/")) new File(name).getParentFile().mkdir(); |
|
207 try (OutputStream fos = new FileOutputStream(name)) { |
|
208 fos.write(name.getBytes(UTF_8)); |
|
209 } |
|
210 return name; |
|
211 } |
|
212 |
|
213 // Creates one or more jar files to test |
|
214 private static List<SignItem> createJars() throws IOException { |
|
215 List<SignItem> jarList = new ArrayList<>(); |
|
216 |
|
217 Manifest m = new Manifest(); |
|
218 m.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0"); |
|
219 |
|
220 // creates a jar file that contains a dummy file |
|
221 jarList.add(createJarFile("test.jar", m, createDummyFile("dummy"))); |
|
222 |
|
223 if (TEST_COMPREHENSIVE_JAR_CONTENTS) { |
|
224 |
|
225 // empty jar file so that jarsigner will add a default manifest |
|
226 jarList.add(createJarFile("empty.jar", m)); |
|
227 |
|
228 // jar file that contains only an empty manifest with empty main |
|
229 // attributes (due to missing "Manifest-Version" header) |
|
230 JarUtils.createJar("nomainatts.jar"); |
|
231 jarList.add(SignItem.build().signedJar("nomainatts")); |
|
232 |
|
233 // creates a jar file that contains several files. |
|
234 jarList.add(createJarFile("files.jar", m, |
|
235 IntStream.range(1, 9).boxed().map(i -> { |
|
236 try { |
|
237 return createDummyFile("dummy" + i); |
|
238 } catch (IOException e) { |
|
239 throw new RuntimeException(e); |
|
240 } |
|
241 }).toArray(String[]::new) |
|
242 )); |
|
243 |
|
244 // forces a line break by exceeding the line width limit of 72 bytes |
|
245 // in the filename and hence manifest entry name |
|
246 jarList.add(createJarFile("longfilename.jar", m, |
|
247 createDummyFile("test".repeat(20)))); |
|
248 |
|
249 // another interesting case is with different digest algorithms |
|
250 // resulting in digests broken across line breaks onto continuation |
|
251 // lines. these however are set with the 'digestAlgs' option or |
|
252 // include all digest algorithms by default, see SignTwice.java. |
|
253 } |
|
254 |
|
255 return jarList; |
|
256 } |
|
257 |
|
258 // updates a signed jar file by adding another file |
|
259 private static List<SignItem> updateJar(SignItem prev) throws IOException { |
|
260 List<SignItem> jarList = new ArrayList<>(); |
|
261 |
|
262 // sign unmodified jar again |
|
263 Files.copy(Path.of(prev.signedJar + ".jar"), |
|
264 Path.of(prev.signedJar + "-signagainunmodified.jar")); |
|
265 jarList.add(SignItem.build(prev) |
|
266 .signedJar(prev.signedJar + "-signagainunmodified")); |
|
267 |
|
268 String oldJar = prev.signedJar; |
|
269 String newJar = oldJar + "-addfile"; |
|
270 String triggerUpdateFile = "addfile"; |
|
271 JarUtils.updateJar(oldJar + ".jar", newJar + ".jar", triggerUpdateFile); |
|
272 jarList.add(SignItem.build(prev).signedJar(newJar) |
|
273 .addContentFiles(Arrays.asList(triggerUpdateFile))); |
|
274 |
|
275 return jarList; |
175 } |
276 } |
176 |
277 |
177 // Creates a key store that includes a set of valid/expired certificates |
278 // Creates a key store that includes a set of valid/expired certificates |
178 // with various algorithms. |
279 // with various algorithms. |
179 private static List<CertInfo> createCertificates(List<JdkInfo> jdkInfoList) |
280 private static List<CertInfo> createCertificates(List<JdkInfo> jdkInfoList) |
180 throws Throwable { |
281 throws Throwable { |
181 List<CertInfo> certList = new ArrayList<CertInfo>(); |
282 List<CertInfo> certList = new ArrayList<>(); |
182 Set<String> expiredCertFilter = new HashSet<String>(); |
283 Set<String> expiredCertFilter = new HashSet<>(); |
183 |
284 |
184 for(JdkInfo jdkInfo : jdkInfoList) { |
285 for (JdkInfo jdkInfo : jdkInfoList) { |
185 for(String keyAlgorithm : KEY_ALGORITHMS) { |
286 for (String keyAlgorithm : keyAlgs()) { |
186 for(String digestAlgorithm : DIGEST_ALGORITHMS) { |
287 if (!jdkInfo.supportsKeyAlg(keyAlgorithm)) continue; |
187 for(int keySize : keySizes(keyAlgorithm)) { |
288 for (int keySize : keySizes(keyAlgorithm)) { |
|
289 for (String digestAlgorithm : digestAlgs()) { |
188 for(boolean expired : EXPIRED) { |
290 for(boolean expired : EXPIRED) { |
189 // It creates only one expired certificate for one |
291 // It creates only one expired certificate for one |
190 // key algorithm. |
292 // key algorithm. |
191 if (expired |
293 if (expired |
192 && !expiredCertFilter.add(keyAlgorithm)) { |
294 && !expiredCertFilter.add(keyAlgorithm)) { |
193 continue; |
295 continue; |
194 } |
296 } |
195 |
297 |
196 CertInfo certInfo = new CertInfo( |
298 CertInfo certInfo = new CertInfo( |
197 jdkInfo.version, |
299 jdkInfo, |
198 keyAlgorithm, |
300 keyAlgorithm, |
199 digestAlgorithm, |
301 digestAlgorithm, |
200 keySize, |
302 keySize, |
201 expired); |
303 expired); |
202 if (!certList.contains(certInfo)) { |
304 // If the signature algorithm is not supported by the |
203 String alias = createCertificate( |
305 // JDK, it cannot try to sign jar with this algorithm. |
204 jdkInfo.jdkPath, certInfo); |
306 String sigalg = certInfo.sigalg(); |
205 if (alias != null) { |
307 if (sigalg != null && |
206 certList.add(certInfo); |
308 !jdkInfo.isSupportedSigalg(sigalg)) { |
207 } |
309 continue; |
208 } |
310 } |
|
311 createCertificate(jdkInfo, certInfo); |
|
312 certList.add(certInfo); |
209 } |
313 } |
210 } |
314 } |
211 } |
315 } |
212 } |
316 } |
213 } |
317 } |
214 |
318 |
|
319 System.out.println("the keystore contents:"); |
|
320 for (JdkInfo jdkInfo : jdkInfoList) { |
|
321 execTool(jdkInfo.jdkPath + "/bin/keytool", new String[] { |
|
322 "-v", |
|
323 "-storetype", |
|
324 "jks", |
|
325 "-storepass", |
|
326 PASSWORD, |
|
327 "-keystore", |
|
328 KEYSTORE, |
|
329 "-list" |
|
330 }); |
|
331 } |
|
332 |
215 return certList; |
333 return certList; |
216 } |
334 } |
217 |
335 |
218 // Creates/Updates a key store that adds a certificate with specific algorithm. |
336 // Creates/Updates a key store that adds a certificate with specific algorithm. |
219 private static String createCertificate(String jdkPath, CertInfo certInfo) |
337 private static void createCertificate(JdkInfo jdkInfo, CertInfo certInfo) |
220 throws Throwable { |
338 throws Throwable { |
221 String alias = certInfo.alias(); |
339 List<String> arguments = new ArrayList<>(); |
222 |
|
223 List<String> arguments = new ArrayList<String>(); |
|
224 arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY); |
340 arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY); |
225 arguments.add("-v"); |
341 arguments.add("-v"); |
|
342 arguments.add("-debug"); |
226 arguments.add("-storetype"); |
343 arguments.add("-storetype"); |
227 arguments.add("jks"); |
344 arguments.add("jks"); |
228 arguments.add("-genkey"); |
345 arguments.add("-keystore"); |
|
346 arguments.add(KEYSTORE); |
|
347 arguments.add("-storepass"); |
|
348 arguments.add(PASSWORD); |
|
349 arguments.add(jdkInfo.majorVersion < 6 ? "-genkey" : "-genkeypair"); |
229 arguments.add("-keyalg"); |
350 arguments.add("-keyalg"); |
230 arguments.add(certInfo.keyAlgorithm); |
351 arguments.add(certInfo.keyAlgorithm); |
231 String sigalg = sigalg(certInfo.digestAlgorithm, certInfo.keyAlgorithm); |
352 String sigalg = certInfo.sigalg(); |
232 if (sigalg != null) { |
353 if (sigalg != null) { |
233 arguments.add("-sigalg"); |
354 arguments.add("-sigalg"); |
234 arguments.add(sigalg); |
355 arguments.add(sigalg); |
235 } |
356 } |
236 if (certInfo.keySize != 0) { |
357 if (certInfo.keySize != 0) { |
237 arguments.add("-keysize"); |
358 arguments.add("-keysize"); |
238 arguments.add(certInfo.keySize + ""); |
359 arguments.add(certInfo.keySize + ""); |
239 } |
360 } |
240 arguments.add("-dname"); |
361 arguments.add("-dname"); |
241 arguments.add("CN=Test"); |
362 arguments.add("CN=" + certInfo); |
242 arguments.add("-alias"); |
363 arguments.add("-alias"); |
243 arguments.add(alias); |
364 arguments.add(certInfo.alias()); |
244 arguments.add("-keypass"); |
365 arguments.add("-keypass"); |
245 arguments.add(PASSWORD); |
|
246 arguments.add("-storepass"); |
|
247 arguments.add(PASSWORD); |
366 arguments.add(PASSWORD); |
248 |
367 |
249 arguments.add("-startdate"); |
368 arguments.add("-startdate"); |
250 arguments.add(startDate(certInfo.expired)); |
369 arguments.add(startDate(certInfo.expired)); |
251 arguments.add("-validity"); |
370 arguments.add("-validity"); |
|
371 // arguments.add(DELAY_VERIFY ? "1" : "222"); // > six months no warn |
252 arguments.add("1"); |
372 arguments.add("1"); |
253 arguments.add("-keystore"); |
|
254 arguments.add(KEYSTORE); |
|
255 |
373 |
256 OutputAnalyzer outputAnalyzer = execTool( |
374 OutputAnalyzer outputAnalyzer = execTool( |
257 jdkPath + "/bin/keytool", |
375 jdkInfo.jdkPath + "/bin/keytool", |
258 arguments.toArray(new String[arguments.size()])); |
376 arguments.toArray(new String[arguments.size()])); |
259 if (outputAnalyzer.getExitValue() == 0 |
377 if (outputAnalyzer.getExitValue() != 0 |
260 && !outputAnalyzer.getOutput().matches("[Ee]xception")) { |
378 || outputAnalyzer.getOutput().matches("[Ee]xception") |
261 return alias; |
379 || outputAnalyzer.getOutput().matches(Test.ERROR + " ?")) { |
262 } else { |
380 System.out.println(outputAnalyzer.getOutput()); |
263 return null; |
381 throw new Exception("error generating a key pair: " + arguments); |
264 } |
382 } |
265 } |
|
266 |
|
267 private static String sigalg(String digestAlgorithm, String keyAlgorithm) { |
|
268 if (digestAlgorithm == DEFAULT) { |
|
269 return null; |
|
270 } |
|
271 |
|
272 String keyName = keyAlgorithm == EC ? "ECDSA" : keyAlgorithm; |
|
273 return digestAlgorithm.replace("-", "") + "with" + keyName; |
|
274 } |
383 } |
275 |
384 |
276 // The validity period of a certificate always be 1 day. For creating an |
385 // The validity period of a certificate always be 1 day. For creating an |
277 // expired certificate, the start date is the time before 1 day, then the |
386 // expired certificate, the start date is the time before 1 day, then the |
278 // certificate expires immediately. And for creating a valid certificate, |
387 // certificate expires immediately. And for creating a valid certificate, |
279 // the start date is the time before (1 day - CERT_VALIDITY minutes), then |
388 // the start date is the time before (1 day - CERT_VALIDITY minutes), then |
280 // the certificate will expires in CERT_VALIDITY minutes. |
389 // the certificate will expires in CERT_VALIDITY minutes. |
281 private static String startDate(boolean expiredCert) { |
390 private static String startDate(boolean expiredCert) { |
282 CALENDAR.setTime(new Date()); |
391 CALENDAR.setTime(new Date()); |
283 CALENDAR.add(Calendar.DAY_OF_MONTH, -1); |
392 if (DELAY_VERIFY || expiredCert) { |
|
393 // corresponds to '-validity 1' |
|
394 CALENDAR.add(Calendar.DAY_OF_MONTH, -1); |
|
395 } |
|
396 if (DELAY_VERIFY && !expiredCert) { |
|
397 CALENDAR.add(Calendar.MINUTE, CERT_VALIDITY); |
|
398 } |
|
399 Date startDate = CALENDAR.getTime(); |
284 if (!expiredCert) { |
400 if (!expiredCert) { |
285 CALENDAR.add(Calendar.MINUTE, CERT_VALIDITY); |
401 lastCertStartTime = startDate.getTime(); |
286 } |
402 } |
287 Date startDate = CALENDAR.getTime(); |
|
288 lastCertStartTime = startDate.getTime(); |
|
289 return DATE_FORMAT.format(startDate); |
403 return DATE_FORMAT.format(startDate); |
290 } |
404 } |
291 |
405 |
292 // Retrieves JDK info from the file which is specified by property jdkListFile, |
406 private static String outfile() { |
293 // or from property jdkList if jdkListFile is not available. |
407 return System.getProperty("o"); |
|
408 } |
|
409 |
|
410 // Retrieves JDK info from the file which is specified by property |
|
411 // jdkListFile, or from property jdkList if jdkListFile is not available. |
294 private static List<JdkInfo> jdkInfoList() throws Throwable { |
412 private static List<JdkInfo> jdkInfoList() throws Throwable { |
295 String[] jdkList = list("jdkList"); |
413 String[] jdkList = list("jdkList"); |
296 if (jdkList.length == 0) { |
414 if (jdkList.length == 0) { |
297 jdkList = new String[] { TEST_JDK }; |
415 jdkList = new String[] { "TEST_JDK" }; |
298 } |
416 } |
299 |
417 |
300 List<JdkInfo> jdkInfoList = new ArrayList<JdkInfo>(); |
418 List<JdkInfo> jdkInfoList = new ArrayList<>(); |
|
419 int index = 0; |
301 for (String jdkPath : jdkList) { |
420 for (String jdkPath : jdkList) { |
302 JdkInfo jdkInfo = new JdkInfo(jdkPath); |
421 JdkInfo jdkInfo = "TEST_JDK".equalsIgnoreCase(jdkPath) ? |
|
422 TEST_JDK_INFO : new JdkInfo(jdkPath); |
303 // The JDK version must be unique. |
423 // The JDK version must be unique. |
304 if (!jdkInfoList.contains(jdkInfo)) { |
424 if (!jdkInfoList.contains(jdkInfo)) { |
|
425 jdkInfo.index = index++; |
|
426 jdkInfo.version = String.format( |
|
427 "%s(%d)", jdkInfo.version, jdkInfo.index); |
305 jdkInfoList.add(jdkInfo); |
428 jdkInfoList.add(jdkInfo); |
306 } else { |
429 } else { |
307 System.out.println("The JDK version is duplicate: " + jdkPath); |
430 System.out.println("The JDK version is duplicate: " + jdkPath); |
308 } |
431 } |
309 } |
432 } |
310 return jdkInfoList; |
433 return jdkInfoList; |
|
434 } |
|
435 |
|
436 private static List<String> keyAlgs() throws IOException { |
|
437 if (KEY_ALGORITHMS == null) KEY_ALGORITHMS = list("keyAlgs"); |
|
438 if (KEY_ALGORITHMS.length == 0) |
|
439 return Arrays.asList(DEFAULT_KEY_ALGORITHMS); |
|
440 return Arrays.stream(KEY_ALGORITHMS).map(a -> a.split(";")[0]) |
|
441 .collect(Collectors.toList()); |
|
442 } |
|
443 |
|
444 // Return key sizes according to the specified key algorithm. |
|
445 private static int[] keySizes(String keyAlgorithm) throws IOException { |
|
446 if (KEY_ALGORITHMS == null) KEY_ALGORITHMS = list("keyAlgs"); |
|
447 for (String keyAlg : KEY_ALGORITHMS) { |
|
448 String[] split = (keyAlg + " ").split(";"); |
|
449 if (keyAlgorithm.equals(split[0].trim()) && split.length > 1) { |
|
450 int sizes[] = new int[split.length - 1]; |
|
451 for (int i = 1; i <= sizes.length; i++) |
|
452 sizes[i - 1] = split[i].isBlank() ? 0 : // default |
|
453 Integer.parseInt(split[i].trim()); |
|
454 return sizes; |
|
455 } |
|
456 } |
|
457 |
|
458 // defaults |
|
459 if (RSA.equals(keyAlgorithm) || DSA.equals(keyAlgorithm)) { |
|
460 return new int[] { 1024, 2048, 0 }; // 0 is no keysize specified |
|
461 } else if (EC.equals(keyAlgorithm)) { |
|
462 return new int[] { 384, 571, 0 }; // 0 is no keysize specified |
|
463 } else { |
|
464 throw new RuntimeException("problem determining key sizes"); |
|
465 } |
|
466 } |
|
467 |
|
468 private static List<String> digestAlgs() throws IOException { |
|
469 if (DIGEST_ALGORITHMS == null) DIGEST_ALGORITHMS = list("digestAlgs"); |
|
470 if (DIGEST_ALGORITHMS.length == 0) |
|
471 return Arrays.asList(DEFAULT_DIGEST_ALGORITHMS); |
|
472 return Arrays.asList(DIGEST_ALGORITHMS); |
311 } |
473 } |
312 |
474 |
313 // Retrieves TSA info from the file which is specified by property tsaListFile, |
475 // Retrieves TSA info from the file which is specified by property tsaListFile, |
314 // or from property tsaList if tsaListFile is not available. |
476 // or from property tsaList if tsaListFile is not available. |
315 private static List<TsaInfo> tsaInfoList() throws IOException { |
477 private static List<TsaInfo> tsaInfoList() throws IOException { |
316 String[] tsaList = list("tsaList"); |
478 String[] tsaList = list("tsaList"); |
317 |
479 |
318 List<TsaInfo> tsaInfoList = new ArrayList<TsaInfo>(); |
480 List<TsaInfo> tsaInfoList = new ArrayList<>(); |
319 for (int i = 0; i < tsaList.length; i++) { |
481 for (int i = 0; i < tsaList.length; i++) { |
320 String[] values = tsaList[i].split(";digests="); |
482 String[] values = tsaList[i].split(";digests="); |
321 |
483 |
322 String[] digests = new String[0]; |
484 String[] digests = new String[0]; |
323 if (values.length == 2) { |
485 if (values.length == 2) { |
324 digests = values[1].split(","); |
486 digests = values[1].split(","); |
325 } |
487 } |
326 |
488 |
327 TsaInfo bufTsa = new TsaInfo(i, values[0]); |
489 String tsaUrl = values[0]; |
328 |
490 if (tsaUrl.isEmpty() || tsaUrl.equalsIgnoreCase("notsa")) { |
|
491 tsaUrl = null; |
|
492 } |
|
493 TsaInfo bufTsa = new TsaInfo(i, tsaUrl); |
329 for (String digest : digests) { |
494 for (String digest : digests) { |
330 bufTsa.addDigest(digest); |
495 bufTsa.addDigest(digest.toUpperCase()); |
331 } |
496 } |
332 |
|
333 tsaInfoList.add(bufTsa); |
497 tsaInfoList.add(bufTsa); |
334 } |
498 } |
335 |
499 |
|
500 if (tsaInfoList.size() == 0) { |
|
501 throw new RuntimeException("TSA service is mandatory unless " |
|
502 + "'notsa' specified explicitly."); |
|
503 } |
336 return tsaInfoList; |
504 return tsaInfoList; |
337 } |
505 } |
338 |
506 |
339 private static String[] list(String listProp) |
507 private static String[] list(String listProp) throws IOException { |
340 throws IOException { |
|
341 String listFileProp = listProp + "File"; |
508 String listFileProp = listProp + "File"; |
342 String listFile = System.getProperty(listFileProp); |
509 String listFile = System.getProperty(listFileProp); |
343 if (!isEmpty(listFile)) { |
510 if (!isEmpty(listFile)) { |
344 System.out.println(listFileProp + "=" + listFile); |
511 System.out.println(listFileProp + "=" + listFile); |
345 List<String> list = new ArrayList<String>(); |
512 List<String> list = new ArrayList<>(); |
346 BufferedReader reader = new BufferedReader( |
513 BufferedReader reader = new BufferedReader( |
347 new FileReader(listFile)); |
514 new FileReader(listFile)); |
348 String line; |
515 String line; |
349 while ((line = reader.readLine()) != null) { |
516 while ((line = reader.readLine()) != null) { |
350 String item = line.trim(); |
517 String item = line.trim(); |
367 |
534 |
368 // A JDK (signer) signs a jar with a variety of algorithms, and then all of |
535 // A JDK (signer) signs a jar with a variety of algorithms, and then all of |
369 // JDKs (verifiers), including the signer itself, try to verify the signed |
536 // JDKs (verifiers), including the signer itself, try to verify the signed |
370 // jars respectively. |
537 // jars respectively. |
371 private static List<SignItem> test(List<JdkInfo> jdkInfoList, |
538 private static List<SignItem> test(List<JdkInfo> jdkInfoList, |
372 List<TsaInfo> tsaInfoList, List<CertInfo> certList) |
539 List<TsaInfo> tsaInfoList, List<CertInfo> certList, |
373 throws Throwable { |
540 List<SignItem> jars) throws Throwable { |
374 detailsOutput.transferPhase(); |
541 detailsOutput.transferPhase(); |
375 List<SignItem> signItems = signing(jdkInfoList, tsaInfoList, certList); |
542 List<SignItem> signItems = new ArrayList<>(); |
|
543 signItems.addAll(signing(jdkInfoList, tsaInfoList, certList, jars)); |
|
544 if (TEST_JAR_UPDATE) { |
|
545 signItems.addAll(signing(jdkInfoList, tsaInfoList, certList, |
|
546 updating(signItems.stream().filter( |
|
547 x -> x.status != Status.ERROR) |
|
548 .collect(Collectors.toList())))); |
|
549 } |
376 |
550 |
377 detailsOutput.transferPhase(); |
551 detailsOutput.transferPhase(); |
378 for (SignItem signItem : signItems) { |
552 for (SignItem signItem : signItems) { |
379 for (JdkInfo verifierInfo : jdkInfoList) { |
553 for (JdkInfo verifierInfo : jdkInfoList) { |
380 // JDK 6 doesn't support EC |
554 if (!verifierInfo.supportsKeyAlg( |
381 if (!verifierInfo.isJdk6() |
555 signItem.certInfo.keyAlgorithm)) continue; |
382 || signItem.certInfo.keyAlgorithm != EC) { |
556 VerifyItem verifyItem = VerifyItem.build(verifierInfo); |
383 verifying(signItem, VerifyItem.build(verifierInfo)); |
557 verifyItem.addSignerCertInfos(signItem); |
384 } |
558 signItem.addVerifyItem(verifyItem); |
385 } |
559 verifying(signItem, verifyItem); |
|
560 } |
|
561 } |
|
562 |
|
563 // if lastCertExpirationTime passed already now, probably some |
|
564 // certificate was already expired during jar signature verification |
|
565 // (jarsigner -verify) and the test should probably be repeated with an |
|
566 // increased validity period -DcertValidity CERT_VALIDITY |
|
567 long lastCertExpirationTime = lastCertStartTime + 24 * 60 * 60 * 1000; |
|
568 if (lastCertExpirationTime < System.currentTimeMillis()) { |
|
569 throw new AssertionError("CERT_VALIDITY (" + CERT_VALIDITY |
|
570 + " [minutes]) was too short. " |
|
571 + "Creating and signing the jars took longer, " |
|
572 + "presumably at least " |
|
573 + ((lastCertExpirationTime - System.currentTimeMillis()) |
|
574 / 60 * 1000 + CERT_VALIDITY) + " [minutes]."); |
386 } |
575 } |
387 |
576 |
388 if (DELAY_VERIFY) { |
577 if (DELAY_VERIFY) { |
389 detailsOutput.transferPhase(); |
578 detailsOutput.transferPhase(); |
390 System.out.print("Waiting for delay verifying"); |
579 System.out.print("Waiting for delay verifying"); |
391 long lastCertExpirationTime = lastCertStartTime + 24 * 60 * 60 * 1000; |
|
392 while (System.currentTimeMillis() < lastCertExpirationTime) { |
580 while (System.currentTimeMillis() < lastCertExpirationTime) { |
393 TimeUnit.SECONDS.sleep(30); |
581 TimeUnit.SECONDS.sleep(30); |
394 System.out.print("."); |
582 System.out.print("."); |
395 } |
583 } |
396 System.out.println(); |
584 System.out.println(); |
402 } |
590 } |
403 } |
591 } |
404 } |
592 } |
405 |
593 |
406 detailsOutput.transferPhase(); |
594 detailsOutput.transferPhase(); |
407 |
|
408 return signItems; |
595 return signItems; |
409 } |
596 } |
410 |
597 |
411 private static List<SignItem> signing(List<JdkInfo> jdkInfos, |
598 private static List<SignItem> signing(List<JdkInfo> jdkInfos, |
412 List<TsaInfo> tsaList, List<CertInfo> certList) throws Throwable { |
599 List<TsaInfo> tsaList, List<CertInfo> certList, |
413 List<SignItem> signItems = new ArrayList<SignItem>(); |
600 List<SignItem> unsignedJars) throws Throwable { |
414 |
601 List<SignItem> signItems = new ArrayList<>(); |
415 Set<String> signFilter = new HashSet<String>(); |
602 |
416 |
603 for (CertInfo certInfo : certList) { |
417 for (JdkInfo signerInfo : jdkInfos) { |
604 JdkInfo signerInfo = certInfo.jdkInfo; |
418 for (String keyAlgorithm : KEY_ALGORITHMS) { |
605 String keyAlgorithm = certInfo.keyAlgorithm; |
419 // JDK 6 doesn't support EC |
606 String sigDigestAlgorithm = certInfo.digestAlgorithm; |
420 if (signerInfo.isJdk6() && keyAlgorithm == EC) { |
607 int keySize = certInfo.keySize; |
421 continue; |
608 boolean expired = certInfo.expired; |
|
609 |
|
610 for (String jarDigestAlgorithm : digestAlgs()) { |
|
611 if (DEFAULT.equals(jarDigestAlgorithm)) { |
|
612 jarDigestAlgorithm = null; |
422 } |
613 } |
423 |
614 |
424 for (String digestAlgorithm : DIGEST_ALGORITHMS) { |
615 for (TsaInfo tsaInfo : tsaList) { |
425 String sigalg = sigalg(digestAlgorithm, keyAlgorithm); |
616 String tsaUrl = tsaInfo.tsaUrl; |
426 // If the signature algorithm is not supported by the JDK, |
617 |
427 // it cannot try to sign jar with this algorithm. |
618 List<String> tsaDigestAlgs = digestAlgs(); |
428 if (sigalg != null && !signerInfo.isSupportedSigalg(sigalg)) { |
619 // no point in specifying a tsa digest algorithm |
429 continue; |
620 // for no TSA, except maybe it would issue a warning. |
|
621 if (tsaUrl == null) tsaDigestAlgs = Arrays.asList(DEFAULT); |
|
622 // If the JDK doesn't support option -tsadigestalg, the |
|
623 // associated cases can just be ignored. |
|
624 if (!signerInfo.supportsTsadigestalg) { |
|
625 tsaDigestAlgs = Arrays.asList(DEFAULT); |
430 } |
626 } |
431 |
627 for (String tsaDigestAlg : tsaDigestAlgs) { |
432 // If the JDK doesn't support option -tsadigestalg, the |
628 if (DEFAULT.equals(tsaDigestAlg)) { |
433 // associated cases just be ignored. |
629 tsaDigestAlg = null; |
434 if (digestAlgorithm != DEFAULT |
630 } else if (!tsaInfo.isDigestSupported(tsaDigestAlg)) { |
435 && !signerInfo.supportsTsadigestalg) { |
631 // It has to ignore the digest algorithm, which |
436 continue; |
632 // is not supported by the TSA server. |
437 } |
633 continue; |
438 |
634 } |
439 for (int keySize : keySizes(keyAlgorithm)) { |
635 |
440 for (boolean expired : EXPIRED) { |
636 if (tsaUrl != null && TsaFilter.filter( |
441 CertInfo certInfo = new CertInfo( |
637 signerInfo.version, |
442 signerInfo.version, |
638 tsaDigestAlg, |
443 keyAlgorithm, |
639 expired, |
444 digestAlgorithm, |
640 tsaInfo.index)) { |
445 keySize, |
641 continue; |
446 expired); |
642 } |
447 if (!certList.contains(certInfo)) { |
643 |
448 continue; |
644 for (SignItem prevSign : unsignedJars) { |
|
645 String unsignedJar = prevSign.signedJar; |
|
646 |
|
647 SignItem signItem = SignItem.build(prevSign) |
|
648 .certInfo(certInfo) |
|
649 .jdkInfo(signerInfo); |
|
650 String signedJar = unsignedJar + "-" + "JDK_" + ( |
|
651 signerInfo.version + "-CERT_" + certInfo). |
|
652 replaceAll("[^a-z_0-9A-Z.]+", "-"); |
|
653 |
|
654 if (jarDigestAlgorithm != null) { |
|
655 signedJar += "-DIGESTALG_" + jarDigestAlgorithm; |
|
656 signItem.digestAlgorithm(jarDigestAlgorithm); |
449 } |
657 } |
450 |
658 if (tsaUrl == null) { |
451 String tsadigestalg = digestAlgorithm != DEFAULT |
659 signItem.tsaIndex(-1); |
452 ? digestAlgorithm |
660 } else { |
453 : null; |
661 signedJar += "-TSA_" + tsaInfo.index; |
454 |
662 signItem.tsaIndex(tsaInfo.index); |
455 for (TsaInfo tsaInfo : tsaList) { |
663 if (tsaDigestAlg != null) { |
456 // It has to ignore the digest algorithm, which |
664 signedJar += "-TSADIGALG_" + tsaDigestAlg; |
457 // is not supported by the TSA server. |
665 signItem.tsaDigestAlgorithm(tsaDigestAlg); |
458 if(!tsaInfo.isDigestSupported(tsadigestalg)) { |
|
459 continue; |
|
460 } |
666 } |
461 |
|
462 String tsaUrl = tsaInfo.tsaUrl; |
|
463 if (TsaFilter.filter( |
|
464 signerInfo.version, |
|
465 digestAlgorithm, |
|
466 expired, |
|
467 tsaInfo.index)) { |
|
468 tsaUrl = null; |
|
469 } |
|
470 |
|
471 String signedJar = "JDK_" |
|
472 + signerInfo.version + "-CERT_" |
|
473 + certInfo |
|
474 + (tsaUrl == null |
|
475 ? "" |
|
476 : "-TSA_" + tsaInfo.index); |
|
477 |
|
478 // It has to ignore the same jar signing. |
|
479 if (!signFilter.add(signedJar)) { |
|
480 continue; |
|
481 } |
|
482 |
|
483 SignItem signItem = SignItem.build() |
|
484 .certInfo(certInfo) |
|
485 .version(signerInfo.version) |
|
486 .signatureAlgorithm(sigalg) |
|
487 .tsaDigestAlgorithm( |
|
488 tsaUrl == null |
|
489 ? null |
|
490 : tsadigestalg) |
|
491 .tsaIndex( |
|
492 tsaUrl == null |
|
493 ? -1 |
|
494 : tsaInfo.index) |
|
495 .signedJar(signedJar); |
|
496 String signingId = signingId(signItem); |
|
497 detailsOutput.writeAnchorName(signingId, |
|
498 "Signing: " + signingId); |
|
499 |
|
500 OutputAnalyzer signOA = signJar( |
|
501 signerInfo.jarsignerPath, |
|
502 sigalg, |
|
503 tsadigestalg, |
|
504 tsaUrl, |
|
505 certInfo.alias(), |
|
506 signedJar); |
|
507 Status signingStatus = signingStatus(signOA); |
|
508 signItem.status(signingStatus); |
|
509 |
|
510 if (signingStatus != Status.ERROR) { |
|
511 // Using the testing JDK, which is specified |
|
512 // by jtreg option "-jdk", to verify the |
|
513 // signed jar and extract the signature |
|
514 // algorithm and timestamp digest algorithm. |
|
515 String output = verifyJar(TEST_JARSIGNER, |
|
516 signedJar).getOutput(); |
|
517 signItem.extractedSignatureAlgorithm( |
|
518 extract(output, |
|
519 " *Signature algorithm.*", |
|
520 ".*: |,.*")); |
|
521 signItem.extractedTsaDigestAlgorithm( |
|
522 extract(output, |
|
523 " *Timestamp digest algorithm.*", |
|
524 ".*: ")); |
|
525 } |
|
526 |
|
527 signItems.add(signItem); |
|
528 } |
667 } |
|
668 signItem.signedJar(signedJar); |
|
669 |
|
670 String signingId = signingId(signItem); |
|
671 detailsOutput.writeAnchorName(signingId, |
|
672 "Signing: " + signingId); |
|
673 |
|
674 OutputAnalyzer signOA = signJar( |
|
675 signerInfo.jarsignerPath, |
|
676 certInfo.sigalg(), |
|
677 jarDigestAlgorithm, |
|
678 tsaDigestAlg, |
|
679 tsaUrl, |
|
680 certInfo.alias(), |
|
681 unsignedJar, |
|
682 signedJar); |
|
683 Status signingStatus = signingStatus(signOA, |
|
684 tsaUrl != null); |
|
685 signItem.status(signingStatus); |
|
686 signItems.add(signItem); |
529 } |
687 } |
530 } |
688 } |
531 } |
689 } |
532 } |
690 } |
533 } |
691 } |
534 |
692 |
535 return signItems; |
693 return signItems; |
536 } |
694 } |
537 |
695 |
|
696 private static List<SignItem> updating(List<SignItem> prevSignItems) |
|
697 throws IOException { |
|
698 List<SignItem> updateItems = new ArrayList<>(); |
|
699 for (SignItem prevSign : prevSignItems) { |
|
700 updateItems.addAll(updateJar(prevSign)); |
|
701 } |
|
702 return updateItems; |
|
703 } |
|
704 |
538 private static void verifying(SignItem signItem, VerifyItem verifyItem) |
705 private static void verifying(SignItem signItem, VerifyItem verifyItem) |
539 throws Throwable { |
706 throws Throwable { |
540 boolean delayVerify = verifyItem.status == Status.NONE; |
707 // TODO: how will be ensured that the first verification is not after valid period expired which is only one minute? |
541 String verifyingId = verifyingId(signItem, verifyItem, !delayVerify); |
708 boolean delayVerify = verifyItem.status != Status.NONE; |
|
709 String verifyingId = verifyingId(signItem, verifyItem, delayVerify); |
542 detailsOutput.writeAnchorName(verifyingId, "Verifying: " + verifyingId); |
710 detailsOutput.writeAnchorName(verifyingId, "Verifying: " + verifyingId); |
543 |
|
544 OutputAnalyzer verifyOA = verifyJar(verifyItem.jdkInfo.jarsignerPath, |
711 OutputAnalyzer verifyOA = verifyJar(verifyItem.jdkInfo.jarsignerPath, |
545 signItem.signedJar); |
712 signItem.signedJar, verifyItem.certInfo == null ? null : |
546 Status verifyingStatus = verifyingStatus(verifyOA); |
713 verifyItem.certInfo.alias()); |
547 |
714 Status verifyingStatus = verifyingStatus(signItem, verifyItem, verifyOA); |
548 // It checks if the default timestamp digest algorithm is SHA-256. |
715 |
549 if (verifyingStatus != Status.ERROR |
716 try { |
550 && signItem.tsaDigestAlgorithm == null) { |
717 String match = "^ (" |
551 verifyingStatus = signItem.extractedTsaDigestAlgorithm != null |
718 + " Signature algorithm: " + signItem.certInfo. |
552 && !signItem.extractedTsaDigestAlgorithm.matches("SHA-?256") |
719 expectedSigalg() + ", " + signItem.certInfo. |
553 ? Status.ERROR |
720 expectedKeySize() + "-bit key" |
554 : verifyingStatus; |
721 + ")|(" |
555 if (verifyingStatus == Status.ERROR) { |
722 + " Digest algorithm: " + signItem.expectedDigestAlg() |
556 System.out.println("The default tsa digest is not SHA-256: " |
723 + (signItem.tsaIndex < 0 ? "" : |
557 + signItem.extractedTsaDigestAlgorithm); |
724 ")|(" |
558 } |
725 + "Timestamped by \".+\" on .*" |
559 } |
726 + ")|(" |
560 |
727 + " Timestamp digest algorithm: " |
561 if (delayVerify) { |
728 + signItem.expectedTsaDigestAlg() |
562 signItem.addVerifyItem(verifyItem.status(verifyingStatus)); |
729 + ")|(" |
|
730 + " Timestamp signature algorithm: .*" |
|
731 ) |
|
732 + ")$"; |
|
733 verifyOA.stdoutShouldMatchByLine( |
|
734 "^- Signed by \"CN=" + signItem.certInfo.toString() |
|
735 .replaceAll("[.]", "[.]") + "\"$", |
|
736 "^(- Signed by \"CN=.+\")?$", |
|
737 match); |
|
738 } catch (Throwable e) { |
|
739 e.printStackTrace(); |
|
740 verifyingStatus = Status.ERROR; |
|
741 } |
|
742 |
|
743 if (!delayVerify) { |
|
744 verifyItem.status(verifyingStatus); |
563 } else { |
745 } else { |
564 verifyItem.delayStatus(verifyingStatus); |
746 verifyItem.delayStatus(verifyingStatus); |
565 } |
747 } |
566 } |
748 |
567 |
749 if (verifyItem.prevVerify != null) { |
568 // Return key sizes according to the specified key algorithm. |
750 verifying(signItem, verifyItem.prevVerify); |
569 private static int[] keySizes(String keyAlgorithm) { |
751 } |
570 if (keyAlgorithm == RSA || keyAlgorithm == DSA) { |
|
571 return new int[] { 1024, 2048, 0 }; |
|
572 } else if (keyAlgorithm == EC) { |
|
573 return new int[] { 384, 571, 0 }; |
|
574 } |
|
575 |
|
576 return null; |
|
577 } |
752 } |
578 |
753 |
579 // Determines the status of signing. |
754 // Determines the status of signing. |
580 private static Status signingStatus(OutputAnalyzer outputAnalyzer) { |
755 private static Status signingStatus(OutputAnalyzer outputAnalyzer, |
581 if (outputAnalyzer.getExitValue() == 0) { |
756 boolean tsa) { |
582 if (outputAnalyzer.getOutput().contains(Test.WARNING)) { |
757 if (outputAnalyzer.getExitValue() != 0) { |
583 return Status.WARNING; |
758 return Status.ERROR; |
584 } else { |
759 } |
585 return Status.NORMAL; |
760 if (!outputAnalyzer.getOutput().contains(Test.JAR_SIGNED)) { |
586 } |
761 return Status.ERROR; |
|
762 } |
|
763 |
|
764 boolean warning = false; |
|
765 for (String line : outputAnalyzer.getOutput().lines() |
|
766 .toArray(String[]::new)) { |
|
767 if (line.matches(Test.ERROR + " ?")) return Status.ERROR; |
|
768 if (line.matches(Test.WARNING + " ?")) warning = true; |
|
769 } |
|
770 return warning ? Status.WARNING : Status.NORMAL; |
|
771 } |
|
772 |
|
773 // Determines the status of verifying. |
|
774 private static Status verifyingStatus(SignItem signItem, VerifyItem |
|
775 verifyItem, OutputAnalyzer outputAnalyzer) { |
|
776 List<String> expectedSignedContent = new ArrayList<>(); |
|
777 if (verifyItem.certInfo == null) { |
|
778 expectedSignedContent.addAll(signItem.jarContents); |
587 } else { |
779 } else { |
|
780 SignItem i = signItem; |
|
781 while (i != null) { |
|
782 if (i.certInfo != null && i.certInfo.equals(verifyItem.certInfo)) { |
|
783 expectedSignedContent.addAll(i.jarContents); |
|
784 } |
|
785 i = i.prevSign; |
|
786 } |
|
787 } |
|
788 List<String> expectedUnsignedContent = |
|
789 new ArrayList<>(signItem.jarContents); |
|
790 expectedUnsignedContent.removeAll(expectedSignedContent); |
|
791 |
|
792 int expectedExitCode = !STRICT || expectedUnsignedContent.isEmpty() ? 0 : 32; |
|
793 if (outputAnalyzer.getExitValue() != expectedExitCode) { |
|
794 System.out.println("verifyingStatus: error: exit code != " + expectedExitCode + ": " + outputAnalyzer.getExitValue() + " != " + expectedExitCode); |
588 return Status.ERROR; |
795 return Status.ERROR; |
589 } |
796 } |
590 } |
797 String expectedSuccessMessage = expectedUnsignedContent.isEmpty() ? |
591 |
798 Test.JAR_VERIFIED : Test.JAR_VERIFIED_WITH_SIGNER_ERRORS; |
592 // Determines the status of verifying. |
799 if (!outputAnalyzer.getOutput().contains(expectedSuccessMessage)) { |
593 private static Status verifyingStatus(OutputAnalyzer outputAnalyzer) { |
800 System.out.println("verifyingStatus: error: expectedSuccessMessage not found: " + expectedSuccessMessage); |
594 if (outputAnalyzer.getExitValue() == 0) { |
801 return Status.ERROR; |
595 String output = outputAnalyzer.getOutput(); |
802 } |
596 if (!output.contains(Test.JAR_VERIFIED)) { |
803 |
|
804 boolean tsa = signItem.tsaIndex >= 0; |
|
805 boolean warning = false; |
|
806 for (String line : outputAnalyzer.getOutput().lines() |
|
807 .toArray(String[]::new)) { |
|
808 if (line.isBlank()) continue; |
|
809 if (Test.JAR_VERIFIED.equals(line)) continue; |
|
810 if (line.matches(Test.ERROR + " ?") && expectedExitCode == 0) { |
|
811 System.out.println("verifyingStatus: error: line.matches(" + Test.ERROR + "\" ?\"): " + line); |
597 return Status.ERROR; |
812 return Status.ERROR; |
598 } else if (output.contains(Test.WARNING)) { |
813 } |
599 return Status.WARNING; |
814 if (line.matches(Test.WARNING + " ?")) { |
600 } else { |
815 warning = true; |
601 return Status.NORMAL; |
816 continue; |
602 } |
817 } |
603 } else { |
818 if (!warning) continue; |
604 return Status.ERROR; |
819 line = line.strip(); |
605 } |
820 if (Test.NOT_YET_VALID_CERT_SIGNING_WARNING.equals(line)) continue; |
606 } |
821 if (Test.HAS_EXPIRING_CERT_SIGNING_WARNING.equals(line)) continue; |
607 |
822 if (Test.HAS_EXPIRING_CERT_VERIFYING_WARNING.equals(line)) continue; |
608 // Extracts string from text by specified patterns. |
823 if (line.matches("^" + Test.NO_TIMESTAMP_SIGNING_WARN_TEMPLATE |
609 private static String extract(String text, String linePattern, |
824 .replaceAll( |
610 String replacePattern) { |
825 "\\(%1\\$tY-%1\\$tm-%1\\$td\\)", "\\\\([^\\\\)]+\\\\)" |
611 Matcher lineMatcher = Pattern.compile(linePattern).matcher(text); |
826 + "( or after any future revocation date)?") |
612 if (lineMatcher.find()) { |
827 .replaceAll("[.]", "[.]") + "$") && !tsa) continue; |
613 String line = lineMatcher.group(0); |
828 if (line.matches("^" + Test.NO_TIMESTAMP_VERIFYING_WARN_TEMPLATE |
614 return line.replaceAll(replacePattern, ""); |
829 .replaceAll("\\(as early as %1\\$tY-%1\\$tm-%1\\$td\\)", |
615 } else { |
830 "\\\\([^\\\\)]+\\\\)" |
616 return null; |
831 + "( or after any future revocation date)?") |
617 } |
832 .replaceAll("[.]", "[.]") + "$") && !tsa) continue; |
|
833 if (line.matches("^This jar contains signatures that do(es)? not " |
|
834 + "include a timestamp[.] Without a timestamp, users may " |
|
835 + "not be able to validate this jar after the signer " |
|
836 + "certificate's expiration date \\([^\\)]+\\) or after " |
|
837 + "any future revocation date[.]") && !tsa) continue; |
|
838 if (Test.CERTIFICATE_SELF_SIGNED.equals(line)) continue; |
|
839 if (Test.HAS_EXPIRED_CERT_VERIFYING_WARNING.equals(line) |
|
840 && signItem.certInfo.expired) continue; |
|
841 System.out.println("verifyingStatus: unexpected line: " + line); |
|
842 return Status.ERROR; // treat unexpected warnings as error |
|
843 } |
|
844 return warning ? Status.WARNING : Status.NORMAL; |
618 } |
845 } |
619 |
846 |
620 // Using specified jarsigner to sign the pre-created jar with specified |
847 // Using specified jarsigner to sign the pre-created jar with specified |
621 // algorithms. |
848 // algorithms. |
622 private static OutputAnalyzer signJar(String jarsignerPath, String sigalg, |
849 private static OutputAnalyzer signJar(String jarsignerPath, String sigalg, |
623 String tsadigestalg, String tsa, String alias, String signedJar) |
850 String jarDigestAlgorithm, |
624 throws Throwable { |
851 String tsadigestalg, String tsa, String alias, String unsignedJar, |
625 List<String> arguments = new ArrayList<String>(); |
852 String signedJar) throws Throwable { |
|
853 List<String> arguments = new ArrayList<>(); |
626 |
854 |
627 if (PROXY_HOST != null && PROXY_PORT != null) { |
855 if (PROXY_HOST != null && PROXY_PORT != null) { |
628 arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST); |
856 arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST); |
629 arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT); |
857 arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT); |
630 arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST); |
858 arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST); |
631 arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT); |
859 arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT); |
632 } |
860 } |
633 arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY); |
861 arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY); |
634 arguments.add("-debug"); |
862 arguments.add("-debug"); |
635 arguments.add("-verbose"); |
863 arguments.add("-verbose"); |
|
864 if (jarDigestAlgorithm != null) { |
|
865 arguments.add("-digestalg"); |
|
866 arguments.add(jarDigestAlgorithm); |
|
867 } |
636 if (sigalg != null) { |
868 if (sigalg != null) { |
637 arguments.add("-sigalg"); |
869 arguments.add("-sigalg"); |
638 arguments.add(sigalg); |
870 arguments.add(sigalg); |
639 } |
871 } |
640 if (tsa != null) { |
872 if (tsa != null) { |
647 } |
879 } |
648 arguments.add("-keystore"); |
880 arguments.add("-keystore"); |
649 arguments.add(KEYSTORE); |
881 arguments.add(KEYSTORE); |
650 arguments.add("-storepass"); |
882 arguments.add("-storepass"); |
651 arguments.add(PASSWORD); |
883 arguments.add(PASSWORD); |
|
884 arguments.add("-sigfile"); |
|
885 arguments.add(nextSigfileName(alias, unsignedJar, signedJar)); |
652 arguments.add("-signedjar"); |
886 arguments.add("-signedjar"); |
653 arguments.add(signedJar + ".jar"); |
887 arguments.add(signedJar + ".jar"); |
654 arguments.add(TEST_JAR_NAME); |
888 arguments.add(unsignedJar + ".jar"); |
655 arguments.add(alias); |
889 arguments.add(alias); |
656 |
890 |
657 OutputAnalyzer outputAnalyzer = execTool( |
891 OutputAnalyzer outputAnalyzer = execTool(jarsignerPath, |
658 jarsignerPath, |
|
659 arguments.toArray(new String[arguments.size()])); |
892 arguments.toArray(new String[arguments.size()])); |
660 return outputAnalyzer; |
893 return outputAnalyzer; |
661 } |
894 } |
662 |
895 |
663 // Using specified jarsigner to verify the signed jar. |
896 // Using specified jarsigner to verify the signed jar. |
664 private static OutputAnalyzer verifyJar(String jarsignerPath, |
897 private static OutputAnalyzer verifyJar(String jarsignerPath, |
665 String signedJar) throws Throwable { |
898 String signedJar, String alias) throws Throwable { |
666 OutputAnalyzer outputAnalyzer = execTool( |
899 List<String> arguments = new ArrayList<>(); |
667 jarsignerPath, |
900 arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY); |
668 "-J-Djava.security.properties=" + JAVA_SECURITY, |
901 arguments.add("-debug"); |
669 "-debug", |
902 arguments.add("-verbose"); |
670 "-verbose", |
903 arguments.add("-certs"); |
671 "-certs", |
904 arguments.add("-keystore"); |
672 "-keystore", KEYSTORE, |
905 arguments.add(KEYSTORE); |
673 "-verify", signedJar + ".jar"); |
906 arguments.add("-verify"); |
|
907 if (STRICT) arguments.add("-strict"); |
|
908 arguments.add(signedJar + ".jar"); |
|
909 if (alias != null) arguments.add(alias); |
|
910 OutputAnalyzer outputAnalyzer = execTool(jarsignerPath, |
|
911 arguments.toArray(new String[arguments.size()])); |
674 return outputAnalyzer; |
912 return outputAnalyzer; |
675 } |
913 } |
676 |
914 |
677 // Generates the test result report. |
915 // Generates the test result report. |
678 private static boolean generateReport(List<TsaInfo> tsaList, |
916 private static boolean generateReport(List<JdkInfo> jdkList, List<TsaInfo> tsaList, |
679 List<SignItem> signItems) throws IOException { |
917 List<SignItem> signItems) throws IOException { |
680 System.out.println("Report is being generated..."); |
918 System.out.println("Report is being generated..."); |
681 |
919 |
682 StringBuilder report = new StringBuilder(); |
920 StringBuilder report = new StringBuilder(); |
683 report.append(HtmlHelper.startHtml()); |
921 report.append(HtmlHelper.startHtml()); |
684 report.append(HtmlHelper.startPre()); |
922 report.append(HtmlHelper.startPre()); |
|
923 |
|
924 // Generates JDK list |
|
925 report.append("JDK list:\n"); |
|
926 for(JdkInfo jdkInfo : jdkList) { |
|
927 report.append(String.format("%d=%s%n", |
|
928 jdkInfo.index, |
|
929 jdkInfo.runtimeVersion)); |
|
930 } |
|
931 |
685 // Generates TSA URLs |
932 // Generates TSA URLs |
686 report.append("TSA list:\n"); |
933 report.append("TSA list:\n"); |
687 for(TsaInfo tsaInfo : tsaList) { |
934 for(TsaInfo tsaInfo : tsaList) { |
688 report.append( |
935 report.append( |
689 String.format("%d=%s%n", tsaInfo.index, tsaInfo.tsaUrl)); |
936 String.format("%d=%s%n", tsaInfo.index, |
|
937 tsaInfo.tsaUrl == null ? "notsa" : tsaInfo.tsaUrl)); |
690 } |
938 } |
691 report.append(HtmlHelper.endPre()); |
939 report.append(HtmlHelper.endPre()); |
692 |
940 |
693 report.append(HtmlHelper.startTable()); |
941 report.append(HtmlHelper.startTable()); |
694 // Generates report headers. |
942 // Generates report headers. |
695 List<String> headers = new ArrayList<String>(); |
943 List<String> headers = new ArrayList<>(); |
696 headers.add("[Certificate]"); |
944 headers.add("[Jarfile]"); |
|
945 headers.add("[Signing Certificate]"); |
697 headers.add("[Signer JDK]"); |
946 headers.add("[Signer JDK]"); |
698 headers.add("[Signature Algorithm]"); |
947 headers.add("[Signature Algorithm]"); |
699 headers.add("[TSA Digest]"); |
948 headers.add("[Jar Digest Algorithm]"); |
|
949 headers.add("[TSA Digest Algorithm]"); |
700 headers.add("[TSA]"); |
950 headers.add("[TSA]"); |
701 headers.add("[Signing Status]"); |
951 headers.add("[Signing Status]"); |
702 headers.add("[Verifier JDK]"); |
952 headers.add("[Verifier JDK]"); |
|
953 headers.add("[Verifying Certificate]"); |
703 headers.add("[Verifying Status]"); |
954 headers.add("[Verifying Status]"); |
704 if (DELAY_VERIFY) { |
955 if (DELAY_VERIFY) { |
705 headers.add("[Delay Verifying Status]"); |
956 headers.add("[Delay Verifying Status]"); |
706 } |
957 } |
707 headers.add("[Failed]"); |
958 headers.add("[Failed]"); |
708 report.append(HtmlHelper.htmlRow( |
959 report.append(HtmlHelper.htmlRow( |
709 headers.toArray(new String[headers.size()]))); |
960 headers.toArray(new String[headers.size()]))); |
710 |
961 |
711 StringBuilder failedReport = new StringBuilder(report.toString()); |
962 StringBuilder failedReport = new StringBuilder(report.toString()); |
712 |
963 |
713 boolean failed = false; |
964 boolean failed = signItems.isEmpty(); |
714 |
965 |
715 // Generates report rows. |
966 // Generates report rows. |
716 for (SignItem signItem : signItems) { |
967 for (SignItem signItem : signItems) { |
|
968 failed = failed || signItem.verifyItems.isEmpty(); |
717 for (VerifyItem verifyItem : signItem.verifyItems) { |
969 for (VerifyItem verifyItem : signItem.verifyItems) { |
718 String reportRow = reportRow(signItem, verifyItem); |
970 String reportRow = reportRow(signItem, verifyItem); |
719 report.append(reportRow); |
971 report.append(reportRow); |
720 boolean isFailedCase = isFailed(signItem, verifyItem); |
972 boolean isFailedCase = isFailed(signItem, verifyItem); |
721 if (isFailedCase) { |
973 if (isFailedCase) { |
765 |
1017 |
766 // Executes the specified JDK tools, such as keytool and jarsigner, and |
1018 // Executes the specified JDK tools, such as keytool and jarsigner, and |
767 // ensures the output is in US English. |
1019 // ensures the output is in US English. |
768 private static OutputAnalyzer execTool(String toolPath, String... args) |
1020 private static OutputAnalyzer execTool(String toolPath, String... args) |
769 throws Throwable { |
1021 throws Throwable { |
770 String[] cmd = new String[args.length + 4]; |
1022 long start = System.currentTimeMillis(); |
771 cmd[0] = toolPath; |
1023 try { |
772 cmd[1] = "-J-Duser.language=en"; |
1024 |
773 cmd[2] = "-J-Duser.country=US"; |
1025 String[] cmd = new String[args.length + 4]; |
774 cmd[3] = "-J-Djava.security.egd=file:/dev/./urandom"; |
1026 cmd[0] = toolPath; |
775 System.arraycopy(args, 0, cmd, 4, args.length); |
1027 cmd[1] = "-J-Duser.language=en"; |
776 return ProcessTools.executeCommand(cmd); |
1028 cmd[2] = "-J-Duser.country=US"; |
|
1029 cmd[3] = "-J-Djava.security.egd=file:/dev/./urandom"; |
|
1030 System.arraycopy(args, 0, cmd, 4, args.length); |
|
1031 return ProcessTools.executeCommand(cmd); |
|
1032 |
|
1033 } finally { |
|
1034 long end = System.currentTimeMillis(); |
|
1035 System.out.println("child process duration [ms]: " + (end - start)); |
|
1036 } |
777 } |
1037 } |
778 |
1038 |
779 private static class JdkInfo { |
1039 private static class JdkInfo { |
780 |
1040 |
|
1041 private int index; |
781 private final String jdkPath; |
1042 private final String jdkPath; |
782 private final String jarsignerPath; |
1043 private final String jarsignerPath; |
783 private final String version; |
1044 private final String runtimeVersion; |
|
1045 private String version; |
|
1046 private final int majorVersion; |
784 private final boolean supportsTsadigestalg; |
1047 private final boolean supportsTsadigestalg; |
785 |
1048 |
786 private Map<String, Boolean> sigalgMap = new HashMap<String, Boolean>(); |
1049 private Map<String, Boolean> sigalgMap = new HashMap<>(); |
787 |
1050 |
788 private JdkInfo(String jdkPath) throws Throwable { |
1051 private JdkInfo(String jdkPath) throws Throwable { |
789 this.jdkPath = jdkPath; |
1052 this.jdkPath = jdkPath; |
790 version = execJdkUtils(jdkPath, JdkUtils.M_JAVA_RUNTIME_VERSION); |
1053 jarsignerPath = jarsignerPath(jdkPath); |
791 if (version == null || version.trim().isEmpty()) { |
1054 runtimeVersion = execJdkUtils(jdkPath, JdkUtils.M_JAVA_RUNTIME_VERSION); |
|
1055 if (runtimeVersion == null || runtimeVersion.isBlank()) { |
792 throw new RuntimeException( |
1056 throw new RuntimeException( |
793 "Cannot determine the JDK version: " + jdkPath); |
1057 "Cannot determine the JDK version: " + jdkPath); |
794 } |
1058 } |
795 jarsignerPath = jarsignerPath(jdkPath); |
1059 version = execJdkUtils(jdkPath, JdkUtils.M_JAVA_VERSION); |
|
1060 majorVersion = Integer.parseInt((runtimeVersion.matches("^1[.].*") ? |
|
1061 runtimeVersion.substring(2) : runtimeVersion).replaceAll("[^0-9].*$", "")); |
796 supportsTsadigestalg = execTool(jarsignerPath, "-help") |
1062 supportsTsadigestalg = execTool(jarsignerPath, "-help") |
797 .getOutput().contains("-tsadigestalg"); |
1063 .getOutput().contains("-tsadigestalg"); |
798 } |
1064 } |
799 |
1065 |
800 private boolean isSupportedSigalg(String sigalg) throws Throwable { |
1066 private boolean isSupportedSigalg(String sigalg) throws Throwable { |
801 if (!sigalgMap.containsKey(sigalg)) { |
1067 if (!sigalgMap.containsKey(sigalg)) { |
802 boolean isSupported = "true".equalsIgnoreCase( |
1068 boolean isSupported = Boolean.parseBoolean( |
803 execJdkUtils( |
1069 execJdkUtils( |
804 jdkPath, |
1070 jdkPath, |
805 JdkUtils.M_IS_SUPPORTED_SIGALG, |
1071 JdkUtils.M_IS_SUPPORTED_SIGALG, |
806 sigalg)); |
1072 sigalg)); |
807 sigalgMap.put(sigalg, isSupported); |
1073 sigalgMap.put(sigalg, isSupported); |
808 } |
1074 } |
809 |
1075 |
810 return sigalgMap.get(sigalg); |
1076 return sigalgMap.get(sigalg); |
811 } |
1077 } |
812 |
1078 |
813 private boolean isJdk6() { |
1079 private boolean isAtLeastMajorVersion(int minVersion) { |
814 return version.startsWith("1.6"); |
1080 return majorVersion >= minVersion; |
|
1081 } |
|
1082 |
|
1083 private boolean supportsKeyAlg(String keyAlgorithm) { |
|
1084 // JDK 6 doesn't support EC |
|
1085 return isAtLeastMajorVersion(6) || !EC.equals(keyAlgorithm); |
815 } |
1086 } |
816 |
1087 |
817 @Override |
1088 @Override |
818 public int hashCode() { |
1089 public int hashCode() { |
819 final int prime = 31; |
1090 final int prime = 31; |
820 int result = 1; |
1091 int result = 1; |
821 result = prime * result |
1092 result = prime * result |
822 + ((version == null) ? 0 : version.hashCode()); |
1093 + ((runtimeVersion == null) ? 0 : runtimeVersion.hashCode()); |
823 return result; |
1094 return result; |
824 } |
1095 } |
825 |
1096 |
826 @Override |
1097 @Override |
827 public boolean equals(Object obj) { |
1098 public boolean equals(Object obj) { |
830 if (obj == null) |
1101 if (obj == null) |
831 return false; |
1102 return false; |
832 if (getClass() != obj.getClass()) |
1103 if (getClass() != obj.getClass()) |
833 return false; |
1104 return false; |
834 JdkInfo other = (JdkInfo) obj; |
1105 JdkInfo other = (JdkInfo) obj; |
835 if (version == null) { |
1106 if (runtimeVersion == null) { |
836 if (other.version != null) |
1107 if (other.runtimeVersion != null) |
837 return false; |
1108 return false; |
838 } else if (!version.equals(other.version)) |
1109 } else if (!runtimeVersion.equals(other.runtimeVersion)) |
839 return false; |
1110 return false; |
840 return true; |
1111 return true; |
841 } |
1112 } |
|
1113 |
|
1114 @Override |
|
1115 public String toString() { |
|
1116 return "JdkInfo[" + runtimeVersion + ", " + jdkPath + "]"; |
|
1117 } |
842 } |
1118 } |
843 |
1119 |
844 private static class TsaInfo { |
1120 private static class TsaInfo { |
845 |
1121 |
846 private final int index; |
1122 private final int index; |
847 private final String tsaUrl; |
1123 private final String tsaUrl; |
848 private Set<String> digestList = new HashSet<String>(); |
1124 private Set<String> digestList = new HashSet<>(); |
849 |
1125 |
850 private TsaInfo(int index, String tsa) { |
1126 private TsaInfo(int index, String tsa) { |
851 this.index = index; |
1127 this.index = index; |
852 this.tsaUrl = tsa; |
1128 this.tsaUrl = tsa; |
853 } |
1129 } |
854 |
1130 |
855 private void addDigest(String digest) { |
1131 private void addDigest(String digest) { |
856 if (!ignore(digest)) { |
1132 digestList.add(digest); |
857 digestList.add(digest); |
|
858 } |
|
859 } |
|
860 |
|
861 private static boolean ignore(String digest) { |
|
862 return !SHA1.equalsIgnoreCase(digest) |
|
863 && !SHA256.equalsIgnoreCase(digest) |
|
864 && !SHA512.equalsIgnoreCase(digest); |
|
865 } |
1133 } |
866 |
1134 |
867 private boolean isDigestSupported(String digest) { |
1135 private boolean isDigestSupported(String digest) { |
868 return digest == null || digestList.isEmpty() |
1136 return digest == null || digestList.isEmpty() |
869 || digestList.contains(digest); |
1137 || digestList.contains(digest); |
870 } |
1138 } |
|
1139 |
|
1140 @Override |
|
1141 public String toString() { |
|
1142 return "TsaInfo[" + index + ", " + tsaUrl + "]"; |
|
1143 } |
871 } |
1144 } |
872 |
1145 |
873 private static class CertInfo { |
1146 private static class CertInfo { |
874 |
1147 |
875 private final String jdkVersion; |
1148 private static int certCounter; |
|
1149 |
|
1150 // nr distinguishes cert CNs in jarsigner -verify output |
|
1151 private final int nr = ++certCounter; |
|
1152 private final JdkInfo jdkInfo; |
876 private final String keyAlgorithm; |
1153 private final String keyAlgorithm; |
877 private final String digestAlgorithm; |
1154 private final String digestAlgorithm; |
878 private final int keySize; |
1155 private final int keySize; |
879 private final boolean expired; |
1156 private final boolean expired; |
880 |
1157 |
881 private CertInfo(String jdkVersion, String keyAlgorithm, |
1158 private CertInfo(JdkInfo jdkInfo, String keyAlgorithm, |
882 String digestAlgorithm, int keySize, boolean expired) { |
1159 String digestAlgorithm, int keySize, boolean expired) { |
883 this.jdkVersion = jdkVersion; |
1160 this.jdkInfo = jdkInfo; |
884 this.keyAlgorithm = keyAlgorithm; |
1161 this.keyAlgorithm = keyAlgorithm; |
885 this.digestAlgorithm = digestAlgorithm; |
1162 this.digestAlgorithm = digestAlgorithm; |
886 this.keySize = keySize; |
1163 this.keySize = keySize; |
887 this.expired = expired; |
1164 this.expired = expired; |
|
1165 } |
|
1166 |
|
1167 private String sigalg() { |
|
1168 return DEFAULT.equals(digestAlgorithm) ? null : expectedSigalg(); |
|
1169 } |
|
1170 |
|
1171 private String expectedSigalg() { |
|
1172 return (DEFAULT.equals(this.digestAlgorithm) ? this.digestAlgorithm |
|
1173 : "SHA-256").replace("-", "") + "with" + |
|
1174 keyAlgorithm + (EC.equals(keyAlgorithm) ? "DSA" : ""); |
|
1175 } |
|
1176 |
|
1177 private int expectedKeySize() { |
|
1178 if (keySize != 0) return keySize; |
|
1179 |
|
1180 // defaults |
|
1181 if (RSA.equals(keyAlgorithm) || DSA.equals(keyAlgorithm)) { |
|
1182 return 2048; |
|
1183 } else if (EC.equals(keyAlgorithm)) { |
|
1184 return 256; |
|
1185 } else { |
|
1186 throw new RuntimeException("problem determining key size"); |
|
1187 } |
888 } |
1188 } |
889 |
1189 |
890 @Override |
1190 @Override |
891 public int hashCode() { |
1191 public int hashCode() { |
892 final int prime = 31; |
1192 final int prime = 31; |
893 int result = 1; |
1193 int result = 1; |
894 result = prime * result |
1194 result = prime * result |
895 + ((digestAlgorithm == null) ? 0 : digestAlgorithm.hashCode()); |
1195 + (digestAlgorithm == null ? 0 : digestAlgorithm.hashCode()); |
896 result = prime * result + (expired ? 1231 : 1237); |
1196 result = prime * result + (expired ? 1231 : 1237); |
897 result = prime * result |
1197 result = prime * result |
898 + ((jdkVersion == null) ? 0 : jdkVersion.hashCode()); |
1198 + (jdkInfo == null ? 0 : jdkInfo.hashCode()); |
899 result = prime * result |
1199 result = prime * result |
900 + ((keyAlgorithm == null) ? 0 : keyAlgorithm.hashCode()); |
1200 + (keyAlgorithm == null ? 0 : keyAlgorithm.hashCode()); |
901 result = prime * result + keySize; |
1201 result = prime * result + keySize; |
902 return result; |
1202 return result; |
903 } |
1203 } |
904 |
1204 |
905 @Override |
1205 @Override |
1087 private SignItem status(Status status) { |
1397 private SignItem status(Status status) { |
1088 this.status = status; |
1398 this.status = status; |
1089 return this; |
1399 return this; |
1090 } |
1400 } |
1091 |
1401 |
|
1402 private SignItem unsignedJar(String unsignedJar) { |
|
1403 this.unsignedJar = unsignedJar; |
|
1404 return this; |
|
1405 } |
|
1406 |
1092 private SignItem signedJar(String signedJar) { |
1407 private SignItem signedJar(String signedJar) { |
1093 this.signedJar = signedJar; |
1408 this.signedJar = signedJar; |
1094 return this; |
1409 return this; |
1095 } |
1410 } |
1096 |
1411 |
|
1412 private SignItem addContentFiles(List<String> files) { |
|
1413 this.jarContents.addAll(files); |
|
1414 return this; |
|
1415 } |
|
1416 |
1097 private void addVerifyItem(VerifyItem verifyItem) { |
1417 private void addVerifyItem(VerifyItem verifyItem) { |
1098 verifyItems.add(verifyItem); |
1418 verifyItems.add(verifyItem); |
1099 } |
1419 } |
|
1420 |
|
1421 private boolean isErrorInclPrev() { |
|
1422 if (prevSign != null && prevSign.isErrorInclPrev()) { |
|
1423 System.out.println("SignItem.isErrorInclPrev: returning true from previous"); |
|
1424 return true; |
|
1425 } |
|
1426 |
|
1427 return status == Status.ERROR; |
|
1428 } |
|
1429 private List<String> toStringWithPrev(Function<SignItem,String> toStr) { |
|
1430 List<String> s = new ArrayList<>(); |
|
1431 if (prevSign != null) { |
|
1432 s.addAll(prevSign.toStringWithPrev(toStr)); |
|
1433 } |
|
1434 if (status != null) { // no status means jar creation or update item |
|
1435 s.add(toStr.apply(this)); |
|
1436 } |
|
1437 return s; |
|
1438 } |
1100 } |
1439 } |
1101 |
1440 |
1102 private static class VerifyItem { |
1441 private static class VerifyItem { |
1103 |
1442 |
|
1443 private VerifyItem prevVerify; |
|
1444 private CertInfo certInfo; |
1104 private JdkInfo jdkInfo; |
1445 private JdkInfo jdkInfo; |
1105 private Status status = Status.NONE; |
1446 private Status status = Status.NONE; |
1106 private Status delayStatus = Status.NONE; |
1447 private Status delayStatus = Status.NONE; |
1107 |
1448 |
1108 private static VerifyItem build(JdkInfo jdkInfo) { |
1449 private static VerifyItem build(JdkInfo jdkInfo) { |
1109 VerifyItem verifyItem = new VerifyItem(); |
1450 VerifyItem verifyItem = new VerifyItem(); |
1110 verifyItem.jdkInfo = jdkInfo; |
1451 verifyItem.jdkInfo = jdkInfo; |
1111 return verifyItem; |
1452 return verifyItem; |
1112 } |
1453 } |
1113 |
1454 |
|
1455 private VerifyItem certInfo(CertInfo certInfo) { |
|
1456 this.certInfo = certInfo; |
|
1457 return this; |
|
1458 } |
|
1459 |
|
1460 private void addSignerCertInfos(SignItem signItem) { |
|
1461 VerifyItem prevVerify = this; |
|
1462 CertInfo lastCertInfo = null; |
|
1463 while (signItem != null) { |
|
1464 // (signItem.certInfo == null) means create or update jar step |
|
1465 if (signItem.certInfo != null |
|
1466 && !signItem.certInfo.equals(lastCertInfo)) { |
|
1467 lastCertInfo = signItem.certInfo; |
|
1468 prevVerify = prevVerify.prevVerify = |
|
1469 build(jdkInfo).certInfo(signItem.certInfo); |
|
1470 } |
|
1471 signItem = signItem.prevSign; |
|
1472 } |
|
1473 } |
|
1474 |
1114 private VerifyItem status(Status status) { |
1475 private VerifyItem status(Status status) { |
1115 this.status = status; |
1476 this.status = status; |
1116 return this; |
1477 return this; |
1117 } |
1478 } |
1118 |
1479 |
|
1480 private boolean isErrorInclPrev() { |
|
1481 if (prevVerify != null && prevVerify.isErrorInclPrev()) { |
|
1482 System.out.println("VerifyItem.isErrorInclPrev: returning true from previous"); |
|
1483 return true; |
|
1484 } |
|
1485 |
|
1486 return status == Status.ERROR || delayStatus == Status.ERROR; |
|
1487 } |
|
1488 |
1119 private VerifyItem delayStatus(Status status) { |
1489 private VerifyItem delayStatus(Status status) { |
1120 this.delayStatus = status; |
1490 this.delayStatus = status; |
1121 return this; |
1491 return this; |
1122 } |
1492 } |
|
1493 |
|
1494 private List<String> toStringWithPrev( |
|
1495 Function<VerifyItem,String> toStr) { |
|
1496 List<String> s = new ArrayList<>(); |
|
1497 if (prevVerify != null) { |
|
1498 s.addAll(prevVerify.toStringWithPrev(toStr)); |
|
1499 } |
|
1500 s.add(toStr.apply(this)); |
|
1501 return s; |
|
1502 } |
1123 } |
1503 } |
1124 |
1504 |
1125 // The identifier for a specific signing. |
1505 // The identifier for a specific signing. |
1126 private static String signingId(SignItem signItem) { |
1506 private static String signingId(SignItem signItem) { |
1127 return signItem.signedJar; |
1507 return signItem.signedJar; |
1128 } |
1508 } |
1129 |
1509 |
1130 // The identifier for a specific verifying. |
1510 // The identifier for a specific verifying. |
1131 private static String verifyingId(SignItem signItem, VerifyItem verifyItem, |
1511 private static String verifyingId(SignItem signItem, VerifyItem verifyItem, |
1132 boolean delayVerify) { |
1512 boolean delayVerify) { |
1133 return "S_" + signingId(signItem) + "-" + (delayVerify ? "DV" : "V") |
1513 return signingId(signItem) + (delayVerify ? "-DV" : "-V") |
1134 + "_" + verifyItem.jdkInfo.version; |
1514 + "_" + verifyItem.jdkInfo.version + |
|
1515 (verifyItem.certInfo == null ? "" : "_" + verifyItem.certInfo); |
1135 } |
1516 } |
1136 |
1517 |
1137 private static String reportRow(SignItem signItem, VerifyItem verifyItem) { |
1518 private static String reportRow(SignItem signItem, VerifyItem verifyItem) { |
1138 List<String> values = new ArrayList<String>(); |
1519 List<String> values = new ArrayList<>(); |
1139 values.add(signItem.certInfo.toString()); |
1520 Consumer<Function<SignItem, String>> s_values_add = f -> { |
1140 values.add(signItem.version); |
1521 values.add(String.join("<br/><br/>", signItem.toStringWithPrev(f))); |
1141 values.add(null2Default(signItem.signatureAlgorithm, |
1522 }; |
1142 signItem.extractedSignatureAlgorithm)); |
1523 Consumer<Function<VerifyItem, String>> v_values_add = f -> { |
1143 values.add(signItem.tsaIndex == -1 |
1524 values.add(String.join("<br/><br/>", verifyItem.toStringWithPrev(f))); |
1144 ? "" |
1525 }; |
1145 : null2Default(signItem.tsaDigestAlgorithm, |
1526 s_values_add.accept(i -> i.unsignedJar + " -> " + i.signedJar); |
1146 signItem.extractedTsaDigestAlgorithm)); |
1527 s_values_add.accept(i -> i.certInfo.toString()); |
1147 values.add(signItem.tsaIndex == -1 ? "" : signItem.tsaIndex + ""); |
1528 s_values_add.accept(i -> i.jdkInfo.version); |
1148 values.add(HtmlHelper.anchorLink( |
1529 s_values_add.accept(i -> i.certInfo.expectedSigalg()); |
|
1530 s_values_add.accept(i -> |
|
1531 null2Default(i.digestAlgorithm, i.expectedDigestAlg())); |
|
1532 s_values_add.accept(i -> i.tsaIndex == -1 ? "" : |
|
1533 null2Default(i.tsaDigestAlgorithm, i.expectedTsaDigestAlg())); |
|
1534 s_values_add.accept(i -> i.tsaIndex == -1 ? "" : i.tsaIndex + ""); |
|
1535 s_values_add.accept(i -> HtmlHelper.anchorLink( |
1149 PhaseOutputStream.fileName(PhaseOutputStream.Phase.SIGNING), |
1536 PhaseOutputStream.fileName(PhaseOutputStream.Phase.SIGNING), |
1150 signingId(signItem), |
1537 signingId(i), |
1151 signItem.status.toString())); |
1538 "" + i.status)); |
1152 values.add(verifyItem.jdkInfo.version); |
1539 values.add(verifyItem.jdkInfo.version); |
1153 values.add(HtmlHelper.anchorLink( |
1540 v_values_add.accept(i -> |
|
1541 i.certInfo == null ? "no alias" : "" + i.certInfo); |
|
1542 v_values_add.accept(i -> HtmlHelper.anchorLink( |
1154 PhaseOutputStream.fileName(PhaseOutputStream.Phase.VERIFYING), |
1543 PhaseOutputStream.fileName(PhaseOutputStream.Phase.VERIFYING), |
1155 verifyingId(signItem, verifyItem, false), |
1544 verifyingId(signItem, i, false), |
1156 verifyItem.status.toString())); |
1545 "" + i.status.toString())); |
1157 if (DELAY_VERIFY) { |
1546 if (DELAY_VERIFY) { |
1158 values.add(HtmlHelper.anchorLink( |
1547 v_values_add.accept(i -> HtmlHelper.anchorLink( |
1159 PhaseOutputStream.fileName( |
1548 PhaseOutputStream.fileName( |
1160 PhaseOutputStream.Phase.DELAY_VERIFYING), |
1549 PhaseOutputStream.Phase.DELAY_VERIFYING), |
1161 verifyingId(signItem, verifyItem, true), |
1550 verifyingId(signItem, verifyItem, true), |
1162 verifyItem.delayStatus.toString())); |
1551 verifyItem.delayStatus.toString())); |
1163 } |
1552 } |
1164 values.add(isFailed(signItem, verifyItem) ? "X" : ""); |
1553 values.add(isFailed(signItem, verifyItem) ? "X" : ""); |
1165 return HtmlHelper.htmlRow(values.toArray(new String[values.size()])); |
1554 return HtmlHelper.htmlRow(values.toArray(new String[values.size()])); |
1166 } |
1555 } |
1167 |
1556 |
1168 private static boolean isFailed(SignItem signItem, |
1557 private static boolean isFailed(SignItem signItem, VerifyItem verifyItem) { |
1169 VerifyItem verifyItem) { |
1558 System.out.println("isFailed: signItem = " + signItem + ", verifyItem = " + verifyItem); |
1170 return signItem.status == Status.ERROR |
1559 // TODO: except known failing cases |
1171 || verifyItem.status == Status.ERROR |
1560 |
1172 || verifyItem.delayStatus == Status.ERROR; |
1561 // Note about isAtLeastMajorVersion in the following conditions: |
|
1562 // signItem.jdkInfo is the jdk which signed the jar last and |
|
1563 // signItem.prevSign.jdkInfo is the jdk which signed the jar first |
|
1564 // assuming only two successive signatures as there actually are now. |
|
1565 // the first signature always works and always has. subject here is |
|
1566 // the update of an already signed jar. the following conditions always |
|
1567 // depend on the second jdk that updated the jar with another signature |
|
1568 // and the first one (signItem(.prevSign)+.jdkInfo) can be ignored. |
|
1569 // this is different for verifyItem. verifyItem.prevVerify refers to |
|
1570 // the first signature created by signItem(.prevSign)+.jdkInfo. |
|
1571 // all verifyItem(.prevVerify)+.jdkInfo however point always to the same |
|
1572 // jdk, only their certInfo is different. the same signatures are |
|
1573 // verified with different jdks in different top-level VerifyItems |
|
1574 // attached directly to signItem.verifyItems and not to |
|
1575 // verifyItem.prevVerify. |
|
1576 |
|
1577 // ManifestDigester fails to parse manifests ending in '\r' with |
|
1578 // IndexOutOfBoundsException at ManifestDigester.java:87 before 8217375 |
|
1579 if (signItem.signedJar.startsWith("eofr") |
|
1580 && !signItem.jdkInfo.isAtLeastMajorVersion(13) |
|
1581 && !verifyItem.jdkInfo.isAtLeastMajorVersion(13)) return false; |
|
1582 |
|
1583 // if there is no blank line after main attributes, JarSigner adds |
|
1584 // individual sections nevertheless without being properly delimited |
|
1585 // in JarSigner.java:777..790 without checking for blank line |
|
1586 // before 8217375 |
|
1587 // if (signItem.signedJar.startsWith("eofn-") |
|
1588 // && signItem.signedJar.contains("-addfile-") |
|
1589 // && !signItem.jdkInfo.isAtLeastMajorVersion(13) |
|
1590 // && !verifyItem.jdkInfo.isAtLeastMajorVersion(13)) return false; // FIXME |
|
1591 |
|
1592 // System.out.println("isFailed: signItem.isErrorInclPrev() " + signItem.isErrorInclPrev()); |
|
1593 // System.out.println("isFailed: verifyItem.isErrorInclPrev() " + verifyItem.isErrorInclPrev()); |
|
1594 boolean isFailed = signItem.isErrorInclPrev() || verifyItem.isErrorInclPrev(); |
|
1595 System.out.println("isFailed: returning " + isFailed); |
|
1596 return isFailed; |
1173 } |
1597 } |
1174 |
1598 |
1175 // If a value is null, then displays the default value or N/A. |
1599 // If a value is null, then displays the default value or N/A. |
1176 private static String null2Default(String value, String defaultValue) { |
1600 private static String null2Default(String value, String defaultValue) { |
1177 return value == null |
1601 return value != null ? value : |
1178 ? DEFAULT + "(" + (defaultValue == null |
1602 DEFAULT + "(" + (defaultValue == null |
1179 ? "N/A" |
1603 ? "N/A" |
1180 : defaultValue) + ")" |
1604 : defaultValue) + ")"; |
1181 : value; |
1605 } |
1182 } |
1606 |
1183 } |
1607 } |