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