提交 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());
} }
} }
} }
......
#
# 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.
先完成此消息的编辑!
想要评论请 注册