提交 1d121526 编写于 作者: R robm

8163304: jarsigner -verbose -verify should print the algorithms used to sign the jar

Reviewed-by: weijun
上级 1dae0751
......@@ -498,6 +498,23 @@ public class SignerInfo implements DerEncoder {
return unauthenticatedAttributes;
}
/**
* Returns the timestamp PKCS7 data unverified.
* @return a PKCS7 object
*/
public PKCS7 getTsToken() throws IOException {
if (unauthenticatedAttributes == null) {
return null;
}
PKCS9Attribute tsTokenAttr =
unauthenticatedAttributes.getAttribute(
PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
if (tsTokenAttr == null) {
return null;
}
return new PKCS7((byte[])tsTokenAttr.getValue());
}
/*
* Extracts a timestamp from a PKCS7 SignerInfo.
*
......@@ -525,19 +542,12 @@ public class SignerInfo implements DerEncoder {
if (timestamp != null || !hasTimestamp)
return timestamp;
if (unauthenticatedAttributes == null) {
hasTimestamp = false;
return null;
}
PKCS9Attribute tsTokenAttr =
unauthenticatedAttributes.getAttribute(
PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
if (tsTokenAttr == null) {
PKCS7 tsToken = getTsToken();
if (tsToken == null) {
hasTimestamp = false;
return null;
}
PKCS7 tsToken = new PKCS7((byte[])tsTokenAttr.getValue());
// Extract the content (an encoded timestamp token info)
byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
// Extract the signer (the Timestamping Authority)
......
......@@ -53,6 +53,9 @@ import java.security.cert.CertificateNotYetValidException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.util.Map.Entry;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.timestamp.TimestampToken;
import sun.security.tools.KeyStoreUtil;
import sun.security.tools.PathList;
import sun.security.x509.*;
......@@ -97,6 +100,15 @@ public class Main {
private static final long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds
private static final DisabledAlgorithmConstraints DISABLED_CHECK =
new DisabledAlgorithmConstraints(
DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS);
private static final Set<CryptoPrimitive> DIGEST_PRIMITIVE_SET = Collections
.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST));
private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET = Collections
.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
// Attention:
// This is the entry that get launched by the security tool jarsigner.
public static void main(String args[]) throws Exception {
......@@ -172,6 +184,8 @@ public class Main {
private boolean badExtendedKeyUsage = false;
private boolean badNetscapeCertType = false;
private boolean seeWeak = false;
CertificateFactory certificateFactory;
CertPathValidator validator;
PKIXParameters pkixParameters;
......@@ -577,6 +591,10 @@ public class Main {
{
boolean anySigned = false; // if there exists entry inside jar signed
JarFile jf = null;
Map<String,String> digestMap = new HashMap<>();
Map<String,PKCS7> sigMap = new HashMap<>();
Map<String,String> sigNameMap = new HashMap<>();
Map<String,String> unparsableSignatures = new HashMap<>();
try {
jf = new JarFile(jarName, true);
......@@ -587,17 +605,44 @@ public class Main {
while (entries.hasMoreElements()) {
JarEntry je = entries.nextElement();
entriesVec.addElement(je);
InputStream is = null;
try (InputStream is = jf.getInputStream(je)) {
String name = je.getName();
if (signatureRelated(name)
&& SignatureFileVerifier.isBlockOrSF(name)) {
String alias = name.substring(name.lastIndexOf('/') + 1,
name.lastIndexOf('.'));
try {
is = jf.getInputStream(je);
int n;
while ((n = is.read(buffer, 0, buffer.length)) != -1) {
if (name.endsWith(".SF")) {
Manifest sf = new Manifest(is);
boolean found = false;
for (Object obj : sf.getMainAttributes().keySet()) {
String key = obj.toString();
if (key.endsWith("-Digest-Manifest")) {
digestMap.put(alias,
key.substring(0, key.length() - 16));
found = true;
break;
}
}
if (!found) {
unparsableSignatures.putIfAbsent(alias,
String.format(
rb.getString("history.unparsable"),
name));
}
} else {
sigNameMap.put(alias, name);
sigMap.put(alias, new PKCS7(is));
}
} catch (IOException ioe) {
unparsableSignatures.putIfAbsent(alias, String.format(
rb.getString("history.unparsable"), name));
}
} else {
while (is.read(buffer, 0, buffer.length) != -1) {
// we just read. this will throw a SecurityException
// if a signature/digest check fails.
}
} finally {
if (is != null) {
is.close();
}
}
}
......@@ -756,13 +801,106 @@ public class Main {
System.out.println(rb.getString(
".X.not.signed.by.specified.alias.es."));
}
System.out.println();
}
if (man == null)
if (man == null) {
System.out.println();
System.out.println(rb.getString("no.manifest."));
}
// Even if the verbose option is not specified, all out strings
// must be generated so seeWeak can be updated.
if (!digestMap.isEmpty()
|| !sigMap.isEmpty()
|| !unparsableSignatures.isEmpty()) {
if (verbose != null) {
System.out.println();
}
for (String s : sigMap.keySet()) {
if (!digestMap.containsKey(s)) {
unparsableSignatures.putIfAbsent(s, String.format(
rb.getString("history.nosf"), s));
}
}
for (String s : digestMap.keySet()) {
PKCS7 p7 = sigMap.get(s);
if (p7 != null) {
String history;
try {
SignerInfo si = p7.getSignerInfos()[0];
X509Certificate signer = si.getCertificate(p7);
String digestAlg = digestMap.get(s);
String sigAlg = AlgorithmId.makeSigAlg(
si.getDigestAlgorithmId().getName(),
si.getDigestEncryptionAlgorithmId().getName());
PublicKey key = signer.getPublicKey();
PKCS7 tsToken = si.getTsToken();
if (tsToken != null) {
SignerInfo tsSi = tsToken.getSignerInfos()[0];
X509Certificate tsSigner = tsSi.getCertificate(tsToken);
byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);
PublicKey tsKey = tsSigner.getPublicKey();
String tsDigestAlg = tsTokenInfo.getHashAlgorithm().getName();
String tsSigAlg = AlgorithmId.makeSigAlg(
tsSi.getDigestAlgorithmId().getName(),
tsSi.getDigestEncryptionAlgorithmId().getName());
Calendar c = Calendar.getInstance(
TimeZone.getTimeZone("UTC"),
Locale.getDefault(Locale.Category.FORMAT));
c.setTime(tsTokenInfo.getDate());
history = String.format(
rb.getString("history.with.ts"),
signer.getSubjectX500Principal(),
withWeak(digestAlg, DIGEST_PRIMITIVE_SET),
withWeak(sigAlg, SIG_PRIMITIVE_SET),
withWeak(key),
c,
tsSigner.getSubjectX500Principal(),
withWeak(tsDigestAlg, DIGEST_PRIMITIVE_SET),
withWeak(tsSigAlg, SIG_PRIMITIVE_SET),
withWeak(tsKey));
} else {
history = String.format(
rb.getString("history.without.ts"),
signer.getSubjectX500Principal(),
withWeak(digestAlg, DIGEST_PRIMITIVE_SET),
withWeak(sigAlg, SIG_PRIMITIVE_SET),
withWeak(key));
}
} catch (Exception e) {
// The only usage of sigNameMap, remember the name
// of the block file if it's invalid.
history = String.format(
rb.getString("history.unparsable"),
sigNameMap.get(s));
}
if (verbose != null) {
System.out.println(history);
}
} else {
unparsableSignatures.putIfAbsent(s, String.format(
rb.getString("history.nobk"), s));
}
}
if (verbose != null) {
for (String s : unparsableSignatures.keySet()) {
System.out.println(unparsableSignatures.get(s));
}
}
}
System.out.println();
if (!anySigned) {
if (hasSignature) {
if (seeWeak) {
if (verbose != null) {
System.out.println(rb.getString("jar.treated.unsigned.see.weak.verbose"));
System.out.println("\n " +
DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS +
"=" + Security.getProperty(DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS));
} else {
System.out.println(rb.getString("jar.treated.unsigned.see.weak"));
}
} else if (hasSignature) {
System.out.println(rb.getString("jar.treated.unsigned"));
} else {
System.out.println(rb.getString("jar.is.unsigned"));
......@@ -869,6 +1007,26 @@ public class Main {
System.exit(1);
}
private String withWeak(String alg, Set<CryptoPrimitive> primitiveSet) {
if (DISABLED_CHECK.permits(primitiveSet, alg, null)) {
return alg;
} else {
seeWeak = true;
return String.format(rb.getString("with.weak"), alg);
}
}
private String withWeak(PublicKey key) {
if (DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
return String.format(
rb.getString("key.bit"), KeyUtil.getKeySize(key));
} else {
seeWeak = true;
return String.format(
rb.getString("key.bit.weak"), KeyUtil.getKeySize(key));
}
}
private static MessageFormat validityTimeForm = null;
private static MessageFormat notYetTimeForm = null;
private static MessageFormat expiredTimeForm = null;
......
......@@ -138,11 +138,26 @@ public class Resources extends java.util.ListResourceBundle {
{"jar.is.unsigned",
"jar is unsigned."},
{"jar.treated.unsigned",
"Signature not parsable or verifiable. The jar will be treated as unsigned. The jar may have been signed with a weak algorithm that is now disabled. For more information, rerun jarsigner with debug enabled (-J-Djava.security.debug=jar)."},
"WARNING: Signature is either not parsable or not verifiable, and the jar will be treated as unsigned. For more information, re-run jarsigner with debug enabled (-J-Djava.security.debug=jar)."},
{"jar.treated.unsigned.see.weak",
"The jar will be treated as unsigned, because it is signed with a weak algorithm that is now disabled.\n\nRe-run jarsigner with the -verbose option for more details."},
{"jar.treated.unsigned.see.weak.verbose",
"WARNING: The jar will be treated as unsigned, because it is signed with a weak algorithm that is now disabled by the security property:"},
{"jar.signed.", "jar signed."},
{"jar.signed.with.signer.errors.", "jar signed, with signer errors."},
{"jar.verified.", "jar verified."},
{"jar.verified.with.signer.errors.", "jar verified, with signer errors."},
{"history.with.ts", "- Signed by \"%1$s\"\n Digest algorithm: %2$s\n Signature algorithm: %3$s, %4$s\n Timestamped by \"%6$s\" on %5$tc\n Timestamp digest algorithm: %7$s\n Timestamp signature algorithm: %8$s, %9$s"},
{"history.without.ts", "- Signed by \"%1$s\"\n Digest algorithm: %2$s\n Signature algorithm: %3$s, %4$s"},
{"history.unparsable", "- Unparsable signature-related file %s"},
{"history.nosf", "- Missing signature-related file META-INF/%s.SF"},
{"history.nobk", "- Missing block file for signature-related file META-INF/%s.SF"},
{"with.weak", "%s (weak)"},
{"key.bit", "%d-bit key"},
{"key.bit.weak", "%d-bit key (weak)"},
{"jarsigner.", "jarsigner: "},
{"signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or.",
"signature filename must consist of the following characters: A-Z, 0-9, _ or -"},
......
......@@ -25,6 +25,7 @@ package jdk.testlibrary;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
......@@ -43,7 +44,8 @@ public final class JarUtils {
/**
* Create jar file with specified files from specified location.
* Create jar file with specified files. If a specified file does not exist,
* a new jar entry will be created with the file name itself as the content.
*/
public static void createJar(String dest, Path filesLocation,
String... fileNames) throws IOException {
......@@ -63,6 +65,8 @@ public final class JarUtils {
}
try (FileInputStream fis = new FileInputStream(file)) {
Utils.transferBetweenStreams(fis, jos);
} catch (FileNotFoundException e) {
jos.write(fileName.getBytes());
}
}
}
......@@ -78,7 +82,17 @@ public final class JarUtils {
}
/**
* Add specified files to existing jar file.
* Add or remove specified files to existing jar file. If a specified file
* to be updated or added does not exist, the jar entry will be created
* with the file name itself as the content.
*
* @param src the original jar file name
* @param dest the new jar file name
* @param files the files to update. The list is broken into 2 groups
* by a "-" string. The files before in the 1st group will
* be either updated or added. The files in the 2nd group
* will be removed. If no "-" exists, all files belong to
* the 1st group.
*/
public static void updateJar(String src, String dest, String... files)
throws IOException {
......@@ -94,8 +108,11 @@ public final class JarUtils {
JarEntry entry = entries.nextElement();
String name = entry.getName();
boolean found = false;
boolean update = true;
for (String file : files) {
if (name.equals(file)) {
if (file.equals("-")) {
update = false;
} else if (name.equals(file)) {
updatedFiles.add(file);
found = true;
break;
......@@ -103,11 +120,18 @@ public final class JarUtils {
}
if (found) {
if (update) {
System.out.println(String.format("Updating %s with %s",
dest, name));
jos.putNextEntry(new JarEntry(name));
try (FileInputStream fis = new FileInputStream(name)) {
Utils.transferBetweenStreams(fis, jos);
} catch (FileNotFoundException e) {
jos.write(name.getBytes());
}
} else {
System.out.println(String.format("Removing %s from %s",
name, dest));
}
} else {
System.out.println(String.format("Copying %s to %s",
......@@ -121,12 +145,17 @@ public final class JarUtils {
// append new files
for (String file : files) {
if (file.equals("-")) {
break;
}
if (!updatedFiles.contains(file)) {
System.out.println(String.format("Adding %s with %s",
dest, file));
jos.putNextEntry(new JarEntry(file));
try (FileInputStream fis = new FileInputStream(file)) {
Utils.transferBetweenStreams(fis, jos);
} catch (FileNotFoundException e) {
jos.write(file.getBytes());
}
}
}
......
......@@ -22,25 +22,31 @@
*/
import com.sun.net.httpserver.*;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
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.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 sun.misc.IOUtils;
import jdk.testlibrary.*;
import jdk.testlibrary.JarUtils;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.PKCS9Attribute;
......@@ -52,11 +58,22 @@ 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 TSKS = "tsks";
static final String JAR = "old.jar";
static final String defaultPolicyId = "2.3.4.5";
static final String defaultPolicyId = "2.3.4";
static String host = null;
static class Handler implements HttpHandler, AutoCloseable {
......@@ -75,11 +92,7 @@ public class TimestampCheck {
t.getRequestBody().read(input);
try {
int path = 0;
if (t.getRequestURI().getPath().length() > 1) {
path = Integer.parseInt(
t.getRequestURI().getPath().substring(1));
}
String path = t.getRequestURI().getPath().substring(1);
byte[] output = sign(input, path);
Headers out = t.getResponseHeaders();
out.set("Content-Type", "application/timestamp-reply");
......@@ -97,24 +110,9 @@ public class TimestampCheck {
/**
* @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
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());
......@@ -138,36 +136,35 @@ public class TimestampCheck {
}
}
// Write TSResponse
System.err.println("\nResponse\n===================");
KeyStore ks = KeyStore.getInstance("JKS");
try (FileInputStream fis = new FileInputStream(keystore)) {
ks.load(fis, "changeit".toCharArray());
}
FileInputStream is = new FileInputStream(keystore);
KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(is, "changeit".toCharArray());
is.close();
String alias = "ts";
if (path == 6) alias = "tsbad1";
if (path == 7) alias = "tsbad2";
if (path == 8) alias = "tsbad3";
if (path.startsWith("bad") || path.equals("weak")) {
alias = "ts" + path;
}
if (path == 11) {
if (path.equals("diffpolicy")) {
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[] signerCertificateChain;
X509Certificate signer = (X509Certificate)chain[0];
if (path == 5) { // Only case 5 uses full chain
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 == 9) {
} else if (path.equals("nocert")) {
signerCertificateChain = new X509Certificate[0];
} else {
signerCertificateChain = new X509Certificate[1];
......@@ -179,11 +176,11 @@ public class TimestampCheck {
tst.putInteger(1);
tst.putOID(policyId);
if (path != 3 && path != 4) {
if (!path.equals("baddigest") && !path.equals("diffalg")) {
tst.putDerValue(messageImprint);
} else {
byte[] data = messageImprint.toByteArray();
if (path == 4) {
if (path.equals("diffalg")) {
data[6] = (byte)0x01;
} else {
data[data.length-1] = (byte)0x01;
......@@ -198,10 +195,10 @@ public class TimestampCheck {
Calendar cal = Calendar.getInstance();
tst.putGeneralizedTime(cal.getTime());
if (path == 2) {
if (path.equals("diffnonce")) {
tst.putInteger(1234);
} else if (path == 1) {
// do nothing
} else if (path.equals("nononce")) {
// no noce
} else {
tst.putInteger(nonce);
}
......@@ -212,6 +209,8 @@ public class TimestampCheck {
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())));
......@@ -229,12 +228,11 @@ public class TimestampCheck {
SignerInfo signerInfo = new SignerInfo(
new X500Name(signer.getIssuerX500Principal().getName()),
signer.getSerialNumber(),
aid, AlgorithmId.get("RSA"), sig.sign());
AlgorithmId.get("SHA-1"), AlgorithmId.get("RSA"), sig.sign());
SignerInfo[] signerInfos = {signerInfo};
PKCS7 p7 =
new PKCS7(algorithms, contentInfo, signerCertificateChain,
signerInfos);
PKCS7 p7 = new PKCS7(algorithms, contentInfo,
signerCertificateChain, signerInfos);
ByteArrayOutputStream p7out = new ByteArrayOutputStream();
p7.encodeSignedData(p7out);
......@@ -293,42 +291,67 @@ public class TimestampCheck {
stop();
}
}
public static void main(String[] args) throws Exception {
try (Handler tsa = Handler.init(0, TSKS);) {
public static void main(String[] args) throws Throwable {
prepare();
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 += " -J-Djava.security.egd=file:/dev/./urandom"
+ " -debug -keystore " + TSKS + " -storepass changeit"
+ " -tsa http://localhost:" + port + "/%d"
+ " -signedjar new_%d.jar " + JAR + " old";
host = "http://localhost:" + port + "/";
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");
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", "MD5",
"-sigalg", "MD5withRSA", "-tsadigestalg", "MD5")
.shouldHaveExitValue(0);
checkWeak("weak.jar");
// 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();
......@@ -336,6 +359,101 @@ public class TimestampCheck {
}
}
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);
}
}
System.err.println("COMMAND: ");
for (String cmd : launcher.getCommand()) {
System.err.print(cmd + " ");
}
System.err.println();
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 {
System.err.println("BadKU: " + file);
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)) {
......@@ -362,41 +480,65 @@ public class TimestampCheck {
}
}
static int which = 0;
/**
* @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
* @param extra more args given to jarsigner
*/
static void jarsigner(String cmd, int path, boolean expected)
throws Exception {
System.err.println("Test " + path);
Process p = Runtime.getRuntime().exec(String.format(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);
}
static OutputAnalyzer sign(String path, String... extra)
throws Throwable {
which++;
System.err.println("\n>> Test #" + which + ": " + Arrays.toString(extra));
List<String> args = new ArrayList<>();
args.add("-J-Djava.security.egd=file:/dev/./urandom");
args.add("-debug");
args.add("-signedjar");
args.add(path + ".jar");
args.add("old.jar");
args.add(path.equals("badku") ? "badku" : "old");
if (!path.equals("none") && !path.equals("badku")) {
args.add("-tsa");
args.add(host + path);
}
args.addAll(Arrays.asList(extra));
return jarsigner(args);
}
// 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");
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(" "));
}
}
#
# Copyright (c) 2007, 2013, 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
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
# @test
# @bug 6543842 6543440 6939248 8009636 8024302
# @summary checking response of timestamp
#
# @run shell/timeout=600 ts.sh
# Run for a long time because jarsigner with timestamp needs to create a
# 64-bit random number and it might be extremely slow on a machine with
# not enough entropy pool
# set platform-dependent variables
OS=`uname -s`
case "$OS" in
Windows_* )
FS="\\"
;;
* )
FS="/"
;;
esac
if [ "${TESTSRC}" = "" ] ; then
TESTSRC="."
fi
if [ "${TESTJAVA}" = "" ] ; then
JAVAC_CMD=`which javac`
TESTJAVA=`dirname $JAVAC_CMD`/..
fi
JAR="${TESTJAVA}${FS}bin${FS}jar"
JAVA="${TESTJAVA}${FS}bin${FS}java"
JAVAC="${TESTJAVA}${FS}bin${FS}javac"
KT="${TESTJAVA}${FS}bin${FS}keytool -keystore tsks -storepass changeit -keypass changeit -keyalg rsa -validity 200"
rm tsks
echo Nothing > A
rm old.jar
$JAR cvf old.jar A
# ca is CA
# old is signer for code
# ts is signer for timestamp
# tsbad1 has no extendedKeyUsage
# tsbad2's extendedKeyUsage is non-critical
# tsbad3's extendedKeyUsage has no timestamping
$KT -alias ca -genkeypair -ext bc -dname CN=CA
$KT -alias old -genkeypair -dname CN=old
$KT -alias ts -genkeypair -dname CN=ts
$KT -alias tsbad1 -genkeypair -dname CN=tsbad1
$KT -alias tsbad2 -genkeypair -dname CN=tsbad2
$KT -alias tsbad3 -genkeypair -dname CN=tsbad3
$KT -alias ts -certreq | \
$KT -alias ca -gencert -ext eku:critical=ts | \
$KT -alias ts -importcert
$KT -alias tsbad1 -certreq | \
$KT -alias ca -gencert | \
$KT -alias tsbad1 -importcert
$KT -alias tsbad2 -certreq | \
$KT -alias ca -gencert -ext eku=ts | \
$KT -alias tsbad2 -importcert
$KT -alias tsbad3 -certreq | \
$KT -alias ca -gencert -ext eku:critical=cs | \
$KT -alias tsbad3 -importcert
$JAVAC -XDignore.symbol.file -d . ${TESTSRC}/TimestampCheck.java
$JAVA ${TESTVMOPTS} TimestampCheck
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册