Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions jdk/src/bsd/doc/man/jarsigner.1
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@ Informational warnings include those that are not errors but regarded as bad pra
hasExpiringCert
This jar contains entries whose signer certificate will expire within six months\&.
.TP
internalInconsistenciesDetected
This jar contains internal inconsistencies detected during verification
that may result in different contents when reading via JarFile
and JarInputStream\&.
.TP
noTimestamp
This jar contains signatures that does not include a timestamp\&. Without a timestamp, users may not be able to validate this JAR file after the signer certificate\&'s expiration date (\f3YYYY-MM-DD\fR) or after any future revocation date\&.
.SH EXAMPLES
Expand Down
5 changes: 5 additions & 0 deletions jdk/src/linux/doc/man/jarsigner.1
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@ Informational warnings include those that are not errors but regarded as bad pra
hasExpiringCert
This jar contains entries whose signer certificate will expire within six months\&.
.TP
internalInconsistenciesDetected
This jar contains internal inconsistencies detected during verification
that may result in different contents when reading via JarFile
and JarInputStream\&.
.TP
noTimestamp
This jar contains signatures that does not include a timestamp\&. Without a timestamp, users may not be able to validate this JAR file after the signer certificate\&'s expiration date (\f3YYYY-MM-DD\fR) or after any future revocation date\&.
.SH EXAMPLES
Expand Down
149 changes: 149 additions & 0 deletions jdk/src/share/classes/sun/security/tools/jarsigner/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
package sun.security.tools.jarsigner;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.cert.CertPathValidatorException;
import java.security.cert.PKIXBuilderParameters;
import java.util.*;
Expand Down Expand Up @@ -223,6 +225,8 @@ public static void main(String args[]) throws Exception {
private Throwable chainNotValidatedReason = null;
private Throwable tsaChainNotValidatedReason = null;

private List<String> crossChkWarnings = new ArrayList<>();

PKIXBuilderParameters pkixParameters;
Set<X509Certificate> trustedCerts = new HashSet<>();

Expand Down Expand Up @@ -976,6 +980,7 @@ void verifyJar(String jarName)
}
}
System.out.println();
crossCheckEntries(jarName);

// If signer is a trusted cert or private entry in user's own
// keystore, it can be self-signed. Please note aliasNotInStore
Expand Down Expand Up @@ -1017,6 +1022,144 @@ void verifyJar(String jarName)
System.exit(1);
}

