|
26 | 26 | package sun.security.tools.jarsigner; |
27 | 27 |
|
28 | 28 | import java.io.*; |
| 29 | +import java.nio.file.Files; |
| 30 | +import java.nio.file.Paths; |
29 | 31 | import java.security.cert.CertPathValidatorException; |
30 | 32 | import java.security.cert.PKIXBuilderParameters; |
31 | 33 | import java.util.*; |
@@ -223,6 +225,8 @@ public static void main(String args[]) throws Exception { |
223 | 225 | private Throwable chainNotValidatedReason = null; |
224 | 226 | private Throwable tsaChainNotValidatedReason = null; |
225 | 227 |
|
| 228 | + private List<String> crossChkWarnings = new ArrayList<>(); |
| 229 | + |
226 | 230 | PKIXBuilderParameters pkixParameters; |
227 | 231 | Set<X509Certificate> trustedCerts = new HashSet<>(); |
228 | 232 |
|
@@ -976,6 +980,7 @@ void verifyJar(String jarName) |
976 | 980 | } |
977 | 981 | } |
978 | 982 | System.out.println(); |
| 983 | + crossCheckEntries(jarName); |
979 | 984 |
|
980 | 985 | // If signer is a trusted cert or private entry in user's own |
981 | 986 | // keystore, it can be self-signed. Please note aliasNotInStore |
@@ -1017,6 +1022,144 @@ void verifyJar(String jarName) |
1017 | 1022 | System.exit(1); |
1018 | 1023 | } |
1019 | 1024 |
|
| 1025 | + private void crossCheckEntries(String jarName) throws Exception { |
| 1026 | + Set<String> locEntries = new HashSet<>(); |
| 1027 | + |
| 1028 | + try (JarFile jarFile = new JarFile(jarName); |
| 1029 | + JarInputStream jis = new JarInputStream( |
| 1030 | + Files.newInputStream(Paths.get(jarName)))) { |
| 1031 | + |
| 1032 | + Manifest cenManifest = jarFile.getManifest(); |
| 1033 | + Manifest locManifest = jis.getManifest(); |
| 1034 | + compareManifest(cenManifest, locManifest); |
| 1035 | + |
| 1036 | + JarEntry locEntry; |
| 1037 | + while ((locEntry = jis.getNextJarEntry()) != null) { |
| 1038 | + String entryName = locEntry.getName(); |
| 1039 | + locEntries.add(entryName); |
| 1040 | + |
| 1041 | + JarEntry cenEntry = jarFile.getJarEntry(entryName); |
| 1042 | + if (cenEntry == null) { |
| 1043 | + crossChkWarnings.add(String.format(rb.getString( |
| 1044 | + "entry.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1045 | + entryName)); |
| 1046 | + continue; |
| 1047 | + } |
| 1048 | + |
| 1049 | + try { |
| 1050 | + readEntry(jis); |
| 1051 | + } catch (SecurityException e) { |
| 1052 | + crossChkWarnings.add(String.format(rb.getString( |
| 1053 | + "signature.verification.failed.on.entry.1.when.reading.via.jarinputstream"), |
| 1054 | + entryName)); |
| 1055 | + continue; |
| 1056 | + } |
| 1057 | + |
| 1058 | + try (InputStream cenInputStream = jarFile.getInputStream(cenEntry)) { |
| 1059 | + if (cenInputStream == null) { |
| 1060 | + crossChkWarnings.add(String.format(rb.getString( |
| 1061 | + "entry.1.present.in.jarfile.but.unreadable"), |
| 1062 | + entryName)); |
| 1063 | + continue; |
| 1064 | + } else { |
| 1065 | + try { |
| 1066 | + readEntry(cenInputStream); |
| 1067 | + } catch (SecurityException e) { |
| 1068 | + crossChkWarnings.add(String.format(rb.getString( |
| 1069 | + "signature.verification.failed.on.entry.1.when.reading.via.jarfile"), |
| 1070 | + entryName)); |
| 1071 | + continue; |
| 1072 | + } |
| 1073 | + } |
| 1074 | + } |
| 1075 | + |
| 1076 | + compareSigners(cenEntry, locEntry); |
| 1077 | + } |
| 1078 | + |
| 1079 | + jarFile.stream() |
| 1080 | + .map(JarEntry::getName) |
| 1081 | + .filter(n -> !locEntries.contains(n) && !n.equals(JarFile.MANIFEST_NAME)) |
| 1082 | + .forEach(n -> crossChkWarnings.add(String.format(rb.getString( |
| 1083 | + "entry.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), n))); |
| 1084 | + } |
| 1085 | + } |
| 1086 | + |
| 1087 | + private void readEntry(InputStream is) throws IOException { |
| 1088 | + byte[] buffer = new byte[8192]; |
| 1089 | + while (is.read(buffer) != -1) { } |
| 1090 | + } |
| 1091 | + |
| 1092 | + private void compareManifest(Manifest cenManifest, Manifest locManifest) { |
| 1093 | + if (cenManifest == null) { |
| 1094 | + crossChkWarnings.add(rb.getString( |
| 1095 | + "manifest.missing.when.reading.jarfile")); |
| 1096 | + return; |
| 1097 | + } |
| 1098 | + if (locManifest == null) { |
| 1099 | + crossChkWarnings.add(rb.getString( |
| 1100 | + "manifest.missing.when.reading.jarinputstream")); |
| 1101 | + return; |
| 1102 | + } |
| 1103 | + |
| 1104 | + Attributes cenMainAttrs = cenManifest.getMainAttributes(); |
| 1105 | + Attributes locMainAttrs = locManifest.getMainAttributes(); |
| 1106 | + |
| 1107 | + for (Object key : cenMainAttrs.keySet()) { |
| 1108 | + Object cenValue = cenMainAttrs.get(key); |
| 1109 | + Object locValue = locMainAttrs.get(key); |
| 1110 | + |
| 1111 | + if (locValue == null) { |
| 1112 | + crossChkWarnings.add(String.format(rb.getString( |
| 1113 | + "manifest.attribute.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), |
| 1114 | + key)); |
| 1115 | + } else if (!cenValue.equals(locValue)) { |
| 1116 | + crossChkWarnings.add(String.format(rb.getString( |
| 1117 | + "manifest.attribute.1.differs.jarfile.value.2.jarinputstream.value.3"), |
| 1118 | + key, cenValue, locValue)); |
| 1119 | + } |
| 1120 | + } |
| 1121 | + |
| 1122 | + for (Object key : locMainAttrs.keySet()) { |
| 1123 | + if (!cenMainAttrs.containsKey(key)) { |
| 1124 | + crossChkWarnings.add(String.format(rb.getString( |
| 1125 | + "manifest.attribute.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1126 | + key)); |
| 1127 | + } |
| 1128 | + } |
| 1129 | + } |
| 1130 | + |
| 1131 | + private void compareSigners(JarEntry cenEntry, JarEntry locEntry) { |
| 1132 | + CodeSigner[] cenSigners = cenEntry.getCodeSigners(); |
| 1133 | + CodeSigner[] locSigners = locEntry.getCodeSigners(); |
| 1134 | + |
| 1135 | + boolean cenHasSigners = cenSigners != null; |
| 1136 | + boolean locHasSigners = locSigners != null; |
| 1137 | + |
| 1138 | + if (cenHasSigners && locHasSigners) { |
| 1139 | + if (!Arrays.equals(cenSigners, locSigners)) { |
| 1140 | + crossChkWarnings.add(String.format(rb.getString( |
| 1141 | + "codesigners.different.for.entry.1.when.reading.jarfile.and.jarinputstream"), |
| 1142 | + cenEntry.getName())); |
| 1143 | + } |
| 1144 | + } else if (cenHasSigners) { |
| 1145 | + crossChkWarnings.add(String.format(rb.getString( |
| 1146 | + "entry.1.is.signed.in.jarfile.but.is.not.signed.in.jarinputstream"), |
| 1147 | + cenEntry.getName())); |
| 1148 | + } else if (locHasSigners) { |
| 1149 | + crossChkWarnings.add(String.format(rb.getString( |
| 1150 | + "entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile"), |
| 1151 | + locEntry.getName())); |
| 1152 | + } |
| 1153 | + } |
| 1154 | + |
| 1155 | + private void displayCrossChkWarnings() { |
| 1156 | + System.out.println(); |
| 1157 | + // First is a summary warning |
| 1158 | + System.out.println(rb.getString("jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream")); |
| 1159 | + // each warning message with prefix "- " |
| 1160 | + crossChkWarnings.forEach(warning -> System.out.println("- " + warning)); |
| 1161 | + } |
| 1162 | + |
1020 | 1163 | private void displayMessagesAndResult(boolean isSigning) { |
1021 | 1164 | String result; |
1022 | 1165 | List<String> errors = new ArrayList<>(); |
@@ -1248,13 +1391,19 @@ private void displayMessagesAndResult(boolean isSigning) { |
1248 | 1391 | System.out.println(rb.getString("Warning.")); |
1249 | 1392 | warnings.forEach(System.out::println); |
1250 | 1393 | } |
| 1394 | + if (!crossChkWarnings.isEmpty()) { |
| 1395 | + displayCrossChkWarnings(); |
| 1396 | + } |
1251 | 1397 | } else { |
1252 | 1398 | if (!errors.isEmpty() || !warnings.isEmpty()) { |
1253 | 1399 | System.out.println(); |
1254 | 1400 | System.out.println(rb.getString("Warning.")); |
1255 | 1401 | errors.forEach(System.out::println); |
1256 | 1402 | warnings.forEach(System.out::println); |
1257 | 1403 | } |
| 1404 | + if (!crossChkWarnings.isEmpty()) { |
| 1405 | + displayCrossChkWarnings(); |
| 1406 | + } |
1258 | 1407 | } |
1259 | 1408 | if (!isSigning && (!errors.isEmpty() || !warnings.isEmpty())) { |
1260 | 1409 | if (! (verbose != null && showcerts)) { |
|
0 commit comments