|
27 | 27 |
|
28 | 28 | import java.io.*; |
29 | 29 | import java.net.UnknownHostException; |
| 30 | +import java.nio.file.Files; |
| 31 | +import java.nio.file.Path; |
30 | 32 | import java.security.cert.CertPathValidatorException; |
31 | 33 | import java.security.cert.PKIXBuilderParameters; |
32 | 34 | import java.util.*; |
@@ -222,6 +224,8 @@ public static void main(String args[]) throws Exception { |
222 | 224 | private Throwable chainNotValidatedReason = null; |
223 | 225 | private Throwable tsaChainNotValidatedReason = null; |
224 | 226 |
|
| 227 | + private List<String> crossChkWarnings = new ArrayList<>(); |
| 228 | + |
225 | 229 | PKIXBuilderParameters pkixParameters; |
226 | 230 | Set<X509Certificate> trustedCerts = new HashSet<>(); |
227 | 231 |
|
@@ -1069,6 +1073,7 @@ void verifyJar(String jarName) |
1069 | 1073 | } |
1070 | 1074 | } |
1071 | 1075 | System.out.println(); |
| 1076 | + crossCheckEntries(jarName); |
1072 | 1077 |
|
1073 | 1078 | if (!anySigned) { |
1074 | 1079 | if (disabledAlgFound) { |
@@ -1103,6 +1108,143 @@ void verifyJar(String jarName) |
1103 | 1108 | System.exit(1); |
1104 | 1109 | } |
1105 | 1110 |
|
| 1111 | + private void crossCheckEntries(String jarName) throws Exception { |
| 1112 | + Set<String> locEntries = new HashSet<>(); |
| 1113 | + |
| 1114 | + try (JarFile jarFile = new JarFile(jarName); |
| 1115 | + JarInputStream jis = new JarInputStream( |
| 1116 | + Files.newInputStream(Path.of(jarName)))) { |
| 1117 | + |
| 1118 | + Manifest cenManifest = jarFile.getManifest(); |
| 1119 | + Manifest locManifest = jis.getManifest(); |
| 1120 | + compareManifest(cenManifest, locManifest); |
| 1121 | + |
| 1122 | + JarEntry locEntry; |
| 1123 | + while ((locEntry = jis.getNextJarEntry()) != null) { |
| 1124 | + String entryName = locEntry.getName(); |
| 1125 | + locEntries.add(entryName); |
| 1126 | + |
| 1127 | + JarEntry cenEntry = jarFile.getJarEntry(entryName); |
| 1128 | + if (cenEntry == null) { |
| 1129 | + crossChkWarnings.add(String.format(rb.getString( |
| 1130 | + "entry.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1131 | + entryName)); |
| 1132 | + continue; |
| 1133 | + } |
| 1134 | + |
| 1135 | + try { |
| 1136 | + readEntry(jis); |
| 1137 | + } catch (SecurityException e) { |
| 1138 | + crossChkWarnings.add(String.format(rb.getString( |
| 1139 | + "signature.verification.failed.on.entry.1.when.reading.via.jarinputstream"), |
| 1140 | + entryName)); |
| 1141 | + continue; |
| 1142 | + } |
| 1143 | + |
| 1144 | + try (InputStream cenInputStream = jarFile.getInputStream(cenEntry)) { |
| 1145 | + if (cenInputStream == null) { |
| 1146 | + crossChkWarnings.add(String.format(rb.getString( |
| 1147 | + "entry.1.present.in.jarfile.but.unreadable"), |
| 1148 | + entryName)); |
| 1149 | + continue; |
| 1150 | + } else { |
| 1151 | + try { |
| 1152 | + readEntry(cenInputStream); |
| 1153 | + } catch (SecurityException e) { |
| 1154 | + crossChkWarnings.add(String.format(rb.getString( |
| 1155 | + "signature.verification.failed.on.entry.1.when.reading.via.jarfile"), |
| 1156 | + entryName)); |
| 1157 | + continue; |
| 1158 | + } |
| 1159 | + } |
| 1160 | + } |
| 1161 | + |
| 1162 | + compareSigners(cenEntry, locEntry); |
| 1163 | + } |
| 1164 | + |
| 1165 | + jarFile.stream() |
| 1166 | + .map(JarEntry::getName) |
| 1167 | + .filter(n -> !locEntries.contains(n) && !n.equals(JarFile.MANIFEST_NAME)) |
| 1168 | + .forEach(n -> crossChkWarnings.add(String.format(rb.getString( |
| 1169 | + "entry.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), n))); |
| 1170 | + } |
| 1171 | + } |
| 1172 | + |
| 1173 | + private void readEntry(InputStream is) throws IOException { |
| 1174 | + is.transferTo(OutputStream.nullOutputStream()); |
| 1175 | + } |
| 1176 | + |
| 1177 | + private void compareManifest(Manifest cenManifest, Manifest locManifest) { |
| 1178 | + if (cenManifest == null) { |
| 1179 | + crossChkWarnings.add(rb.getString( |
| 1180 | + "manifest.missing.when.reading.jarfile")); |
| 1181 | + return; |
| 1182 | + } |
| 1183 | + if (locManifest == null) { |
| 1184 | + crossChkWarnings.add(rb.getString( |
| 1185 | + "manifest.missing.when.reading.jarinputstream")); |
| 1186 | + return; |
| 1187 | + } |
| 1188 | + |
| 1189 | + Attributes cenMainAttrs = cenManifest.getMainAttributes(); |
| 1190 | + Attributes locMainAttrs = locManifest.getMainAttributes(); |
| 1191 | + |
| 1192 | + for (Object key : cenMainAttrs.keySet()) { |
| 1193 | + Object cenValue = cenMainAttrs.get(key); |
| 1194 | + Object locValue = locMainAttrs.get(key); |
| 1195 | + |
| 1196 | + if (locValue == null) { |
| 1197 | + crossChkWarnings.add(String.format(rb.getString( |
| 1198 | + "manifest.attribute.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), |
| 1199 | + key)); |
| 1200 | + } else if (!cenValue.equals(locValue)) { |
| 1201 | + crossChkWarnings.add(String.format(rb.getString( |
| 1202 | + "manifest.attribute.1.differs.jarfile.value.2.jarinputstream.value.3"), |
| 1203 | + key, cenValue, locValue)); |
| 1204 | + } |
| 1205 | + } |
| 1206 | + |
| 1207 | + for (Object key : locMainAttrs.keySet()) { |
| 1208 | + if (!cenMainAttrs.containsKey(key)) { |
| 1209 | + crossChkWarnings.add(String.format(rb.getString( |
| 1210 | + "manifest.attribute.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1211 | + key)); |
| 1212 | + } |
| 1213 | + } |
| 1214 | + } |
| 1215 | + |
| 1216 | + private void compareSigners(JarEntry cenEntry, JarEntry locEntry) { |
| 1217 | + CodeSigner[] cenSigners = cenEntry.getCodeSigners(); |
| 1218 | + CodeSigner[] locSigners = locEntry.getCodeSigners(); |
| 1219 | + |
| 1220 | + boolean cenHasSigners = cenSigners != null; |
| 1221 | + boolean locHasSigners = locSigners != null; |
| 1222 | + |
| 1223 | + if (cenHasSigners && locHasSigners) { |
| 1224 | + if (!Arrays.equals(cenSigners, locSigners)) { |
| 1225 | + crossChkWarnings.add(String.format(rb.getString( |
| 1226 | + "codesigners.different.for.entry.1.when.reading.jarfile.and.jarinputstream"), |
| 1227 | + cenEntry.getName())); |
| 1228 | + } |
| 1229 | + } else if (cenHasSigners) { |
| 1230 | + crossChkWarnings.add(String.format(rb.getString( |
| 1231 | + "entry.1.is.signed.in.jarfile.but.is.not.signed.in.jarinputstream"), |
| 1232 | + cenEntry.getName())); |
| 1233 | + } else if (locHasSigners) { |
| 1234 | + crossChkWarnings.add(String.format(rb.getString( |
| 1235 | + "entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile"), |
| 1236 | + locEntry.getName())); |
| 1237 | + } |
| 1238 | + } |
| 1239 | + |
| 1240 | + private void displayCrossChkWarnings() { |
| 1241 | + System.out.println(); |
| 1242 | + // First is a summary warning |
| 1243 | + System.out.println(rb.getString("jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream")); |
| 1244 | + // each warning message with prefix "- " |
| 1245 | + crossChkWarnings.forEach(warning -> System.out.println("- " + warning)); |
| 1246 | + } |
| 1247 | + |
1106 | 1248 | private void displayMessagesAndResult(boolean isSigning) { |
1107 | 1249 | String result; |
1108 | 1250 | List<String> errors = new ArrayList<>(); |
@@ -1329,13 +1471,19 @@ private void displayMessagesAndResult(boolean isSigning) { |
1329 | 1471 | System.out.println(rb.getString("Warning.")); |
1330 | 1472 | warnings.forEach(System.out::println); |
1331 | 1473 | } |
| 1474 | + if (!crossChkWarnings.isEmpty()) { |
| 1475 | + displayCrossChkWarnings(); |
| 1476 | + } |
1332 | 1477 | } else { |
1333 | 1478 | if (!errors.isEmpty() || !warnings.isEmpty()) { |
1334 | 1479 | System.out.println(); |
1335 | 1480 | System.out.println(rb.getString("Warning.")); |
1336 | 1481 | errors.forEach(System.out::println); |
1337 | 1482 | warnings.forEach(System.out::println); |
1338 | 1483 | } |
| 1484 | + if (!crossChkWarnings.isEmpty()) { |
| 1485 | + displayCrossChkWarnings(); |
| 1486 | + } |
1339 | 1487 | } |
1340 | 1488 |
|
1341 | 1489 | if (!isSigning && (!errors.isEmpty() || !warnings.isEmpty())) { |
|
0 commit comments