提交 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 { ...@@ -498,6 +498,23 @@ public class SignerInfo implements DerEncoder {
return unauthenticatedAttributes; 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. * Extracts a timestamp from a PKCS7 SignerInfo.
* *
...@@ -525,19 +542,12 @@ public class SignerInfo implements DerEncoder { ...@@ -525,19 +542,12 @@ public class SignerInfo implements DerEncoder {
if (timestamp != null || !hasTimestamp) if (timestamp != null || !hasTimestamp)
return timestamp; return timestamp;
if (unauthenticatedAttributes == null) { PKCS7 tsToken = getTsToken();
hasTimestamp = false; if (tsToken == null) {
return null;
}
PKCS9Attribute tsTokenAttr =
unauthenticatedAttributes.getAttribute(
PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
if (tsTokenAttr == null) {
hasTimestamp = false; hasTimestamp = false;
return null; return null;
} }
PKCS7 tsToken = new PKCS7((byte[])tsTokenAttr.getValue());
// Extract the content (an encoded timestamp token info) // Extract the content (an encoded timestamp token info)
byte[] encTsTokenInfo = tsToken.getContentInfo().getData(); byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
// Extract the signer (the Timestamping Authority) // Extract the signer (the Timestamping Authority)
......
...@@ -53,6 +53,9 @@ import java.security.cert.CertificateNotYetValidException; ...@@ -53,6 +53,9 @@ import java.security.cert.CertificateNotYetValidException;
import java.security.cert.PKIXParameters; import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor; import java.security.cert.TrustAnchor;
import java.util.Map.Entry; 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.KeyStoreUtil;
import sun.security.tools.PathList; import sun.security.tools.PathList;
import sun.security.x509.*; import sun.security.x509.*;
...@@ -97,6 +100,15 @@ public class Main { ...@@ -97,6 +100,15 @@ public class Main {
private static final long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds 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: // Attention:
// This is the entry that get launched by the security tool jarsigner. // This is the entry that get launched by the security tool jarsigner.
public static void main(String args[]) throws Exception { public static void main(String args[]) throws Exception {
...@@ -172,6 +184,8 @@ public class Main { ...@@ -172,6 +184,8 @@ public class Main {
private boolean badExtendedKeyUsage = false; private boolean badExtendedKeyUsage = false;
private boolean badNetscapeCertType = false; private boolean badNetscapeCertType = false;
private boolean seeWeak = false;
CertificateFactory certificateFactory; CertificateFactory certificateFactory;
CertPathValidator validator; CertPathValidator validator;
PKIXParameters pkixParameters; PKIXParameters pkixParameters;
...@@ -577,6 +591,10 @@ public class Main { ...@@ -577,6 +591,10 @@ public class Main {
{ {
boolean anySigned = false; // if there exists entry inside jar signed boolean anySigned = false; // if there exists entry inside jar signed
JarFile jf = null; 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 { try {
jf = new JarFile(jarName, true); jf = new JarFile(jarName, true);
...@@ -587,17 +605,44 @@ public class Main { ...@@ -587,17 +605,44 @@ public class Main {
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
JarEntry je = entries.nextElement(); JarEntry je = entries.nextElement();
entriesVec.addElement(je); 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 { try {
is = jf.getInputStream(je); if (name.endsWith(".SF")) {
int n; Manifest sf = new Manifest(is);
while ((n = is.read(buffer, 0, buffer.length)) != -1) { 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 // we just read. this will throw a SecurityException
// if a signature/digest check fails. // if a signature/digest check fails.
} }
} finally {
if (is != null) {
is.close();
} }
} }
} }
...@@ -756,13 +801,106 @@ public class Main { ...@@ -756,13 +801,106 @@ public class Main {
System.out.println(rb.getString( System.out.println(rb.getString(
".X.not.signed.by.specified.alias.es.")); ".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.")); 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 (!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")); System.out.println(rb.getString("jar.treated.unsigned"));
} else { } else {
System.out.println(rb.getString("jar.is.unsigned")); System.out.println(rb.getString("jar.is.unsigned"));
...@@ -869,6 +1007,26 @@ public class Main { ...@@ -869,6 +1007,26 @@ public class Main {
System.exit(1); 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 validityTimeForm = null;
private static MessageFormat notYetTimeForm = null; private static MessageFormat notYetTimeForm = null;
private static MessageFormat expiredTimeForm = null; private static MessageFormat expiredTimeForm = null;
......
...@@ -138,11 +138,26 @@ public class Resources extends java.util.ListResourceBundle { ...@@ -138,11 +138,26 @@ public class Resources extends java.util.ListResourceBundle {
{"jar.is.unsigned", {"jar.is.unsigned",
"jar is unsigned."}, "jar is unsigned."},
{"jar.treated.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.", "jar signed."},
{"jar.signed.with.signer.errors.", "jar signed, with signer errors."}, {"jar.signed.with.signer.errors.", "jar signed, with signer errors."},
{"jar.verified.", "jar verified."}, {"jar.verified.", "jar verified."},
{"jar.verified.with.signer.errors.", "jar verified, with signer errors."}, {"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: "}, {"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.",
"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; ...@@ -25,6 +25,7 @@ package jdk.testlibrary;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
...@@ -43,7 +44,8 @@ public final class JarUtils { ...@@ -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, public static void createJar(String dest, Path filesLocation,
String... fileNames) throws IOException { String... fileNames) throws IOException {
...@@ -63,6 +65,8 @@ public final class JarUtils { ...@@ -63,6 +65,8 @@ public final class JarUtils {
} }
try (FileInputStream fis = new FileInputStream(file)) { try (FileInputStream fis = new FileInputStream(file)) {
Utils.transferBetweenStreams(fis, jos); Utils.transferBetweenStreams(fis, jos);
} catch (FileNotFoundException e) {
jos.write(fileName.getBytes());
} }
} }
} }
...@@ -78,7 +82,17 @@ public final class JarUtils { ...@@ -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) public static void updateJar(String src, String dest, String... files)
throws IOException { throws IOException {
...@@ -94,8 +108,11 @@ public final class JarUtils { ...@@ -94,8 +108,11 @@ public final class JarUtils {
JarEntry entry = entries.nextElement(); JarEntry entry = entries.nextElement();
String name = entry.getName(); String name = entry.getName();
boolean found = false; boolean found = false;
boolean update = true;
for (String file : files) { for (String file : files) {
if (name.equals(file)) { if (file.equals("-")) {
update = false;
} else if (name.equals(file)) {
updatedFiles.add(file); updatedFiles.add(file);
found = true; found = true;
break; break;
...@@ -103,11 +120,18 @@ public final class JarUtils { ...@@ -103,11 +120,18 @@ public final class JarUtils {
} }
if (found) { if (found) {
if (update) {
System.out.println(String.format("Updating %s with %s", System.out.println(String.format("Updating %s with %s",
dest, name)); dest, name));
jos.putNextEntry(new JarEntry(name)); jos.putNextEntry(new JarEntry(name));
try (FileInputStream fis = new FileInputStream(name)) { try (FileInputStream fis = new FileInputStream(name)) {
Utils.transferBetweenStreams(fis, jos); Utils.transferBetweenStreams(fis, jos);
} catch (FileNotFoundException e) {
jos.write(name.getBytes());
}
} else {
System.out.println(String.format("Removing %s from %s",
name, dest));
} }
} else { } else {
System.out.println(String.format("Copying %s to %s", System.out.println(String.format("Copying %s to %s",
...@@ -121,12 +145,17 @@ public final class JarUtils { ...@@ -121,12 +145,17 @@ public final class JarUtils {
// append new files // append new files
for (String file : files) { for (String file : files) {
if (file.equals("-")) {
break;
}
if (!updatedFiles.contains(file)) { if (!updatedFiles.contains(file)) {
System.out.println(String.format("Adding %s with %s", System.out.println(String.format("Adding %s with %s",
dest, file)); dest, file));
jos.putNextEntry(new JarEntry(file)); jos.putNextEntry(new JarEntry(file));
try (FileInputStream fis = new FileInputStream(file)) { try (FileInputStream fis = new FileInputStream(file)) {
Utils.transferBetweenStreams(fis, jos); Utils.transferBetweenStreams(fis, jos);
} catch (FileNotFoundException e) {
jos.write(file.getBytes());
} }
} }
} }
......
...@@ -22,25 +22,31 @@ ...@@ -22,25 +22,31 @@
*/ */
import com.sun.net.httpserver.*; import com.sun.net.httpserver.*;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Signature; import java.security.Signature;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.List;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import sun.misc.IOUtils; import sun.misc.IOUtils;
import jdk.testlibrary.*;
import jdk.testlibrary.JarUtils;
import sun.security.pkcs.ContentInfo; import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7; import sun.security.pkcs.PKCS7;
import sun.security.pkcs.PKCS9Attribute; import sun.security.pkcs.PKCS9Attribute;
...@@ -52,11 +58,22 @@ import sun.security.util.ObjectIdentifier; ...@@ -52,11 +58,22 @@ import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId; import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name; 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 { 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 { static class Handler implements HttpHandler, AutoCloseable {
...@@ -75,11 +92,7 @@ public class TimestampCheck { ...@@ -75,11 +92,7 @@ public class TimestampCheck {
t.getRequestBody().read(input); t.getRequestBody().read(input);
try { try {
int path = 0; String path = t.getRequestURI().getPath().substring(1);
if (t.getRequestURI().getPath().length() > 1) {
path = Integer.parseInt(
t.getRequestURI().getPath().substring(1));
}
byte[] output = sign(input, path); byte[] output = sign(input, path);
Headers out = t.getResponseHeaders(); Headers out = t.getResponseHeaders();
out.set("Content-Type", "application/timestamp-reply"); out.set("Content-Type", "application/timestamp-reply");
...@@ -97,24 +110,9 @@ public class TimestampCheck { ...@@ -97,24 +110,9 @@ public class TimestampCheck {
/** /**
* @param input The data to sign * @param input The data to sign
* @param path different cases to simulate, impl on URL path * @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 * @returns the signed
*/ */
byte[] sign(byte[] input, int path) throws Exception { byte[] sign(byte[] input, String path) throws Exception {
// Read TSRequest
DerValue value = new DerValue(input); DerValue value = new DerValue(input);
System.err.println("\nIncoming Request\n==================="); System.err.println("\nIncoming Request\n===================");
System.err.println("Version: " + value.data.getInteger()); System.err.println("Version: " + value.data.getInteger());
...@@ -138,36 +136,35 @@ public class TimestampCheck { ...@@ -138,36 +136,35 @@ public class TimestampCheck {
} }
} }
// Write TSResponse
System.err.println("\nResponse\n==================="); System.err.println("\nResponse\n===================");
KeyStore ks = KeyStore.getInstance("JKS"); FileInputStream is = new FileInputStream(keystore);
try (FileInputStream fis = new FileInputStream(keystore)) { KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(fis, "changeit".toCharArray()); ks.load(is, "changeit".toCharArray());
} is.close();
String alias = "ts"; String alias = "ts";
if (path == 6) alias = "tsbad1"; if (path.startsWith("bad") || path.equals("weak")) {
if (path == 7) alias = "tsbad2"; alias = "ts" + path;
if (path == 8) alias = "tsbad3"; }
if (path == 11) { if (path.equals("diffpolicy")) {
policyId = new ObjectIdentifier(defaultPolicyId); policyId = new ObjectIdentifier(defaultPolicyId);
} }
DerOutputStream statusInfo = new DerOutputStream(); DerOutputStream statusInfo = new DerOutputStream();
statusInfo.putInteger(0); statusInfo.putInteger(0);
DerOutputStream token = new DerOutputStream();
AlgorithmId[] algorithms = {aid}; AlgorithmId[] algorithms = {aid};
Certificate[] chain = ks.getCertificateChain(alias); Certificate[] chain = ks.getCertificateChain(alias);
X509Certificate[] signerCertificateChain = null; X509Certificate[] signerCertificateChain;
X509Certificate signer = (X509Certificate)chain[0]; 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]; signerCertificateChain = new X509Certificate[chain.length];
for (int i=0; i<chain.length; i++) { for (int i=0; i<chain.length; i++) {
signerCertificateChain[i] = (X509Certificate)chain[i]; signerCertificateChain[i] = (X509Certificate)chain[i];
} }
} else if (path == 9) { } else if (path.equals("nocert")) {
signerCertificateChain = new X509Certificate[0]; signerCertificateChain = new X509Certificate[0];
} else { } else {
signerCertificateChain = new X509Certificate[1]; signerCertificateChain = new X509Certificate[1];
...@@ -179,11 +176,11 @@ public class TimestampCheck { ...@@ -179,11 +176,11 @@ public class TimestampCheck {
tst.putInteger(1); tst.putInteger(1);
tst.putOID(policyId); tst.putOID(policyId);
if (path != 3 && path != 4) { if (!path.equals("baddigest") && !path.equals("diffalg")) {
tst.putDerValue(messageImprint); tst.putDerValue(messageImprint);
} else { } else {
byte[] data = messageImprint.toByteArray(); byte[] data = messageImprint.toByteArray();
if (path == 4) { if (path.equals("diffalg")) {
data[6] = (byte)0x01; data[6] = (byte)0x01;
} else { } else {
data[data.length-1] = (byte)0x01; data[data.length-1] = (byte)0x01;
...@@ -198,10 +195,10 @@ public class TimestampCheck { ...@@ -198,10 +195,10 @@ public class TimestampCheck {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
tst.putGeneralizedTime(cal.getTime()); tst.putGeneralizedTime(cal.getTime());
if (path == 2) { if (path.equals("diffnonce")) {
tst.putInteger(1234); tst.putInteger(1234);
} else if (path == 1) { } else if (path.equals("nononce")) {
// do nothing // no noce
} else { } else {
tst.putInteger(nonce); tst.putInteger(nonce);
} }
...@@ -212,6 +209,8 @@ public class TimestampCheck { ...@@ -212,6 +209,8 @@ public class TimestampCheck {
DerOutputStream tstInfo2 = new DerOutputStream(); DerOutputStream tstInfo2 = new DerOutputStream();
tstInfo2.putOctetString(tstInfo.toByteArray()); 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"); Signature sig = Signature.getInstance("SHA1withRSA");
sig.initSign((PrivateKey)(ks.getKey( sig.initSign((PrivateKey)(ks.getKey(
alias, "changeit".toCharArray()))); alias, "changeit".toCharArray())));
...@@ -229,12 +228,11 @@ public class TimestampCheck { ...@@ -229,12 +228,11 @@ public class TimestampCheck {
SignerInfo signerInfo = new SignerInfo( SignerInfo signerInfo = new SignerInfo(
new X500Name(signer.getIssuerX500Principal().getName()), new X500Name(signer.getIssuerX500Principal().getName()),
signer.getSerialNumber(), signer.getSerialNumber(),
aid, AlgorithmId.get("RSA"), sig.sign()); AlgorithmId.get("SHA-1"), AlgorithmId.get("RSA"), sig.sign());
SignerInfo[] signerInfos = {signerInfo}; SignerInfo[] signerInfos = {signerInfo};
PKCS7 p7 = PKCS7 p7 = new PKCS7(algorithms, contentInfo,
new PKCS7(algorithms, contentInfo, signerCertificateChain, signerCertificateChain, signerInfos);
signerInfos);
ByteArrayOutputStream p7out = new ByteArrayOutputStream(); ByteArrayOutputStream p7out = new ByteArrayOutputStream();
p7.encodeSignedData(p7out); p7.encodeSignedData(p7out);
...@@ -293,42 +291,67 @@ public class TimestampCheck { ...@@ -293,42 +291,67 @@ public class TimestampCheck {
stop(); 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(); tsa.start();
int port = tsa.getPort(); int port = tsa.getPort();
String cmd; host = "http://localhost:" + port + "/";
// 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";
if (args.length == 0) { // Run this test if (args.length == 0) { // Run this test
jarsigner(cmd, 0, true); // Success, normal call sign("none")
jarsigner(cmd, 1, false); // These 4 should fail .shouldContain("is not timestamped")
jarsigner(cmd, 2, false); .shouldHaveExitValue(0);
jarsigner(cmd, 3, false);
jarsigner(cmd, 4, false); sign("badku")
jarsigner(cmd, 5, true); // Success, 6543440 solved. .shouldHaveExitValue(0);
jarsigner(cmd, 6, false); // tsbad1 checkBadKU("badku.jar");
jarsigner(cmd, 7, false); // tsbad2
jarsigner(cmd, 8, false); // tsbad3 sign("normal")
jarsigner(cmd, 9, false); // no cert in timestamp .shouldNotContain("is not timestamped")
jarsigner(cmd + " -tsapolicyid 1.2.3.4", 10, true); .shouldHaveExitValue(0);
checkTimestamp("new_10.jar", "1.2.3.4", "SHA-256");
jarsigner(cmd + " -tsapolicyid 1.2.3.5", 11, false); sign("nononce")
jarsigner(cmd + " -tsadigestalg SHA", 12, true); .shouldHaveExitValue(1);
checkTimestamp("new_12.jar", defaultPolicyId, "SHA-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 } else { // Run as a standalone server
System.err.println("Press Enter to quit server"); System.err.println("Press Enter to quit server");
System.in.read(); System.in.read();
...@@ -336,6 +359,101 @@ public class TimestampCheck { ...@@ -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) static void checkTimestamp(String file, String policyId, String digestAlg)
throws Exception { throws Exception {
try (JarFile jf = new JarFile(file)) { try (JarFile jf = new JarFile(file)) {
...@@ -362,41 +480,65 @@ public class TimestampCheck { ...@@ -362,41 +480,65 @@ public class TimestampCheck {
} }
} }
static int which = 0;
/** /**
* @param cmd the command line (with a hole to plug in) * @param extra more args given to jarsigner
* @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) static OutputAnalyzer sign(String path, String... extra)
throws Exception { throws Throwable {
System.err.println("Test " + path); which++;
Process p = Runtime.getRuntime().exec(String.format(cmd, path, path)); System.err.println("\n>> Test #" + which + ": " + Arrays.toString(extra));
BufferedReader reader = new BufferedReader( List<String> args = new ArrayList<>();
new InputStreamReader(p.getErrorStream())); args.add("-J-Djava.security.egd=file:/dev/./urandom");
while (true) { args.add("-debug");
String s = reader.readLine(); args.add("-signedjar");
if (s == null) break; args.add(path + ".jar");
System.err.println(s); args.add("old.jar");
} args.add(path.equals("badku") ? "badku" : "old");
if (!path.equals("none") && !path.equals("badku")) {
// Will not see noTimestamp warning args.add("-tsa");
boolean seeWarning = false; args.add(host + path);
reader = new BufferedReader( }
new InputStreamReader(p.getInputStream())); args.addAll(Arrays.asList(extra));
while (true) { return jarsigner(args);
String s = reader.readLine(); }
if (s == null) break;
System.err.println(s); static void prepare() throws Exception {
if (s.indexOf("Warning:") >= 0) { jdk.testlibrary.JarUtils.createJar("old.jar", "A");
seeWarning = true; Files.deleteIfExists(Paths.get("tsks"));
} keytool("-alias ca -genkeypair -ext bc -dname CN=CA");
} keytool("-alias old -genkeypair -dname CN=old");
int result = p.waitFor(); keytool("-alias badku -genkeypair -dname CN=badku");
if (expected && result != 0 || !expected && result == 0) { keytool("-alias ts -genkeypair -dname CN=ts");
throw new Exception("Failed"); keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsbad1");
} keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1");
if (seeWarning) { keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2");
throw new Exception("See warning"); 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.
先完成此消息的编辑!
想要评论请 注册