From 05ea083b0563ddacf3e38dc329ba00dc4bac9b29 Mon Sep 17 00:00:00 2001 From: Eirik Bjorsnos Date: Mon, 6 Feb 2023 15:43:53 +0000 Subject: [PATCH] 8301167: Update VerifySignedJar to actually exercise and test verification Reviewed-by: weijun --- .../util/jar/JarFile/VerifySignedJar.java | 124 ++++++++++++++---- test/jdk/java/util/jar/JarFile/thawjar.jar | Bin 2441 -> 0 bytes 2 files changed, 98 insertions(+), 26 deletions(-) delete mode 100644 test/jdk/java/util/jar/JarFile/thawjar.jar diff --git a/test/jdk/java/util/jar/JarFile/VerifySignedJar.java b/test/jdk/java/util/jar/JarFile/VerifySignedJar.java index 1f4db736e8f..bd5490502c9 100644 --- a/test/jdk/java/util/jar/JarFile/VerifySignedJar.java +++ b/test/jdk/java/util/jar/JarFile/VerifySignedJar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,48 +23,120 @@ /** * @test + * @library /test/lib + * @modules java.base/sun.security.x509 + * @modules java.base/sun.security.tools.keytool * @bug 4419266 4842702 * @summary Make sure verifying signed Jar doesn't throw SecurityException */ -import java.io.File; -import java.util.jar.JarFile; +import jdk.security.jarsigner.JarSigner; +import sun.security.tools.keytool.CertAndKeyGen; +import sun.security.x509.X500Name; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; -import java.util.Enumeration; +import java.util.zip.ZipFile; + +import static jdk.test.lib.Utils.runAndCheckException; + public class VerifySignedJar { - private static void Unreached (Object o) - throws Exception - { - // Should never get here - throw new Exception ("Expected exception was not thrown"); - } public static void main(String[] args) throws Exception { - File f = new File(System.getProperty("test.src", "."), "thawjar.jar"); - JarFile jf = new JarFile(f); - try { - // Read entries via Enumeration - for (Enumeration e = jf.entries(); e.hasMoreElements();) - jf.getInputStream((ZipEntry) e.nextElement()); - // Read entry by name - ZipEntry ze = jf.getEntry("getprop.class"); - JarEntry je = jf.getJarEntry("getprop.class"); + Path j = createJar(); + Path s = signJar(j, keyEntry("cn=duke")); - // Make sure we throw NPE on null objects - try { Unreached (jf.getEntry(null)); } - catch (NullPointerException e) {} + try (JarFile jf = new JarFile(s.toFile())) { - try { Unreached (jf.getJarEntry(null)); } - catch (NullPointerException e) {} + for (JarEntry e: Collections.list(jf.entries())) { + // Reading entry to trigger verification + jf.getInputStream(e).transferTo(OutputStream.nullOutputStream()); + // Check that all regular files are signed by duke + if (!e.getName().startsWith("META-INF/")) { + checkSignedBy(e, "cn=duke"); + } + } - try { Unreached (jf.getInputStream(null)); } - catch (NullPointerException e) {} + // Read ZIP and JAR entries by name + Objects.requireNonNull(jf.getEntry("getprop.class")); + Objects.requireNonNull(jf.getJarEntry("getprop.class")); + + // Make sure we throw NPE on null parameters + runAndCheckException(() -> jf.getEntry(null), NullPointerException.class); + runAndCheckException(() -> jf.getJarEntry(null), NullPointerException.class); + runAndCheckException(() -> jf.getInputStream(null), NullPointerException.class); } catch (SecurityException se) { throw new Exception("Got SecurityException when verifying signed " + "jar:" + se); } } + + // Check that a JAR entry is signed by an expected DN + private static void checkSignedBy(JarEntry e, String expectedDn) throws Exception { + Certificate[] certs = e.getCertificates(); + if (certs == null || certs.length == 0) { + throw new Exception("JarEntry has no certificates: " + e.getName()); + } + + if (certs[0] instanceof X509Certificate x) { + String name = x.getSubjectX500Principal().getName(); + if (!name.equalsIgnoreCase(expectedDn)) { + throw new Exception("Expected entry signed by %s, was %s".formatted(name, expectedDn)); + } + } else { + throw new Exception("Expected JarEntry.getCertificate to return X509Certificate"); + } + } + + private static Path createJar() throws Exception { + Path j = Path.of("unsigned.jar"); + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(j))){ + out.putNextEntry(new JarEntry("getprop.class")); + out.write(new byte[] {(byte) 0XCA, (byte) 0XFE, (byte) 0XBA, (byte) 0XBE}); + } + return j; + } + + private static Path signJar(Path j, KeyStore.PrivateKeyEntry entry) throws Exception { + Path s = Path.of("signed.jar"); + + JarSigner signer = new JarSigner.Builder(entry) + .signerName("zigbert") + .digestAlgorithm("SHA-256") + .signatureAlgorithm("SHA256withRSA") + .build(); + + try (ZipFile zip = new ZipFile(j.toFile()); + OutputStream out = Files.newOutputStream(s)) { + signer.sign(zip, out); + } + + return s; + } + + private static KeyStore.PrivateKeyEntry keyEntry(String dname) throws Exception { + + CertAndKeyGen gen = new CertAndKeyGen("RSA", "SHA256withRSA"); + + gen.generate(1048); // Small key size makes test run faster + + var oneDay = TimeUnit.DAYS.toSeconds(1); + Certificate cert = gen.getSelfCertificate(new X500Name(dname), oneDay); + + return new KeyStore.PrivateKeyEntry(gen.getPrivateKey(), + new Certificate[] {cert}); + } } diff --git a/test/jdk/java/util/jar/JarFile/thawjar.jar b/test/jdk/java/util/jar/JarFile/thawjar.jar deleted file mode 100644 index 49d247cb40d9a5dc2e4d9c65be47b5cccd709a02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2441 zcmZuz2{;s58y+TRlC4I}DA`?VVwjToiIHn-46-E4RSd%nW1Hl(gbYH;l5j0! zkFxw2V}vM@$S!5f}{P&#aJI`~L?>*1?&iB0EYmNaP0B{-@fTqqU=0(b$ z7vKf}C_n%}fV1l6dfms*+eaDa=|m>SS=xH{qlI^?QJ*!uGV;Z8Qo9b{Q^(eZ+dVm8 zt(#&OmdqE`Q9}2PJ@T$%FsxxI;CD7A8a)A8dMr#g7C)H|f-kUM29a;h4lXY0BsK|V z%?}Ou+H^n!?8R@Ku{t|&W@K;)-Kqyvni80!*|;>L#bbE+;V{NE+mBwfG9SxCe~5l@ zS}jEYA}?Z+Tuz)&<>Nn;x3X-w8#OpQLMHmd>a*w_RK=7rBs?Voy2u>Ppn^^h#&3w2 z!+#4%hZd|{jVPVKTZXwj*XVq(a@wp{QbI{?XJp->uBXhe30ELrVl?DC$IOz~ekViy zb8*3rzZ3XE8j4T&&8#6#(@jQe+Ku!I>aQb6S{}A;A)c=$9oFxzeb2UyS1Hjr;`Dk_ zA}2IAP#9dQYZ^xlptw>$fCDd>j^u!CkBo-jlDhB~*K5RQ0Jo@eZxQGQ;zogJW%GZiOL5t<@%UIl?e35ZIAmh-@vD2=_*tA zrJiS-1?jO}wm@q_n}vsHDcbp4v@TfPx;Fl~GcK%LT3s_jAhZXRI9TXKN-i?O+-bfP zb59&hhj`S?C!3j%7exGJ(Y9dYK0e>l${m#~$-RhjDc>lL1=?fG$XcT}@)F^AZK zL6p`S0w_3H``5)+fg}*O>WRnUNhE4^-*yJNvh+jLFN>0HD>u|q7><;zH~Bz`|uh0b6&glMeXe1v`B0JEcwal*V9Rl zYqgPC-j-{?pF=RU1MA-9Jl%;40D$~&2>u|rIlKB@S0>}*;&jdKBA^kAJ4eSZ;jZES zRg>tW_&BLvP?RYWAZu@9e66h-K2a)$TbpQmm(*Z3O0ijS7&bjLYwT`WNEgb|G{Bs` zcq-m>XsZd-cG9{vWmCGRvC#nY`pkr9oRGZ%;uqmOYA%WzIYaG8ZjDx(Xu;Iqbz?%q zV9g#6>tY^V4KPfu-j6`#+w{Ff?1NWwG zdUVr&*J0oImMPgX)z^M6{xj=J!<=u3xrKTQNXfIv`qPQ|SG3z-Bw3@yUM-spxJqu7 zeouVnZht}b`EK>vJw^ph(*4p4?94OvsJ+ceboS5TI*4X34suw=m1keZ{0NsH*$L_H zjCGkDAg;P^z3gJVtb7=Cmhmv^bwX5PAN5^GQk->0LYi@ON^{7uoJe0?2)UMHWxA05 zEFP9gtceMWU*+ntrIr*#1;GEz39gK$Rv?<1D>|*D@r!7# zI3Elg681J6rRxw*-Cohsvjq~U(9|*`BLn9^!sHFZ)eJpGGgP@wgV95>j(q_AwY;824X2YsVFrl%qS1>6A*Wk2}GWO zwvP+rx$R^;rNGO_Q^^D_M#*O2W`rNFn($Z@Z|aQFSG| z>tnpZ-rEBkpC{Cd(gGBMbPBDe<=za;5Y5`)>IH1%hxbMNQ*|Gir3L<5 zt5xf=GpP?OmcI7nXr22&PvGgd!iw>bGRbK-Krcbc_q~d@ajBj6aY1iYu)ns=vI%3n z9v-u!D&vQ`imTm?*R<2Nr`rpPnIucWx{+_yFQlKk>?VoOXFWDXY1dd^Z;$6BsPei! z(_!X`x;B23(#hUx>1U%XYsA%69o5{OwLq-fym4+3#wS8Wgd$s9bc^pCxFAWF_It~1 z(&q!atar<21_`>L2gK?1D*|1^;4wMSn8*}i`$55)HV;pQ#dH{=X3!*Z(@oMY-HqV+ z0rRv!IGKFoa?q?q#H3Jaxv83+Z=74(>S_-TSHm`%D8HAze6yzr