|
28 | 28 | import java.io.*; |
29 | 29 | import java.net.UnknownHostException; |
30 | 30 | import java.net.URLClassLoader; |
| 31 | +import java.nio.file.Files; |
| 32 | +import java.nio.file.Path; |
31 | 33 | import java.security.cert.CertPathValidatorException; |
32 | 34 | import java.security.cert.PKIXBuilderParameters; |
33 | 35 | import java.security.interfaces.ECKey; |
@@ -228,6 +230,8 @@ public static void main(String args[]) throws Exception { |
228 | 230 | private Throwable chainNotValidatedReason = null; |
229 | 231 | private Throwable tsaChainNotValidatedReason = null; |
230 | 232 |
|
| 233 | + private List<String> crossChkWarnings = new ArrayList<>(); |
| 234 | + |
231 | 235 | PKIXBuilderParameters pkixParameters; |
232 | 236 | Set<X509Certificate> trustedCerts = new HashSet<>(); |
233 | 237 |
|
@@ -1099,6 +1103,7 @@ void verifyJar(String jarName) |
1099 | 1103 | } |
1100 | 1104 | } |
1101 | 1105 | System.out.println(); |
| 1106 | + crossCheckEntries(jarName); |
1102 | 1107 |
|
1103 | 1108 | if (!anySigned) { |
1104 | 1109 | if (disabledAlgFound) { |
@@ -1133,6 +1138,143 @@ void verifyJar(String jarName) |
1133 | 1138 | System.exit(1); |
1134 | 1139 | } |
1135 | 1140 |
|
| 1141 | + private void crossCheckEntries(String jarName) throws Exception { |
| 1142 | + Set<String> locEntries = new HashSet<>(); |
| 1143 | + |
| 1144 | + try (JarFile jarFile = new JarFile(jarName); |
| 1145 | + JarInputStream jis = new JarInputStream( |
| 1146 | + Files.newInputStream(Path.of(jarName)))) { |
| 1147 | + |
| 1148 | + Manifest cenManifest = jarFile.getManifest(); |
| 1149 | + Manifest locManifest = jis.getManifest(); |
| 1150 | + compareManifest(cenManifest, locManifest); |
| 1151 | + |
| 1152 | + JarEntry locEntry; |
| 1153 | + while ((locEntry = jis.getNextJarEntry()) != null) { |
| 1154 | + String entryName = locEntry.getName(); |
| 1155 | + locEntries.add(entryName); |
| 1156 | + |
| 1157 | + JarEntry cenEntry = jarFile.getJarEntry(entryName); |
| 1158 | + if (cenEntry == null) { |
| 1159 | + crossChkWarnings.add(String.format(rb.getString( |
| 1160 | + "entry.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1161 | + entryName)); |
| 1162 | + continue; |
| 1163 | + } |
| 1164 | + |
| 1165 | + try { |
| 1166 | + readEntry(jis); |
| 1167 | + } catch (SecurityException e) { |
| 1168 | + crossChkWarnings.add(String.format(rb.getString( |
| 1169 | + "signature.verification.failed.on.entry.1.when.reading.via.jarinputstream"), |
| 1170 | + entryName)); |
| 1171 | + continue; |
| 1172 | + } |
| 1173 | + |
| 1174 | + try (InputStream cenInputStream = jarFile.getInputStream(cenEntry)) { |
| 1175 | + if (cenInputStream == null) { |
| 1176 | + crossChkWarnings.add(String.format(rb.getString( |
| 1177 | + "entry.1.present.in.jarfile.but.unreadable"), |
| 1178 | + entryName)); |
| 1179 | + continue; |
| 1180 | + } else { |
| 1181 | + try { |
| 1182 | + readEntry(cenInputStream); |
| 1183 | + } catch (SecurityException e) { |
| 1184 | + crossChkWarnings.add(String.format(rb.getString( |
| 1185 | + "signature.verification.failed.on.entry.1.when.reading.via.jarfile"), |
| 1186 | + entryName)); |
| 1187 | + continue; |
| 1188 | + } |
| 1189 | + } |
| 1190 | + } |
| 1191 | + |
| 1192 | + compareSigners(cenEntry, locEntry); |
| 1193 | + } |
| 1194 | + |
| 1195 | + jarFile.stream() |
| 1196 | + .map(JarEntry::getName) |
| 1197 | + .filter(n -> !locEntries.contains(n) && !n.equals(JarFile.MANIFEST_NAME)) |
| 1198 | + .forEach(n -> crossChkWarnings.add(String.format(rb.getString( |
| 1199 | + "entry.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), n))); |
| 1200 | + } |
| 1201 | + } |
| 1202 | + |
| 1203 | + private void readEntry(InputStream is) throws IOException { |
| 1204 | + is.transferTo(OutputStream.nullOutputStream()); |
| 1205 | + } |
| 1206 | + |
| 1207 | + private void compareManifest(Manifest cenManifest, Manifest locManifest) { |
| 1208 | + if (cenManifest == null) { |
| 1209 | + crossChkWarnings.add(rb.getString( |
| 1210 | + "manifest.missing.when.reading.jarfile")); |
| 1211 | + return; |
| 1212 | + } |
| 1213 | + if (locManifest == null) { |
| 1214 | + crossChkWarnings.add(rb.getString( |
| 1215 | + "manifest.missing.when.reading.jarinputstream")); |
| 1216 | + return; |
| 1217 | + } |
| 1218 | + |
| 1219 | + Attributes cenMainAttrs = cenManifest.getMainAttributes(); |
| 1220 | + Attributes locMainAttrs = locManifest.getMainAttributes(); |
| 1221 | + |
| 1222 | + for (Object key : cenMainAttrs.keySet()) { |
| 1223 | + Object cenValue = cenMainAttrs.get(key); |
| 1224 | + Object locValue = locMainAttrs.get(key); |
| 1225 | + |
| 1226 | + if (locValue == null) { |
| 1227 | + crossChkWarnings.add(String.format(rb.getString( |
| 1228 | + "manifest.attribute.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), |
| 1229 | + key)); |
| 1230 | + } else if (!cenValue.equals(locValue)) { |
| 1231 | + crossChkWarnings.add(String.format(rb.getString( |
| 1232 | + "manifest.attribute.1.differs.jarfile.value.2.jarinputstream.value.3"), |
| 1233 | + key, cenValue, locValue)); |
| 1234 | + } |
| 1235 | + } |
| 1236 | + |
| 1237 | + for (Object key : locMainAttrs.keySet()) { |
| 1238 | + if (!cenMainAttrs.containsKey(key)) { |
| 1239 | + crossChkWarnings.add(String.format(rb.getString( |
| 1240 | + "manifest.attribute.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1241 | + key)); |
| 1242 | + } |
| 1243 | + } |
| 1244 | + } |
| 1245 | + |
| 1246 | + private void compareSigners(JarEntry cenEntry, JarEntry locEntry) { |
| 1247 | + CodeSigner[] cenSigners = cenEntry.getCodeSigners(); |
| 1248 | + CodeSigner[] locSigners = locEntry.getCodeSigners(); |
| 1249 | + |
| 1250 | + boolean cenHasSigners = cenSigners != null; |
| 1251 | + boolean locHasSigners = locSigners != null; |
| 1252 | + |
| 1253 | + if (cenHasSigners && locHasSigners) { |
| 1254 | + if (!Arrays.equals(cenSigners, locSigners)) { |
| 1255 | + crossChkWarnings.add(String.format(rb.getString( |
| 1256 | + "codesigners.different.for.entry.1.when.reading.jarfile.and.jarinputstream"), |
| 1257 | + cenEntry.getName())); |
| 1258 | + } |
| 1259 | + } else if (cenHasSigners) { |
| 1260 | + crossChkWarnings.add(String.format(rb.getString( |
| 1261 | + "entry.1.is.signed.in.jarfile.but.is.not.signed.in.jarinputstream"), |
| 1262 | + cenEntry.getName())); |
| 1263 | + } else if (locHasSigners) { |
| 1264 | + crossChkWarnings.add(String.format(rb.getString( |
| 1265 | + "entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile"), |
| 1266 | + locEntry.getName())); |
| 1267 | + } |
| 1268 | + } |
| 1269 | + |
| 1270 | + private void displayCrossChkWarnings() { |
| 1271 | + System.out.println(); |
| 1272 | + // First is a summary warning |
| 1273 | + System.out.println(rb.getString("jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream")); |
| 1274 | + // each warning message with prefix "- " |
| 1275 | + crossChkWarnings.forEach(warning -> System.out.println("- " + warning)); |
| 1276 | + } |
| 1277 | + |
1136 | 1278 | private void displayMessagesAndResult(boolean isSigning) { |
1137 | 1279 | String result; |
1138 | 1280 | List<String> errors = new ArrayList<>(); |
@@ -1359,13 +1501,19 @@ private void displayMessagesAndResult(boolean isSigning) { |
1359 | 1501 | System.out.println(rb.getString("Warning.")); |
1360 | 1502 | warnings.forEach(System.out::println); |
1361 | 1503 | } |
| 1504 | + if (!crossChkWarnings.isEmpty()) { |
| 1505 | + displayCrossChkWarnings(); |
| 1506 | + } |
1362 | 1507 | } else { |
1363 | 1508 | if (!errors.isEmpty() || !warnings.isEmpty()) { |
1364 | 1509 | System.out.println(); |
1365 | 1510 | System.out.println(rb.getString("Warning.")); |
1366 | 1511 | errors.forEach(System.out::println); |
1367 | 1512 | warnings.forEach(System.out::println); |
1368 | 1513 | } |
| 1514 | + if (!crossChkWarnings.isEmpty()) { |
| 1515 | + displayCrossChkWarnings(); |
| 1516 | + } |
1369 | 1517 | } |
1370 | 1518 |
|
1371 | 1519 | if (!isSigning && (!errors.isEmpty() || !warnings.isEmpty())) { |
|
0 commit comments