private void crossCheckEntries(String jarName) throws Exception {
Set<String> locEntries = new HashSet<>();

try (JarFile jarFile = new JarFile(jarName);
JarInputStream jis = new JarInputStream(
Files.newInputStream(Paths.get(jarName)))) {

Manifest cenManifest = jarFile.getManifest();
Manifest locManifest = jis.getManifest();
compareManifest(cenManifest, locManifest);

JarEntry locEntry;
while ((locEntry = jis.getNextJarEntry()) != null) {
String entryName = locEntry.getName();
locEntries.add(entryName);

JarEntry cenEntry = jarFile.getJarEntry(entryName);
if (cenEntry == null) {
crossChkWarnings.add(String.format(rb.getString(
"entry.1.present.when.reading.jarinputstream.but.missing.via.jarfile"),
entryName));
continue;
}

try {
readEntry(jis);
} catch (SecurityException e) {
crossChkWarnings.add(String.format(rb.getString(
"signature.verification.failed.on.entry.1.when.reading.via.jarinputstream"),
entryName));
continue;
}

try (InputStream cenInputStream = jarFile.getInputStream(cenEntry)) {
if (cenInputStream == null) {
crossChkWarnings.add(String.format(rb.getString(
"entry.1.present.in.jarfile.but.unreadable"),
entryName));
continue;
} else {
try {
readEntry(cenInputStream);
} catch (SecurityException e) {
crossChkWarnings.add(String.format(rb.getString(
"signature.verification.failed.on.entry.1.when.reading.via.jarfile"),
entryName));
continue;
}
}
}

compareSigners(cenEntry, locEntry);
}

jarFile.stream()
.map(JarEntry::getName)
.filter(n -> !locEntries.contains(n) && !n.equals(JarFile.MANIFEST_NAME))
.forEach(n -> crossChkWarnings.add(String.format(rb.getString(
"entry.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), n)));
}
}

private void readEntry(InputStream is) throws IOException {
byte[] buffer = new byte[8192];
while (is.read(buffer) != -1) { }
}

private void compareManifest(Manifest cenManifest, Manifest locManifest) {
if (cenManifest == null) {
crossChkWarnings.add(rb.getString(
"manifest.missing.when.reading.jarfile"));
return;
}
if (locManifest == null) {
crossChkWarnings.add(rb.getString(
"manifest.missing.when.reading.jarinputstream"));
return;
}

Attributes cenMainAttrs = cenManifest.getMainAttributes();
Attributes locMainAttrs = locManifest.getMainAttributes();

for (Object key : cenMainAttrs.keySet()) {
Object cenValue = cenMainAttrs.get(key);
Object locValue = locMainAttrs.get(key);

if (locValue == null) {
crossChkWarnings.add(String.format(rb.getString(
"manifest.attribute.1.present.when.reading.jarfile.but.missing.via.jarinputstream"),
key));
} else if (!cenValue.equals(locValue)) {
crossChkWarnings.add(String.format(rb.getString(
"manifest.attribute.1.differs.jarfile.value.2.jarinputstream.value.3"),
key, cenValue, locValue));
}
}

for (Object key : locMainAttrs.keySet()) {
if (!cenMainAttrs.containsKey(key)) {
crossChkWarnings.add(String.format(rb.getString(
"manifest.attribute.1.present.when.reading.jarinputstream.but.missing.via.jarfile"),
key));
}
}
}

private void compareSigners(JarEntry cenEntry, JarEntry locEntry) {
CodeSigner[] cenSigners = cenEntry.getCodeSigners();
CodeSigner[] locSigners = locEntry.getCodeSigners();

boolean cenHasSigners = cenSigners != null;
boolean locHasSigners = locSigners != null;

if (cenHasSigners && locHasSigners) {
if (!Arrays.equals(cenSigners, locSigners)) {
crossChkWarnings.add(String.format(rb.getString(
"codesigners.different.for.entry.1.when.reading.jarfile.and.jarinputstream"),
cenEntry.getName()));
}
} else if (cenHasSigners) {
crossChkWarnings.add(String.format(rb.getString(
"entry.1.is.signed.in.jarfile.but.is.not.signed.in.jarinputstream"),
cenEntry.getName()));
} else if (locHasSigners) {
crossChkWarnings.add(String.format(rb.getString(
"entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile"),
locEntry.getName()));
}
}

private void displayCrossChkWarnings() {
System.out.println();
// First is a summary warning
System.out.println(rb.getString("jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream"));
// each warning message with prefix "- "
crossChkWarnings.forEach(warning -> System.out.println("- " + warning));
}

private void displayMessagesAndResult(boolean isSigning) {
String result;
List<String> errors = new ArrayList<>();
Expand Down Expand Up @@ -1248,13 +1391,19 @@ private void displayMessagesAndResult(boolean isSigning) {
System.out.println(rb.getString("Warning."));
warnings.forEach(System.out::println);
}
if (!crossChkWarnings.isEmpty()) {
displayCrossChkWarnings();
}
} else {
if (!errors.isEmpty() || !warnings.isEmpty()) {
System.out.println();
System.out.println(rb.getString("Warning."));
errors.forEach(System.out::println);
warnings.forEach(System.out::println);
}
if (!crossChkWarnings.isEmpty()) {
displayCrossChkWarnings();
}
}
if (!isSigning && (!errors.isEmpty() || !warnings.isEmpty())) {
if (! (verbose != null && showcerts)) {
Expand Down
28 changes: 28 additions & 0 deletions jdk/src/share/classes/sun/security/tools/jarsigner/Resources.java
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,34 @@ public class Resources extends java.util.ListResourceBundle {
{"Cannot.find.environment.variable.",
"Cannot find environment variable: "},
{"Cannot.find.file.", "Cannot find file: "},
{"manifest.missing.when.reading.jarfile",
"Manifest is missing when reading via JarFile"},
{"manifest.missing.when.reading.jarinputstream",
"Manifest is missing when reading via JarInputStream"},
{"manifest.attribute.1.present.when.reading.jarfile.but.missing.via.jarinputstream",
"Manifest main attribute %s is present when reading via JarFile but missing when reading via JarInputStream"},
{"manifest.attribute.1.present.when.reading.jarinputstream.but.missing.via.jarfile",
"Manifest main attribute %s is present when reading via JarInputStream but missing when reading via JarFile"},
{"manifest.attribute.1.differs.jarfile.value.2.jarinputstream.value.3",
"Manifest main attribute %1$s differs: JarFile value = %2$s, JarInputStream value = %3$s"},
{"entry.1.present.when.reading.jarinputstream.but.missing.via.jarfile",
"Entry %s is present when reading via JarInputStream but missing when reading via JarFile"},
{"entry.1.present.when.reading.jarfile.but.missing.via.jarinputstream",
"Entry %s is present when reading via JarFile but missing when reading via JarInputStream"},
{"entry.1.present.in.jarfile.but.unreadable",
"Entry %s is present in JarFile but unreadable"},
{"codesigners.different.for.entry.1.when.reading.jarfile.and.jarinputstream",
"Code signers are different for entry %s when reading from JarFile and JarInputStream"},
{"entry.1.is.signed.in.jarfile.but.is.not.signed.in.jarinputstream",
"Entry %s is signed in JarFile but is not signed in JarInputStream"},
{"entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile",
"Entry %s is signed in JarInputStream but is not signed in JarFile"},
{"jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream",
"This JAR file contains internal inconsistencies that may result in different contents when reading via JarFile and JarInputStream:"},
{"signature.verification.failed.on.entry.1.when.reading.via.jarinputstream",
"Signature verification failed on entry %s when reading via JarInputStream"},
{"signature.verification.failed.on.entry.1.when.reading.via.jarfile",
"Signature verification failed on entry %s when reading via JarFile"},
};

/**
Expand Down
5 changes: 5 additions & 0 deletions jdk/src/solaris/doc/sun/man/man1/jarsigner.1
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@ Informational warnings include those that are not errors but regarded as bad pra
hasExpiringCert
This jar contains entries whose signer certificate will expire within six months\&.
.TP
internalInconsistenciesDetected
This jar contains internal inconsistencies detected during verification
that may result in different contents when reading via JarFile
and JarInputStream\&.
.TP
noTimestamp
This jar contains signatures that does not include a timestamp\&. Without a timestamp, users may not be able to validate this JAR file after the signer certificate\&'s expiration date (\f3YYYY-MM-DD\fR) or after any future revocation date\&.
.SH EXAMPLES
Expand Down
Loading