< prev index next >

test/sun/security/tools/jarsigner/TimestampCheck.java

Print this page
rev 15871 : 8163304: jarsigner -verbose -verify should print the algorithms used to sign the jar

*** 20,48 **** * or visit www.oracle.com if you need additional information or have any * questions. */ import com.sun.net.httpserver.*; - import java.io.BufferedReader; import java.io.ByteArrayOutputStream; ! import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; - import java.io.InputStreamReader; import java.io.OutputStream; import java.math.BigInteger; import java.net.InetSocketAddress; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.jar.JarEntry; import java.util.jar.JarFile; - import java.util.Locale; import sun.security.pkcs.ContentInfo; import sun.security.pkcs.PKCS7; import sun.security.pkcs.PKCS9Attribute; import sun.security.pkcs.SignerInfo; import sun.security.timestamp.TimestampToken; --- 20,52 ---- * or visit www.oracle.com if you need additional information or have any * questions. */ import com.sun.net.httpserver.*; import java.io.ByteArrayOutputStream; ! import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.InetSocketAddress; + import java.nio.file.Files; + import java.nio.file.Paths; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.Certificate; import java.security.cert.X509Certificate; + import java.util.ArrayList; + import java.util.Arrays; import java.util.Calendar; + import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; + import jdk.testlibrary.*; + import jdk.testlibrary.JarUtils; import sun.security.pkcs.ContentInfo; import sun.security.pkcs.PKCS7; import sun.security.pkcs.PKCS9Attribute; import sun.security.pkcs.SignerInfo; import sun.security.timestamp.TimestampToken;
*** 50,64 **** import sun.security.util.DerValue; import sun.security.util.ObjectIdentifier; import sun.security.x509.AlgorithmId; import sun.security.x509.X500Name; public class TimestampCheck { - static final String TSKS = "tsks"; - static final String JAR = "old.jar"; ! static final String defaultPolicyId = "2.3.4.5"; static class Handler implements HttpHandler, AutoCloseable { private final HttpServer httpServer; private final String keystore; --- 54,79 ---- import sun.security.util.DerValue; import sun.security.util.ObjectIdentifier; import sun.security.x509.AlgorithmId; import sun.security.x509.X500Name; + /* + * @test + * @bug 6543842 6543440 6939248 8009636 8024302 8163304 + * @summary checking response of timestamp + * @modules java.base/sun.security.pkcs + * java.base/sun.security.timestamp + * java.base/sun.security.x509 + * java.base/sun.security.util + * java.base/sun.security.tools.keytool + * @library /lib/testlibrary + * @run main/timeout=600 TimestampCheck + */ public class TimestampCheck { ! static final String defaultPolicyId = "2.3.4"; ! static String host = null; static class Handler implements HttpHandler, AutoCloseable { private final HttpServer httpServer; private final String keystore;
*** 73,87 **** } byte[] input = new byte[len]; t.getRequestBody().read(input); try { ! int path = 0; ! if (t.getRequestURI().getPath().length() > 1) { ! path = Integer.parseInt( ! t.getRequestURI().getPath().substring(1)); ! } byte[] output = sign(input, path); Headers out = t.getResponseHeaders(); out.set("Content-Type", "application/timestamp-reply"); t.sendResponseHeaders(200, output.length); --- 88,98 ---- } byte[] input = new byte[len]; t.getRequestBody().read(input); try { ! String path = t.getRequestURI().getPath().substring(1); byte[] output = sign(input, path); Headers out = t.getResponseHeaders(); out.set("Content-Type", "application/timestamp-reply"); t.sendResponseHeaders(200, output.length);
*** 95,122 **** } /** * @param input The data to sign * @param path different cases to simulate, impl on URL path - * 0: normal - * 1: Missing nonce - * 2: Different nonce - * 3: Bad digets octets in messageImprint - * 4: Different algorithmId in messageImprint - * 5: whole chain in cert set - * 6: extension is missing - * 7: extension is non-critical - * 8: extension does not have timestamping - * 9: no cert in response - * 10: normal - * 11: always return default policy id - * 12: normal - * otherwise: normal * @returns the signed */ ! byte[] sign(byte[] input, int path) throws Exception { ! // Read TSRequest DerValue value = new DerValue(input); System.err.println("\nIncoming Request\n==================="); System.err.println("Version: " + value.data.getInteger()); DerValue messageImprint = value.data.getDerValue(); AlgorithmId aid = AlgorithmId.parse( --- 106,119 ---- } /** * @param input The data to sign * @param path different cases to simulate, impl on URL path * @returns the signed */ ! byte[] sign(byte[] input, String path) throws Exception { ! DerValue value = new DerValue(input); System.err.println("\nIncoming Request\n==================="); System.err.println("Version: " + value.data.getInteger()); DerValue messageImprint = value.data.getDerValue(); AlgorithmId aid = AlgorithmId.parse(
*** 136,175 **** policyId = v.getOID(); System.err.println("PolicyID: " + policyId); } } - // Write TSResponse System.err.println("\nResponse\n==================="); ! KeyStore ks = KeyStore.getInstance("JKS"); ! try (FileInputStream fis = new FileInputStream(keystore)) { ! ks.load(fis, "changeit".toCharArray()); ! } String alias = "ts"; ! if (path == 6) alias = "tsbad1"; ! if (path == 7) alias = "tsbad2"; ! if (path == 8) alias = "tsbad3"; ! if (path == 11) { policyId = new ObjectIdentifier(defaultPolicyId); } DerOutputStream statusInfo = new DerOutputStream(); statusInfo.putInteger(0); - DerOutputStream token = new DerOutputStream(); AlgorithmId[] algorithms = {aid}; Certificate[] chain = ks.getCertificateChain(alias); ! X509Certificate[] signerCertificateChain = null; X509Certificate signer = (X509Certificate)chain[0]; ! if (path == 5) { // Only case 5 uses full chain signerCertificateChain = new X509Certificate[chain.length]; for (int i=0; i<chain.length; i++) { signerCertificateChain[i] = (X509Certificate)chain[i]; } ! } else if (path == 9) { signerCertificateChain = new X509Certificate[0]; } else { signerCertificateChain = new X509Certificate[1]; signerCertificateChain[0] = (X509Certificate)chain[0]; } --- 133,169 ---- policyId = v.getOID(); System.err.println("PolicyID: " + policyId); } } System.err.println("\nResponse\n==================="); ! KeyStore ks = KeyStore.getInstance( ! new File(keystore), "changeit".toCharArray()); String alias = "ts"; ! if (path.startsWith("bad") || path.equals("weak")) { ! alias = "ts" + path; ! } ! if (path.equals("diffpolicy")) { policyId = new ObjectIdentifier(defaultPolicyId); } DerOutputStream statusInfo = new DerOutputStream(); statusInfo.putInteger(0); AlgorithmId[] algorithms = {aid}; Certificate[] chain = ks.getCertificateChain(alias); ! X509Certificate[] signerCertificateChain; X509Certificate signer = (X509Certificate)chain[0]; ! ! if (path.equals("fullchain")) { // Only case 5 uses full chain signerCertificateChain = new X509Certificate[chain.length]; for (int i=0; i<chain.length; i++) { signerCertificateChain[i] = (X509Certificate)chain[i]; } ! } else if (path.equals("nocert")) { signerCertificateChain = new X509Certificate[0]; } else { signerCertificateChain = new X509Certificate[1]; signerCertificateChain[0] = (X509Certificate)chain[0]; }
*** 177,191 **** DerOutputStream tst = new DerOutputStream(); tst.putInteger(1); tst.putOID(policyId); ! if (path != 3 && path != 4) { tst.putDerValue(messageImprint); } else { byte[] data = messageImprint.toByteArray(); ! if (path == 4) { data[6] = (byte)0x01; } else { data[data.length-1] = (byte)0x01; data[data.length-2] = (byte)0x02; data[data.length-3] = (byte)0x03; --- 171,185 ---- DerOutputStream tst = new DerOutputStream(); tst.putInteger(1); tst.putOID(policyId); ! if (!path.equals("baddigest") && !path.equals("diffalg")) { tst.putDerValue(messageImprint); } else { byte[] data = messageImprint.toByteArray(); ! if (path.equals("diffalg")) { data[6] = (byte)0x01; } else { data[data.length-1] = (byte)0x01; data[data.length-2] = (byte)0x02; data[data.length-3] = (byte)0x03;
*** 196,219 **** tst.putInteger(1); Calendar cal = Calendar.getInstance(); tst.putGeneralizedTime(cal.getTime()); ! if (path == 2) { tst.putInteger(1234); ! } else if (path == 1) { ! // do nothing } else { tst.putInteger(nonce); } DerOutputStream tstInfo = new DerOutputStream(); tstInfo.write(DerValue.tag_Sequence, tst); DerOutputStream tstInfo2 = new DerOutputStream(); tstInfo2.putOctetString(tstInfo.toByteArray()); Signature sig = Signature.getInstance("SHA1withRSA"); sig.initSign((PrivateKey)(ks.getKey( alias, "changeit".toCharArray()))); sig.update(tstInfo.toByteArray()); --- 190,215 ---- tst.putInteger(1); Calendar cal = Calendar.getInstance(); tst.putGeneralizedTime(cal.getTime()); ! if (path.equals("diffnonce")) { tst.putInteger(1234); ! } else if (path.equals("nononce")) { ! // no noce } else { tst.putInteger(nonce); } DerOutputStream tstInfo = new DerOutputStream(); tstInfo.write(DerValue.tag_Sequence, tst); DerOutputStream tstInfo2 = new DerOutputStream(); tstInfo2.putOctetString(tstInfo.toByteArray()); + // Always use the same algorithm at timestamp signing + // so it is different from the hash algorithm. Signature sig = Signature.getInstance("SHA1withRSA"); sig.initSign((PrivateKey)(ks.getKey( alias, "changeit".toCharArray()))); sig.update(tstInfo.toByteArray());
*** 227,242 **** System.err.println(signer.getSerialNumber()); SignerInfo signerInfo = new SignerInfo( new X500Name(signer.getIssuerX500Principal().getName()), signer.getSerialNumber(), ! aid, AlgorithmId.get("RSA"), sig.sign()); SignerInfo[] signerInfos = {signerInfo}; ! PKCS7 p7 = ! new PKCS7(algorithms, contentInfo, signerCertificateChain, ! signerInfos); ByteArrayOutputStream p7out = new ByteArrayOutputStream(); p7.encodeSignedData(p7out); DerOutputStream response = new DerOutputStream(); response.write(DerValue.tag_Sequence, statusInfo); --- 223,237 ---- System.err.println(signer.getSerialNumber()); SignerInfo signerInfo = new SignerInfo( new X500Name(signer.getIssuerX500Principal().getName()), signer.getSerialNumber(), ! AlgorithmId.get("SHA-1"), AlgorithmId.get("RSA"), sig.sign()); SignerInfo[] signerInfos = {signerInfo}; ! PKCS7 p7 = new PKCS7(algorithms, contentInfo, ! signerCertificateChain, signerInfos); ByteArrayOutputStream p7out = new ByteArrayOutputStream(); p7.encodeSignedData(p7out); DerOutputStream response = new DerOutputStream(); response.write(DerValue.tag_Sequence, statusInfo);
*** 292,346 **** public void close() throws Exception { stop(); } } ! public static void main(String[] args) throws Exception { ! try (Handler tsa = Handler.init(0, TSKS);) { ! tsa.start(); ! int port = tsa.getPort(); ! String cmd; ! // Use -J-Djava.security.egd=file:/dev/./urandom to speed up ! // nonce generation in timestamping request. Not avaibale on ! // Windows and defaults to thread seed generator, not too bad. ! if (System.getProperty("java.home").endsWith("jre")) { ! cmd = System.getProperty("java.home") + "/../bin/jarsigner"; ! } else { ! cmd = System.getProperty("java.home") + "/bin/jarsigner"; ! } ! cmd += " " + System.getProperty("test.tool.vm.opts") ! + " -J-Djava.security.egd=file:/dev/./urandom" ! + " -J-Duser.language=en -J-Duser.country=US" ! + " -debug -keystore " + TSKS + " -storepass changeit" ! + " -tsa http://localhost:" + port + "/%d" ! + " -signedjar new_%d.jar " + JAR + " old"; if (args.length == 0) { // Run this test ! jarsigner(cmd, 0, true); // Success, normal call ! jarsigner(cmd, 1, false); // These 4 should fail ! jarsigner(cmd, 2, false); ! jarsigner(cmd, 3, false); ! jarsigner(cmd, 4, false); ! jarsigner(cmd, 5, true); // Success, 6543440 solved. ! jarsigner(cmd, 6, false); // tsbad1 ! jarsigner(cmd, 7, false); // tsbad2 ! jarsigner(cmd, 8, false); // tsbad3 ! jarsigner(cmd, 9, false); // no cert in timestamp ! jarsigner(cmd + " -tsapolicyid 1.2.3.4", 10, true); ! checkTimestamp("new_10.jar", "1.2.3.4", "SHA-256"); ! jarsigner(cmd + " -tsapolicyid 1.2.3.5", 11, false); ! jarsigner(cmd + " -tsadigestalg SHA", 12, true); ! checkTimestamp("new_12.jar", defaultPolicyId, "SHA-1"); } else { // Run as a standalone server System.err.println("Press Enter to quit server"); System.in.read(); } } } static void checkTimestamp(String file, String policyId, String digestAlg) throws Exception { try (JarFile jf = new JarFile(file)) { JarEntry je = jf.getJarEntry("META-INF/OLD.RSA"); try (InputStream is = jf.getInputStream(je)) { --- 287,466 ---- public void close() throws Exception { stop(); } } ! public static void main(String[] args) throws Throwable { ! prepare(); ! try (Handler tsa = Handler.init(0, "tsks");) { ! tsa.start(); ! int port = tsa.getPort(); ! host = "http://localhost:" + port + "/"; if (args.length == 0) { // Run this test ! sign("none") ! .shouldContain("is not timestamped") ! .shouldHaveExitValue(0); ! ! sign("badku") ! .shouldHaveExitValue(0); ! checkBadKU("badku.jar"); ! ! sign("normal") ! .shouldNotContain("is not timestamped") ! .shouldHaveExitValue(0); ! ! sign("nononce") ! .shouldHaveExitValue(1); ! sign("diffnonce") ! .shouldHaveExitValue(1); ! sign("baddigest") ! .shouldHaveExitValue(1); ! sign("diffalg") ! .shouldHaveExitValue(1); ! sign("fullchain") ! .shouldHaveExitValue(0); // Success, 6543440 solved. ! sign("bad1") ! .shouldHaveExitValue(1); ! sign("bad2") ! .shouldHaveExitValue(1); ! sign("bad3") ! .shouldHaveExitValue(1); ! sign("nocert") ! .shouldHaveExitValue(1); ! ! sign("policy", "-tsapolicyid", "1.2.3") ! .shouldHaveExitValue(0); ! checkTimestamp("policy.jar", "1.2.3", "SHA-256"); ! ! sign("diffpolicy", "-tsapolicyid", "1.2.3") ! .shouldHaveExitValue(1); ! ! sign("tsaalg", "-tsadigestalg", "SHA") ! .shouldHaveExitValue(0); ! checkTimestamp("tsaalg.jar", defaultPolicyId, "SHA-1"); ! ! sign("weak", "-digestalg", "MD2", ! "-sigalg", "MD2withRSA", "-tsadigestalg", "MD2") ! .shouldHaveExitValue(0) ! .shouldMatch("MD2.*-digestalg.*risk") ! .shouldMatch("MD2.*-tsadigestalg.*risk") ! .shouldMatch("MD2withRSA.*-sigalg.*risk"); ! checkWeak("weak.jar"); ! ! // Using MD5 is a warning in signing but still accepted ! // as signed in verification ! sign("semiweak", "-digestalg", "MD5", ! "-sigalg", "MD5withRSA", "-tsadigestalg", "MD5") ! .shouldHaveExitValue(0) ! .shouldMatch("MD5.*-digestalg.*risk") ! .shouldMatch("MD5.*-tsadigestalg.*risk") ! .shouldMatch("MD5withRSA.*-sigalg.*risk"); ! verify("semiweak.jar") ! .shouldHaveExitValue(0) ! .shouldContain("jar verified"); ! ! // When .SF or .RSA is missing or invalid ! checkMissingOrInvalidFiles("normal.jar"); } else { // Run as a standalone server System.err.println("Press Enter to quit server"); System.in.read(); } } } + private static void checkMissingOrInvalidFiles(String s) + throws Throwable { + JarUtils.updateJar(s, "1.jar", "-", "META-INF/OLD.SF"); + verify("1.jar", "-verbose") + .shouldHaveExitValue(0) + .shouldContain("treated as unsigned") + .shouldContain("Missing signature-related file META-INF/OLD.SF"); + JarUtils.updateJar(s, "2.jar", "-", "META-INF/OLD.RSA"); + verify("2.jar", "-verbose") + .shouldHaveExitValue(0) + .shouldContain("treated as unsigned") + .shouldContain("Missing block file for signature-related file META-INF/OLD.SF"); + JarUtils.updateJar(s, "3.jar", "META-INF/OLD.SF"); + verify("3.jar", "-verbose") + .shouldHaveExitValue(0) + .shouldContain("treated as unsigned") + .shouldContain("Unparsable signature-related file META-INF/OLD.SF"); + JarUtils.updateJar(s, "4.jar", "META-INF/OLD.RSA"); + verify("4.jar", "-verbose") + .shouldHaveExitValue(0) + .shouldContain("treated as unsigned") + .shouldContain("Unparsable signature-related file META-INF/OLD.RSA"); + } + + static OutputAnalyzer jarsigner(List<String> extra) + throws Throwable { + JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jarsigner") + .addVMArg("-Duser.language=en") + .addVMArg("-Duser.country=US") + .addToolArg("-keystore") + .addToolArg("tsks") + .addToolArg("-storepass") + .addToolArg("changeit"); + for (String s : extra) { + if (s.startsWith("-J")) { + launcher.addVMArg(s.substring(2)); + } else { + launcher.addToolArg(s); + } + } + return ProcessTools.executeCommand(launcher.getCommand()); + } + + static OutputAnalyzer verify(String file, String... extra) + throws Throwable { + List<String> args = new ArrayList<>(); + args.add("-verify"); + args.add(file); + args.addAll(Arrays.asList(extra)); + return jarsigner(args); + } + + static void checkBadKU(String file) throws Throwable { + verify(file) + .shouldHaveExitValue(0) + .shouldContain("treated as unsigned") + .shouldContain("re-run jarsigner with debug enabled"); + verify(file, "-verbose") + .shouldHaveExitValue(0) + .shouldContain("Signed by") + .shouldContain("treated as unsigned") + .shouldContain("re-run jarsigner with debug enabled"); + verify(file, "-J-Djava.security.debug=jar") + .shouldHaveExitValue(0) + .shouldContain("SignatureException: Key usage restricted") + .shouldContain("treated as unsigned") + .shouldContain("re-run jarsigner with debug enabled"); + } + + static void checkWeak(String file) throws Throwable { + verify(file) + .shouldHaveExitValue(0) + .shouldContain("treated as unsigned") + .shouldMatch("weak algorithm that is now disabled.") + .shouldMatch("Re-run jarsigner with the -verbose option for more details"); + verify(file, "-verbose") + .shouldHaveExitValue(0) + .shouldContain("treated as unsigned") + .shouldMatch("weak algorithm that is now disabled by") + .shouldMatch("Digest algorithm: .*weak") + .shouldMatch("Signature algorithm: .*weak") + .shouldMatch("Timestamp digest algorithm: .*weak") + .shouldNotMatch("Timestamp signature algorithm: .*weak.*weak") + .shouldMatch("Timestamp signature algorithm: .*key.*weak"); + verify(file, "-J-Djava.security.debug=jar") + .shouldHaveExitValue(0) + .shouldMatch("SignatureException:.*Disabled"); + } + static void checkTimestamp(String file, String policyId, String digestAlg) throws Exception { try (JarFile jf = new JarFile(file)) { JarEntry je = jf.getJarEntry("META-INF/OLD.RSA"); try (InputStream is = jf.getInputStream(je)) {
*** 363,405 **** } } } } /** ! * @param cmd the command line (with a hole to plug in) ! * @param path the path in the URL, i.e, http://localhost/path ! * @param expected if this command should succeed */ ! static void jarsigner(String cmd, int path, boolean expected) ! throws Exception { ! System.err.println("Test " + path); ! Process p = Runtime.getRuntime().exec(String.format(Locale.ROOT,cmd, path, path)); ! BufferedReader reader = new BufferedReader( ! new InputStreamReader(p.getErrorStream())); ! while (true) { ! String s = reader.readLine(); ! if (s == null) break; ! System.err.println(s); ! } ! ! // Will not see noTimestamp warning ! boolean seeWarning = false; ! reader = new BufferedReader( ! new InputStreamReader(p.getInputStream())); ! while (true) { ! String s = reader.readLine(); ! if (s == null) break; ! System.err.println(s); ! if (s.indexOf("Warning:") >= 0) { ! seeWarning = true; ! } ! } ! int result = p.waitFor(); ! if (expected && result != 0 || !expected && result == 0) { ! throw new Exception("Failed"); ! } ! if (seeWarning) { ! throw new Exception("See warning"); ! } } } --- 483,546 ---- } } } } + static int which = 0; + /** ! * @param extra more args given to jarsigner */ ! static OutputAnalyzer sign(String path, String... extra) ! throws Throwable { ! which++; ! System.err.println("\n>> Test #" + which + ": " + Arrays.toString(extra)); ! List<String> args = List.of("-J-Djava.security.egd=file:/dev/./urandom", ! "-debug", "-signedjar", path + ".jar", "old.jar", ! path.equals("badku") ? "badku" : "old"); ! args = new ArrayList<>(args); ! if (!path.equals("none") && !path.equals("badku")) { ! args.add("-tsa"); ! args.add(host + path); ! } ! args.addAll(Arrays.asList(extra)); ! return jarsigner(args); ! } ! ! static void prepare() throws Exception { ! jdk.testlibrary.JarUtils.createJar("old.jar", "A"); ! Files.deleteIfExists(Paths.get("tsks")); ! keytool("-alias ca -genkeypair -ext bc -dname CN=CA"); ! keytool("-alias old -genkeypair -dname CN=old"); ! keytool("-alias badku -genkeypair -dname CN=badku"); ! keytool("-alias ts -genkeypair -dname CN=ts"); ! keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsbad1"); ! keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1"); ! keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2"); ! keytool("-alias tsbad3 -genkeypair -dname CN=tsbad3"); ! ! gencert("old"); ! gencert("badku", "-ext ku:critical=keyAgreement"); ! gencert("ts", "-ext eku:critical=ts"); ! gencert("tsweak", "-ext eku:critical=ts"); ! gencert("tsbad1"); ! gencert("tsbad2", "-ext eku=ts"); ! gencert("tsbad3", "-ext eku:critical=cs"); ! } ! ! static void gencert(String alias, String... extra) throws Exception { ! keytool("-alias " + alias + " -certreq -file " + alias + ".req"); ! String genCmd = "-gencert -alias ca -infile " + ! alias + ".req -outfile " + alias + ".cert"; ! for (String s : extra) { ! genCmd += " " + s; ! } ! keytool(genCmd); ! keytool("-alias " + alias + " -importcert -file " + alias + ".cert"); ! } ! ! static void keytool(String cmd) throws Exception { ! cmd = "-keystore tsks -storepass changeit -keypass changeit " + ! "-keyalg rsa -validity 200 " + cmd; ! sun.security.tools.keytool.Main.main(cmd.split(" ")); } }
< prev index next >