|
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.*; |
@@ -217,6 +219,8 @@ public static void main(String args[]) throws Exception { |
217 | 219 | private Throwable chainNotValidatedReason = null; |
218 | 220 | private Throwable tsaChainNotValidatedReason = null; |
219 | 221 |
|
| 222 | + private List<String> crossChkWarnings = new ArrayList<>(); |
| 223 | + |
220 | 224 | PKIXBuilderParameters pkixParameters; |
221 | 225 | Set<X509Certificate> trustedCerts = new HashSet<>(); |
222 | 226 |
|
@@ -1045,6 +1049,7 @@ void verifyJar(String jarName) |
1045 | 1049 | } |
1046 | 1050 | } |
1047 | 1051 | System.out.println(); |
| 1052 | + crossCheckEntries(jarName); |
1048 | 1053 |
|
1049 | 1054 | if (!anySigned) { |
1050 | 1055 | if (disabledAlgFound) { |
@@ -1079,6 +1084,143 @@ void verifyJar(String jarName) |
1079 | 1084 | System.exit(1); |
1080 | 1085 | } |
1081 | 1086 |
|
| 1087 | + private void crossCheckEntries(String jarName) throws Exception { |
| 1088 | + Set<String> locEntries = new HashSet<>(); |
| 1089 | + |
| 1090 | + try (JarFile jarFile = new JarFile(jarName); |
| 1091 | + JarInputStream jis = new JarInputStream( |
| 1092 | + Files.newInputStream(Path.of(jarName)))) { |
| 1093 | + |
| 1094 | + Manifest cenManifest = jarFile.getManifest(); |
| 1095 | + Manifest locManifest = jis.getManifest(); |
| 1096 | + compareManifest(cenManifest, locManifest); |
| 1097 | + |
| 1098 | + JarEntry locEntry; |
| 1099 | + while ((locEntry = jis.getNextJarEntry()) != null) { |
| 1100 | + String entryName = locEntry.getName(); |
| 1101 | + locEntries.add(entryName); |
| 1102 | + |
| 1103 | + JarEntry cenEntry = jarFile.getJarEntry(entryName); |
| 1104 | + if (cenEntry == null) { |
| 1105 | + crossChkWarnings.add(String.format(rb.getString( |
| 1106 | + "entry.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1107 | + entryName)); |
| 1108 | + continue; |
| 1109 | + } |
| 1110 | + |
| 1111 | + try { |
| 1112 | + readEntry(jis); |
| 1113 | + } catch (SecurityException e) { |
| 1114 | + crossChkWarnings.add(String.format(rb.getString( |
| 1115 | + "signature.verification.failed.on.entry.1.when.reading.via.jarinputstream"), |
| 1116 | + entryName)); |
| 1117 | + continue; |
| 1118 | + } |
| 1119 | + |
| 1120 | + try (InputStream cenInputStream = jarFile.getInputStream(cenEntry)) { |
| 1121 | + if (cenInputStream == null) { |
| 1122 | + crossChkWarnings.add(String.format(rb.getString( |
| 1123 | + "entry.1.present.in.jarfile.but.unreadable"), |
| 1124 | + entryName)); |
| 1125 | + continue; |
| 1126 | + } else { |
| 1127 | + try { |
| 1128 | + readEntry(cenInputStream); |
| 1129 | + } catch (SecurityException e) { |
| 1130 | + crossChkWarnings.add(String.format(rb.getString( |
| 1131 | + "signature.verification.failed.on.entry.1.when.reading.via.jarfile"), |
| 1132 | + entryName)); |
| 1133 | + continue; |
| 1134 | + } |
| 1135 | + } |
| 1136 | + } |
| 1137 | + |
| 1138 | + compareSigners(cenEntry, locEntry); |
| 1139 | + } |
| 1140 | + |
| 1141 | + jarFile.stream() |
| 1142 | + .map(JarEntry::getName) |
| 1143 | + .filter(n -> !locEntries.contains(n) && !n.equals(JarFile.MANIFEST_NAME)) |
| 1144 | + .forEach(n -> crossChkWarnings.add(String.format(rb.getString( |
| 1145 | + "entry.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), n))); |
| 1146 | + } |
| 1147 | + } |
| 1148 | + |
| 1149 | + private void readEntry(InputStream is) throws IOException { |
| 1150 | + is.transferTo(OutputStream.nullOutputStream()); |
| 1151 | + } |
| 1152 | + |
| 1153 | + private void compareManifest(Manifest cenManifest, Manifest locManifest) { |
| 1154 | + if (cenManifest == null) { |
| 1155 | + crossChkWarnings.add(rb.getString( |
| 1156 | + "manifest.missing.when.reading.jarfile")); |
| 1157 | + return; |
| 1158 | + } |
| 1159 | + if (locManifest == null) { |
| 1160 | + crossChkWarnings.add(rb.getString( |
| 1161 | + "manifest.missing.when.reading.jarinputstream")); |
| 1162 | + return; |
| 1163 | + } |
| 1164 | + |
| 1165 | + Attributes cenMainAttrs = cenManifest.getMainAttributes(); |
| 1166 | + Attributes locMainAttrs = locManifest.getMainAttributes(); |
| 1167 | + |
| 1168 | + for (Object key : cenMainAttrs.keySet()) { |
| 1169 | + Object cenValue = cenMainAttrs.get(key); |
| 1170 | + Object locValue = locMainAttrs.get(key); |
| 1171 | + |
| 1172 | + if (locValue == null) { |
| 1173 | + crossChkWarnings.add(String.format(rb.getString( |
| 1174 | + "manifest.attribute.1.present.when.reading.jarfile.but.missing.via.jarinputstream"), |
| 1175 | + key)); |
| 1176 | + } else if (!cenValue.equals(locValue)) { |
| 1177 | + crossChkWarnings.add(String.format(rb.getString( |
| 1178 | + "manifest.attribute.1.differs.jarfile.value.2.jarinputstream.value.3"), |
| 1179 | + key, cenValue, locValue)); |
| 1180 | + } |
| 1181 | + } |
| 1182 | + |
| 1183 | + for (Object key : locMainAttrs.keySet()) { |
| 1184 | + if (!cenMainAttrs.containsKey(key)) { |
| 1185 | + crossChkWarnings.add(String.format(rb.getString( |
| 1186 | + "manifest.attribute.1.present.when.reading.jarinputstream.but.missing.via.jarfile"), |
| 1187 | + key)); |
| 1188 | + } |
| 1189 | + } |
| 1190 | + } |
| 1191 | + |
| 1192 | + private void compareSigners(JarEntry cenEntry, JarEntry locEntry) { |
| 1193 | + CodeSigner[] cenSigners = cenEntry.getCodeSigners(); |
| 1194 | + CodeSigner[] locSigners = locEntry.getCodeSigners(); |
| 1195 | + |
| 1196 | + boolean cenHasSigners = cenSigners != null; |
| 1197 | + boolean locHasSigners = locSigners != null; |
| 1198 | + |
| 1199 | + if (cenHasSigners && locHasSigners) { |
| 1200 | + if (!Arrays.equals(cenSigners, locSigners)) { |
| 1201 | + crossChkWarnings.add(String.format(rb.getString( |
| 1202 | + "codesigners.different.for.entry.1.when.reading.jarfile.and.jarinputstream"), |
| 1203 | + cenEntry.getName())); |
| 1204 | + } |
| 1205 | + } else if (cenHasSigners) { |
| 1206 | + crossChkWarnings.add(String.format(rb.getString( |
| 1207 | + "entry.1.is.signed.in.jarfile.but.is.not.signed.in.jarinputstream"), |
| 1208 | + cenEntry.getName())); |
| 1209 | + } else if (locHasSigners) { |
| 1210 | + crossChkWarnings.add(String.format(rb.getString( |
| 1211 | + "entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile"), |
| 1212 | + locEntry.getName())); |
| 1213 | + } |
| 1214 | + } |
| 1215 | + |
| 1216 | + private void displayCrossChkWarnings() { |
| 1217 | + System.out.println(); |
| 1218 | + // First is a summary warning |
| 1219 | + System.out.println(rb.getString("jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream")); |
| 1220 | + // each warning message with prefix "- " |
| 1221 | + crossChkWarnings.forEach(warning -> System.out.println("- " + warning)); |
| 1222 | + } |
| 1223 | + |
1082 | 1224 | private void displayMessagesAndResult(boolean isSigning) { |
1083 | 1225 | String result; |
1084 | 1226 | List<String> errors = new ArrayList<>(); |
@@ -1314,13 +1456,19 @@ private void displayMessagesAndResult(boolean isSigning) { |
1314 | 1456 | System.out.println(rb.getString("Warning.")); |
1315 | 1457 | warnings.forEach(System.out::println); |
1316 | 1458 | } |
| 1459 | + if (!crossChkWarnings.isEmpty()) { |
| 1460 | + displayCrossChkWarnings(); |
| 1461 | + } |
1317 | 1462 | } else { |
1318 | 1463 | if (!errors.isEmpty() || !warnings.isEmpty()) { |
1319 | 1464 | System.out.println(); |
1320 | 1465 | System.out.println(rb.getString("Warning.")); |
1321 | 1466 | errors.forEach(System.out::println); |
1322 | 1467 | warnings.forEach(System.out::println); |
1323 | 1468 | } |
| 1469 | + if (!crossChkWarnings.isEmpty()) { |
| 1470 | + displayCrossChkWarnings(); |
| 1471 | + } |
1324 | 1472 | } |
1325 | 1473 | if (!isSigning && (!errors.isEmpty() || !warnings.isEmpty())) { |
1326 | 1474 | if (! (verbose != null && showcerts)) { |
|
0 commit comments