1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apksig.internal.apk.v1; 18 19 import com.android.apksig.ApkVerifier.Issue; 20 import com.android.apksig.ApkVerifier.IssueWithParams; 21 import com.android.apksig.apk.ApkFormatException; 22 import com.android.apksig.apk.ApkUtils; 23 import com.android.apksig.internal.jar.ManifestParser; 24 import com.android.apksig.internal.util.AndroidSdkVersion; 25 import com.android.apksig.internal.util.InclusiveIntRange; 26 import com.android.apksig.internal.util.MessageDigestSink; 27 import com.android.apksig.internal.zip.CentralDirectoryRecord; 28 import com.android.apksig.internal.zip.LocalFileRecord; 29 import com.android.apksig.util.DataSource; 30 import com.android.apksig.zip.ZipFormatException; 31 import java.io.IOException; 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.security.MessageDigest; 35 import java.security.NoSuchAlgorithmException; 36 import java.security.SignatureException; 37 import java.security.cert.CertificateException; 38 import java.security.cert.X509Certificate; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Base64; 42 import java.util.Base64.Decoder; 43 import java.util.Collection; 44 import java.util.Collections; 45 import java.util.HashMap; 46 import java.util.HashSet; 47 import java.util.List; 48 import java.util.Locale; 49 import java.util.Map; 50 import java.util.Set; 51 import java.util.StringTokenizer; 52 import java.util.jar.Attributes; 53 import sun.security.pkcs.PKCS7; 54 import sun.security.pkcs.SignerInfo; 55 56 /** 57 * APK verifier which uses JAR signing (aka v1 signing scheme). 58 * 59 * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a> 60 */ 61 public abstract class V1SchemeVerifier { 62 63 private static final String MANIFEST_ENTRY_NAME = V1SchemeSigner.MANIFEST_ENTRY_NAME; 64 65 private V1SchemeVerifier() {} 66 67 /** 68 * Verifies the provided APK's JAR signatures and returns the result of verification. APK is 69 * considered verified only if {@link Result#verified} is {@code true}. If verification fails, 70 * the result will contain errors -- see {@link Result#getErrors()}. 71 * 72 * @throws ApkFormatException if the APK is malformed 73 * @throws IOException if an I/O error occurs when reading the APK 74 * @throws NoSuchAlgorithmException if the APK's JAR signatures cannot be verified because a 75 * required cryptographic algorithm implementation is missing 76 */ 77 public static Result verify( 78 DataSource apk, 79 ApkUtils.ZipSections apkSections, 80 Map<Integer, String> supportedApkSigSchemeNames, 81 Set<Integer> foundApkSigSchemeIds, 82 int minSdkVersion, 83 int maxSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException { 84 if (minSdkVersion > maxSdkVersion) { 85 throw new IllegalArgumentException( 86 "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion 87 + ")"); 88 } 89 90 Result result = new Result(); 91 92 // Parse the ZIP Central Directory and check that there are no entries with duplicate names. 93 List<CentralDirectoryRecord> cdRecords = parseZipCentralDirectory(apk, apkSections); 94 Set<String> cdEntryNames = checkForDuplicateEntries(cdRecords, result); 95 if (result.containsErrors()) { 96 return result; 97 } 98 99 // Verify JAR signature(s). 100 Signers.verify( 101 apk, 102 apkSections.getZipCentralDirectoryOffset(), 103 cdRecords, 104 cdEntryNames, 105 supportedApkSigSchemeNames, 106 foundApkSigSchemeIds, 107 minSdkVersion, 108 maxSdkVersion, 109 result); 110 111 return result; 112 } 113 114 /** 115 * Returns the set of entry names and reports any duplicate entry names in the {@code result} 116 * as errors. 117 */ 118 private static Set<String> checkForDuplicateEntries( 119 List<CentralDirectoryRecord> cdRecords, Result result) { 120 Set<String> cdEntryNames = new HashSet<>(cdRecords.size()); 121 Set<String> duplicateCdEntryNames = null; 122 for (CentralDirectoryRecord cdRecord : cdRecords) { 123 String entryName = cdRecord.getName(); 124 if (!cdEntryNames.add(entryName)) { 125 // This is an error. Report this once per duplicate name. 126 if (duplicateCdEntryNames == null) { 127 duplicateCdEntryNames = new HashSet<>(); 128 } 129 if (duplicateCdEntryNames.add(entryName)) { 130 result.addError(Issue.JAR_SIG_DUPLICATE_ZIP_ENTRY, entryName); 131 } 132 } 133 } 134 return cdEntryNames; 135 } 136 137 /** 138 * All JAR signers of an APK. 139 */ 140 private static class Signers { 141 142 /** 143 * Verifies JAR signatures of the provided APK and populates the provided result container 144 * with errors, warnings, and information about signers. The APK is considered verified if 145 * the {@link Result#verified} is {@code true}. 146 */ 147 private static void verify( 148 DataSource apk, 149 long cdStartOffset, 150 List<CentralDirectoryRecord> cdRecords, 151 Set<String> cdEntryNames, 152 Map<Integer, String> supportedApkSigSchemeNames, 153 Set<Integer> foundApkSigSchemeIds, 154 int minSdkVersion, 155 int maxSdkVersion, 156 Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { 157 158 // Find JAR manifest and signature block files. 159 CentralDirectoryRecord manifestEntry = null; 160 Map<String, CentralDirectoryRecord> sigFileEntries = new HashMap<>(1); 161 List<CentralDirectoryRecord> sigBlockEntries = new ArrayList<>(1); 162 for (CentralDirectoryRecord cdRecord : cdRecords) { 163 String entryName = cdRecord.getName(); 164 if (!entryName.startsWith("META-INF/")) { 165 continue; 166 } 167 if ((manifestEntry == null) && (MANIFEST_ENTRY_NAME.equals(entryName))) { 168 manifestEntry = cdRecord; 169 continue; 170 } 171 if (entryName.endsWith(".SF")) { 172 sigFileEntries.put(entryName, cdRecord); 173 continue; 174 } 175 if ((entryName.endsWith(".RSA")) 176 || (entryName.endsWith(".DSA")) 177 || (entryName.endsWith(".EC"))) { 178 sigBlockEntries.add(cdRecord); 179 continue; 180 } 181 } 182 if (manifestEntry == null) { 183 result.addError(Issue.JAR_SIG_NO_MANIFEST); 184 return; 185 } 186 187 // Parse the JAR manifest and check that all JAR entries it references exist in the APK. 188 byte[] manifestBytes; 189 try { 190 manifestBytes = 191 LocalFileRecord.getUncompressedData(apk, manifestEntry, cdStartOffset); 192 } catch (ZipFormatException e) { 193 throw new ApkFormatException("Malformed ZIP entry: " + manifestEntry.getName(), e); 194 } 195 Map<String, ManifestParser.Section> entryNameToManifestSection = null; 196 ManifestParser manifest = new ManifestParser(manifestBytes); 197 ManifestParser.Section manifestMainSection = manifest.readSection(); 198 List<ManifestParser.Section> manifestIndividualSections = manifest.readAllSections(); 199 entryNameToManifestSection = new HashMap<>(manifestIndividualSections.size()); 200 int manifestSectionNumber = 0; 201 for (ManifestParser.Section manifestSection : manifestIndividualSections) { 202 manifestSectionNumber++; 203 String entryName = manifestSection.getName(); 204 if (entryName == null) { 205 result.addError(Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION, manifestSectionNumber); 206 continue; 207 } 208 if (entryNameToManifestSection.put(entryName, manifestSection) != null) { 209 result.addError(Issue.JAR_SIG_DUPLICATE_MANIFEST_SECTION, entryName); 210 continue; 211 } 212 if (!cdEntryNames.contains(entryName)) { 213 result.addError( 214 Issue.JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST, entryName); 215 continue; 216 } 217 } 218 if (result.containsErrors()) { 219 return; 220 } 221 // STATE OF AFFAIRS: 222 // * All JAR entries listed in JAR manifest are present in the APK. 223 224 // Identify signers 225 List<Signer> signers = new ArrayList<>(sigBlockEntries.size()); 226 for (CentralDirectoryRecord sigBlockEntry : sigBlockEntries) { 227 String sigBlockEntryName = sigBlockEntry.getName(); 228 int extensionDelimiterIndex = sigBlockEntryName.lastIndexOf('.'); 229 if (extensionDelimiterIndex == -1) { 230 throw new RuntimeException( 231 "Signature block file name does not contain extension: " 232 + sigBlockEntryName); 233 } 234 String sigFileEntryName = 235 sigBlockEntryName.substring(0, extensionDelimiterIndex) + ".SF"; 236 CentralDirectoryRecord sigFileEntry = sigFileEntries.get(sigFileEntryName); 237 if (sigFileEntry == null) { 238 result.addWarning( 239 Issue.JAR_SIG_MISSING_FILE, sigBlockEntryName, sigFileEntryName); 240 continue; 241 } 242 String signerName = sigBlockEntryName.substring("META-INF/".length()); 243 Result.SignerInfo signerInfo = 244 new Result.SignerInfo( 245 signerName, sigBlockEntryName, sigFileEntry.getName()); 246 Signer signer = new Signer(signerName, sigBlockEntry, sigFileEntry, signerInfo); 247 signers.add(signer); 248 } 249 if (signers.isEmpty()) { 250 result.addError(Issue.JAR_SIG_NO_SIGNATURES); 251 return; 252 } 253 254 // Verify each signer's signature block file .(RSA|DSA|EC) against the corresponding 255 // signature file .SF. Any error encountered for any signer terminates verification, to 256 // mimic Android's behavior. 257 for (Signer signer : signers) { 258 signer.verifySigBlockAgainstSigFile( 259 apk, cdStartOffset, minSdkVersion, maxSdkVersion); 260 if (signer.getResult().containsErrors()) { 261 result.signers.add(signer.getResult()); 262 } 263 } 264 if (result.containsErrors()) { 265 return; 266 } 267 // STATE OF AFFAIRS: 268 // * All JAR entries listed in JAR manifest are present in the APK. 269 // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). 270 271 // Verify each signer's signature file (.SF) against the JAR manifest. 272 List<Signer> remainingSigners = new ArrayList<>(signers.size()); 273 for (Signer signer : signers) { 274 signer.verifySigFileAgainstManifest( 275 manifestBytes, 276 manifestMainSection, 277 entryNameToManifestSection, 278 supportedApkSigSchemeNames, 279 foundApkSigSchemeIds, 280 minSdkVersion, 281 maxSdkVersion); 282 if (signer.isIgnored()) { 283 result.ignoredSigners.add(signer.getResult()); 284 } else { 285 if (signer.getResult().containsErrors()) { 286 result.signers.add(signer.getResult()); 287 } else { 288 remainingSigners.add(signer); 289 } 290 } 291 } 292 if (result.containsErrors()) { 293 return; 294 } 295 signers = remainingSigners; 296 if (signers.isEmpty()) { 297 result.addError(Issue.JAR_SIG_NO_SIGNATURES); 298 return; 299 } 300 // STATE OF AFFAIRS: 301 // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). 302 // * Contents of all JAR manifest sections listed in .SF files verify against .SF files. 303 // * All JAR entries listed in JAR manifest are present in the APK. 304 305 // Verify data of JAR entries against JAR manifest and .SF files. On Android, an APK's 306 // JAR entry is considered signed by signers associated with an .SF file iff the entry 307 // is mentioned in the .SF file and the entry's digest(s) mentioned in the JAR manifest 308 // match theentry's uncompressed data. Android requires that all such JAR entries are 309 // signed by the same set of signers. This set may be smaller than the set of signers 310 // we've identified so far. 311 Set<Signer> apkSigners = 312 verifyJarEntriesAgainstManifestAndSigners( 313 apk, 314 cdStartOffset, 315 cdRecords, 316 entryNameToManifestSection, 317 signers, 318 minSdkVersion, 319 maxSdkVersion, 320 result); 321 if (result.containsErrors()) { 322 return; 323 } 324 // STATE OF AFFAIRS: 325 // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). 326 // * Contents of all JAR manifest sections listed in .SF files verify against .SF files. 327 // * All JAR entries listed in JAR manifest are present in the APK. 328 // * All JAR entries present in the APK and supposed to be covered by JAR signature 329 // (i.e., reside outside of META-INF/) are covered by signatures from the same set 330 // of signers. 331 332 // Report any JAR entries which aren't covered by signature. 333 Set<String> signatureEntryNames = new HashSet<>(1 + result.signers.size() * 2); 334 signatureEntryNames.add(manifestEntry.getName()); 335 for (Signer signer : apkSigners) { 336 signatureEntryNames.add(signer.getSignatureBlockEntryName()); 337 signatureEntryNames.add(signer.getSignatureFileEntryName()); 338 } 339 for (CentralDirectoryRecord cdRecord : cdRecords) { 340 String entryName = cdRecord.getName(); 341 if ((entryName.startsWith("META-INF/")) 342 && (!entryName.endsWith("/")) 343 && (!signatureEntryNames.contains(entryName))) { 344 result.addWarning(Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY, entryName); 345 } 346 } 347 348 // Reflect the sets of used signers and ignored signers in the result. 349 for (Signer signer : signers) { 350 if (apkSigners.contains(signer)) { 351 result.signers.add(signer.getResult()); 352 } else { 353 result.ignoredSigners.add(signer.getResult()); 354 } 355 } 356 357 result.verified = true; 358 } 359 } 360 361 private static class Signer { 362 private final String mName; 363 private final Result.SignerInfo mResult; 364 private final CentralDirectoryRecord mSignatureFileEntry; 365 private final CentralDirectoryRecord mSignatureBlockEntry; 366 private boolean mIgnored; 367 368 private byte[] mSigFileBytes; 369 private Set<String> mSigFileEntryNames; 370 371 private Signer( 372 String name, 373 CentralDirectoryRecord sigBlockEntry, 374 CentralDirectoryRecord sigFileEntry, 375 Result.SignerInfo result) { 376 mName = name; 377 mResult = result; 378 mSignatureBlockEntry = sigBlockEntry; 379 mSignatureFileEntry = sigFileEntry; 380 } 381 382 public String getName() { 383 return mName; 384 } 385 386 public String getSignatureFileEntryName() { 387 return mSignatureFileEntry.getName(); 388 } 389 390 public String getSignatureBlockEntryName() { 391 return mSignatureBlockEntry.getName(); 392 } 393 394 void setIgnored() { 395 mIgnored = true; 396 } 397 398 public boolean isIgnored() { 399 return mIgnored; 400 } 401 402 public Set<String> getSigFileEntryNames() { 403 return mSigFileEntryNames; 404 } 405 406 public Result.SignerInfo getResult() { 407 return mResult; 408 } 409 410 @SuppressWarnings("restriction") 411 public void verifySigBlockAgainstSigFile( 412 DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion) 413 throws IOException, ApkFormatException, NoSuchAlgorithmException { 414 byte[] sigBlockBytes; 415 try { 416 sigBlockBytes = 417 LocalFileRecord.getUncompressedData( 418 apk, mSignatureBlockEntry, cdStartOffset); 419 } catch (ZipFormatException e) { 420 throw new ApkFormatException( 421 "Malformed ZIP entry: " + mSignatureBlockEntry.getName(), e); 422 } 423 try { 424 mSigFileBytes = 425 LocalFileRecord.getUncompressedData( 426 apk, mSignatureFileEntry, cdStartOffset); 427 } catch (ZipFormatException e) { 428 throw new ApkFormatException( 429 "Malformed ZIP entry: " + mSignatureFileEntry.getName(), e); 430 } 431 PKCS7 sigBlock; 432 try { 433 sigBlock = new PKCS7(sigBlockBytes); 434 } catch (IOException e) { 435 if (e.getCause() instanceof CertificateException) { 436 mResult.addError( 437 Issue.JAR_SIG_MALFORMED_CERTIFICATE, mSignatureBlockEntry.getName(), e); 438 } else { 439 mResult.addError( 440 Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); 441 } 442 return; 443 } 444 SignerInfo[] unverifiedSignerInfos = sigBlock.getSignerInfos(); 445 if ((unverifiedSignerInfos == null) || (unverifiedSignerInfos.length == 0)) { 446 mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName()); 447 return; 448 } 449 450 SignerInfo verifiedSignerInfo = null; 451 if ((unverifiedSignerInfos != null) && (unverifiedSignerInfos.length > 0)) { 452 for (int i = 0; i < unverifiedSignerInfos.length; i++) { 453 SignerInfo unverifiedSignerInfo = unverifiedSignerInfos[i]; 454 String digestAlgorithmOid = 455 unverifiedSignerInfo.getDigestAlgorithmId().getOID().toString(); 456 String signatureAlgorithmOid = 457 unverifiedSignerInfo 458 .getDigestEncryptionAlgorithmId().getOID().toString(); 459 InclusiveIntRange desiredApiLevels = 460 InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion); 461 List<InclusiveIntRange> apiLevelsWhereDigestAndSigAlgorithmSupported = 462 getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid); 463 List<InclusiveIntRange> apiLevelsWhereDigestAlgorithmNotSupported = 464 desiredApiLevels.getValuesNotIn(apiLevelsWhereDigestAndSigAlgorithmSupported); 465 if (!apiLevelsWhereDigestAlgorithmNotSupported.isEmpty()) { 466 mResult.addError( 467 Issue.JAR_SIG_UNSUPPORTED_SIG_ALG, 468 mSignatureBlockEntry.getName(), 469 digestAlgorithmOid, 470 signatureAlgorithmOid, 471 String.valueOf(apiLevelsWhereDigestAlgorithmNotSupported)); 472 return; 473 } 474 try { 475 verifiedSignerInfo = sigBlock.verify(unverifiedSignerInfo, mSigFileBytes); 476 } catch (SignatureException e) { 477 mResult.addError( 478 Issue.JAR_SIG_VERIFY_EXCEPTION, 479 mSignatureBlockEntry.getName(), 480 mSignatureFileEntry.getName(), 481 e); 482 return; 483 } 484 if (verifiedSignerInfo != null) { 485 // Verified 486 break; 487 } 488 489 // Did not verify 490 if (minSdkVersion < AndroidSdkVersion.N) { 491 // Prior to N, Android attempted to verify only the first SignerInfo. 492 mResult.addError( 493 Issue.JAR_SIG_DID_NOT_VERIFY, 494 mSignatureBlockEntry.getName(), 495 mSignatureFileEntry.getName()); 496 return; 497 } 498 } 499 } 500 if (verifiedSignerInfo == null) { 501 mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName()); 502 return; 503 } 504 505 // TODO: PKCS7 class doesn't guarantee that returned certificates' getEncoded returns 506 // the original encoded form of certificates rather than the DER re-encoded form. We 507 // need to replace the PKCS7 parser/verifier. 508 List<X509Certificate> certChain; 509 try { 510 certChain = verifiedSignerInfo.getCertificateChain(sigBlock); 511 } catch (IOException e) { 512 throw new RuntimeException( 513 "Failed to obtain cert chain from " + mSignatureBlockEntry.getName(), e); 514 } 515 if ((certChain == null) || (certChain.isEmpty())) { 516 throw new RuntimeException("Verified SignerInfo does not have a certificate chain"); 517 } 518 mResult.certChain.clear(); 519 mResult.certChain.addAll(certChain); 520 } 521 522 private static final String OID_DIGEST_MD5 = "1.2.840.113549.2.5"; 523 private static final String OID_DIGEST_SHA1 = "1.3.14.3.2.26"; 524 private static final String OID_DIGEST_SHA224 = "2.16.840.1.101.3.4.2.4"; 525 private static final String OID_DIGEST_SHA256 = "2.16.840.1.101.3.4.2.1"; 526 private static final String OID_DIGEST_SHA384 = "2.16.840.1.101.3.4.2.2"; 527 private static final String OID_DIGEST_SHA512 = "2.16.840.1.101.3.4.2.3"; 528 529 private static final String OID_SIG_RSA = "1.2.840.113549.1.1.1"; 530 private static final String OID_SIG_MD5_WITH_RSA = "1.2.840.113549.1.1.4"; 531 private static final String OID_SIG_SHA1_WITH_RSA = "1.2.840.113549.1.1.5"; 532 private static final String OID_SIG_SHA224_WITH_RSA = "1.2.840.113549.1.1.14"; 533 private static final String OID_SIG_SHA256_WITH_RSA = "1.2.840.113549.1.1.11"; 534 private static final String OID_SIG_SHA384_WITH_RSA = "1.2.840.113549.1.1.12"; 535 private static final String OID_SIG_SHA512_WITH_RSA = "1.2.840.113549.1.1.13"; 536 537 private static final String OID_SIG_DSA = "1.2.840.10040.4.1"; 538 private static final String OID_SIG_SHA1_WITH_DSA = "1.2.840.10040.4.3"; 539 private static final String OID_SIG_SHA224_WITH_DSA = "2.16.840.1.101.3.4.3.1"; 540 private static final String OID_SIG_SHA256_WITH_DSA = "2.16.840.1.101.3.4.3.2"; 541 542 private static final String OID_SIG_EC_PUBLIC_KEY = "1.2.840.10045.2.1"; 543 private static final String OID_SIG_SHA1_WITH_ECDSA = "1.2.840.10045.4.1"; 544 private static final String OID_SIG_SHA224_WITH_ECDSA = "1.2.840.10045.4.3.1"; 545 private static final String OID_SIG_SHA256_WITH_ECDSA = "1.2.840.10045.4.3.2"; 546 private static final String OID_SIG_SHA384_WITH_ECDSA = "1.2.840.10045.4.3.3"; 547 private static final String OID_SIG_SHA512_WITH_ECDSA = "1.2.840.10045.4.3.4"; 548 549 private static final Map<String, List<InclusiveIntRange>> SUPPORTED_SIG_ALG_OIDS = 550 new HashMap<>(); 551 { 552 addSupportedSigAlg( 553 OID_DIGEST_MD5, OID_SIG_RSA, 554 InclusiveIntRange.from(0)); 555 addSupportedSigAlg( 556 OID_DIGEST_MD5, OID_SIG_MD5_WITH_RSA, 557 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); 558 addSupportedSigAlg( 559 OID_DIGEST_MD5, OID_SIG_SHA1_WITH_RSA, 560 InclusiveIntRange.fromTo(21, 23)); 561 addSupportedSigAlg( 562 OID_DIGEST_MD5, OID_SIG_SHA224_WITH_RSA, 563 InclusiveIntRange.fromTo(21, 23)); 564 addSupportedSigAlg( 565 OID_DIGEST_MD5, OID_SIG_SHA256_WITH_RSA, 566 InclusiveIntRange.fromTo(21, 23)); 567 addSupportedSigAlg( 568 OID_DIGEST_MD5, OID_SIG_SHA384_WITH_RSA, 569 InclusiveIntRange.fromTo(21, 23)); 570 addSupportedSigAlg( 571 OID_DIGEST_MD5, OID_SIG_SHA512_WITH_RSA, 572 InclusiveIntRange.fromTo(21, 23)); 573 574 addSupportedSigAlg( 575 OID_DIGEST_SHA1, OID_SIG_RSA, 576 InclusiveIntRange.from(0)); 577 addSupportedSigAlg( 578 OID_DIGEST_SHA1, OID_SIG_MD5_WITH_RSA, 579 InclusiveIntRange.fromTo(21, 23)); 580 addSupportedSigAlg( 581 OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_RSA, 582 InclusiveIntRange.from(0)); 583 addSupportedSigAlg( 584 OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_RSA, 585 InclusiveIntRange.fromTo(21, 23)); 586 addSupportedSigAlg( 587 OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_RSA, 588 InclusiveIntRange.fromTo(21, 23)); 589 addSupportedSigAlg( 590 OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_RSA, 591 InclusiveIntRange.fromTo(21, 23)); 592 addSupportedSigAlg( 593 OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_RSA, 594 InclusiveIntRange.fromTo(21, 23)); 595 596 addSupportedSigAlg( 597 OID_DIGEST_SHA224, OID_SIG_RSA, 598 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); 599 addSupportedSigAlg( 600 OID_DIGEST_SHA224, OID_SIG_MD5_WITH_RSA, 601 InclusiveIntRange.fromTo(21, 23)); 602 addSupportedSigAlg( 603 OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_RSA, 604 InclusiveIntRange.fromTo(21, 23)); 605 addSupportedSigAlg( 606 OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_RSA, 607 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); 608 addSupportedSigAlg( 609 OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_RSA, 610 InclusiveIntRange.fromTo(21, 21)); 611 addSupportedSigAlg( 612 OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_RSA, 613 InclusiveIntRange.fromTo(21, 23)); 614 addSupportedSigAlg( 615 OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_RSA, 616 InclusiveIntRange.fromTo(21, 23)); 617 618 addSupportedSigAlg( 619 OID_DIGEST_SHA256, OID_SIG_RSA, 620 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18)); 621 addSupportedSigAlg( 622 OID_DIGEST_SHA256, OID_SIG_MD5_WITH_RSA, 623 InclusiveIntRange.fromTo(21, 23)); 624 addSupportedSigAlg( 625 OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_RSA, 626 InclusiveIntRange.fromTo(21, 21)); 627 addSupportedSigAlg( 628 OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_RSA, 629 InclusiveIntRange.fromTo(21, 23)); 630 addSupportedSigAlg( 631 OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_RSA, 632 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18)); 633 addSupportedSigAlg( 634 OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_RSA, 635 InclusiveIntRange.fromTo(21, 23)); 636 addSupportedSigAlg( 637 OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_RSA, 638 InclusiveIntRange.fromTo(21, 23)); 639 640 addSupportedSigAlg( 641 OID_DIGEST_SHA384, OID_SIG_RSA, 642 InclusiveIntRange.from(18)); 643 addSupportedSigAlg( 644 OID_DIGEST_SHA384, OID_SIG_MD5_WITH_RSA, 645 InclusiveIntRange.fromTo(21, 23)); 646 addSupportedSigAlg( 647 OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_RSA, 648 InclusiveIntRange.fromTo(21, 23)); 649 addSupportedSigAlg( 650 OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_RSA, 651 InclusiveIntRange.fromTo(21, 23)); 652 addSupportedSigAlg( 653 OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_RSA, 654 InclusiveIntRange.fromTo(21, 23)); 655 addSupportedSigAlg( 656 OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_RSA, 657 InclusiveIntRange.from(21)); 658 addSupportedSigAlg( 659 OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_RSA, 660 InclusiveIntRange.fromTo(21, 23)); 661 662 addSupportedSigAlg( 663 OID_DIGEST_SHA512, OID_SIG_RSA, 664 InclusiveIntRange.from(18)); 665 addSupportedSigAlg( 666 OID_DIGEST_SHA512, OID_SIG_MD5_WITH_RSA, 667 InclusiveIntRange.fromTo(21, 23)); 668 addSupportedSigAlg( 669 OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_RSA, 670 InclusiveIntRange.fromTo(21, 23)); 671 addSupportedSigAlg( 672 OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_RSA, 673 InclusiveIntRange.fromTo(21, 23)); 674 addSupportedSigAlg( 675 OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_RSA, 676 InclusiveIntRange.fromTo(21, 23)); 677 addSupportedSigAlg( 678 OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_RSA, 679 InclusiveIntRange.fromTo(21, 21)); 680 addSupportedSigAlg( 681 OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_RSA, 682 InclusiveIntRange.from(21)); 683 684 addSupportedSigAlg( 685 OID_DIGEST_MD5, OID_SIG_SHA1_WITH_DSA, 686 InclusiveIntRange.fromTo(21, 23)); 687 addSupportedSigAlg( 688 OID_DIGEST_MD5, OID_SIG_SHA224_WITH_DSA, 689 InclusiveIntRange.fromTo(21, 23)); 690 addSupportedSigAlg( 691 OID_DIGEST_MD5, OID_SIG_SHA256_WITH_DSA, 692 InclusiveIntRange.fromTo(21, 23)); 693 694 addSupportedSigAlg( 695 OID_DIGEST_SHA1, OID_SIG_DSA, 696 InclusiveIntRange.from(0)); 697 addSupportedSigAlg( 698 OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_DSA, 699 InclusiveIntRange.from(9)); 700 addSupportedSigAlg( 701 OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_DSA, 702 InclusiveIntRange.fromTo(21, 23)); 703 addSupportedSigAlg( 704 OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_DSA, 705 InclusiveIntRange.fromTo(21, 23)); 706 707 addSupportedSigAlg( 708 OID_DIGEST_SHA224, OID_SIG_DSA, 709 InclusiveIntRange.from(22)); 710 addSupportedSigAlg( 711 OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_DSA, 712 InclusiveIntRange.fromTo(21, 23)); 713 addSupportedSigAlg( 714 OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_DSA, 715 InclusiveIntRange.from(21)); 716 addSupportedSigAlg( 717 OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_DSA, 718 InclusiveIntRange.fromTo(21, 23)); 719 720 addSupportedSigAlg( 721 OID_DIGEST_SHA256, OID_SIG_DSA, 722 InclusiveIntRange.from(22)); 723 addSupportedSigAlg( 724 OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_DSA, 725 InclusiveIntRange.fromTo(21, 23)); 726 addSupportedSigAlg( 727 OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_DSA, 728 InclusiveIntRange.fromTo(21, 23)); 729 addSupportedSigAlg( 730 OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_DSA, 731 InclusiveIntRange.from(21)); 732 733 addSupportedSigAlg( 734 OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_DSA, 735 InclusiveIntRange.fromTo(21, 23)); 736 addSupportedSigAlg( 737 OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_DSA, 738 InclusiveIntRange.fromTo(21, 23)); 739 addSupportedSigAlg( 740 OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_DSA, 741 InclusiveIntRange.fromTo(21, 23)); 742 743 addSupportedSigAlg( 744 OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_DSA, 745 InclusiveIntRange.fromTo(21, 23)); 746 addSupportedSigAlg( 747 OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_DSA, 748 InclusiveIntRange.fromTo(21, 23)); 749 addSupportedSigAlg( 750 OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_DSA, 751 InclusiveIntRange.fromTo(21, 23)); 752 753 addSupportedSigAlg( 754 OID_DIGEST_SHA1, OID_SIG_EC_PUBLIC_KEY, 755 InclusiveIntRange.from(18)); 756 addSupportedSigAlg( 757 OID_DIGEST_SHA224, OID_SIG_EC_PUBLIC_KEY, 758 InclusiveIntRange.from(21)); 759 addSupportedSigAlg( 760 OID_DIGEST_SHA256, OID_SIG_EC_PUBLIC_KEY, 761 InclusiveIntRange.from(18)); 762 addSupportedSigAlg( 763 OID_DIGEST_SHA384, OID_SIG_EC_PUBLIC_KEY, 764 InclusiveIntRange.from(18)); 765 addSupportedSigAlg( 766 OID_DIGEST_SHA512, OID_SIG_EC_PUBLIC_KEY, 767 InclusiveIntRange.from(18)); 768 769 addSupportedSigAlg( 770 OID_DIGEST_MD5, OID_SIG_SHA1_WITH_ECDSA, 771 InclusiveIntRange.fromTo(21, 23)); 772 addSupportedSigAlg( 773 OID_DIGEST_MD5, OID_SIG_SHA224_WITH_ECDSA, 774 InclusiveIntRange.fromTo(21, 23)); 775 addSupportedSigAlg( 776 OID_DIGEST_MD5, OID_SIG_SHA256_WITH_ECDSA, 777 InclusiveIntRange.fromTo(21, 23)); 778 addSupportedSigAlg( 779 OID_DIGEST_MD5, OID_SIG_SHA384_WITH_ECDSA, 780 InclusiveIntRange.fromTo(21, 23)); 781 addSupportedSigAlg( 782 OID_DIGEST_MD5, OID_SIG_SHA512_WITH_ECDSA, 783 InclusiveIntRange.fromTo(21, 23)); 784 785 addSupportedSigAlg( 786 OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_ECDSA, 787 InclusiveIntRange.from(18)); 788 addSupportedSigAlg( 789 OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_ECDSA, 790 InclusiveIntRange.fromTo(21, 23)); 791 addSupportedSigAlg( 792 OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_ECDSA, 793 InclusiveIntRange.fromTo(21, 23)); 794 addSupportedSigAlg( 795 OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_ECDSA, 796 InclusiveIntRange.fromTo(21, 23)); 797 addSupportedSigAlg( 798 OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_ECDSA, 799 InclusiveIntRange.fromTo(21, 23)); 800 801 addSupportedSigAlg( 802 OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_ECDSA, 803 InclusiveIntRange.fromTo(21, 23)); 804 addSupportedSigAlg( 805 OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_ECDSA, 806 InclusiveIntRange.from(21)); 807 addSupportedSigAlg( 808 OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_ECDSA, 809 InclusiveIntRange.fromTo(21, 23)); 810 addSupportedSigAlg( 811 OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_ECDSA, 812 InclusiveIntRange.fromTo(21, 23)); 813 addSupportedSigAlg( 814 OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_ECDSA, 815 InclusiveIntRange.fromTo(21, 23)); 816 817 addSupportedSigAlg( 818 OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_ECDSA, 819 InclusiveIntRange.fromTo(21, 23)); 820 addSupportedSigAlg( 821 OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_ECDSA, 822 InclusiveIntRange.fromTo(21, 23)); 823 addSupportedSigAlg( 824 OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_ECDSA, 825 InclusiveIntRange.from(21)); 826 addSupportedSigAlg( 827 OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_ECDSA, 828 InclusiveIntRange.fromTo(21, 23)); 829 addSupportedSigAlg( 830 OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_ECDSA, 831 InclusiveIntRange.fromTo(21, 23)); 832 833 addSupportedSigAlg( 834 OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_ECDSA, 835 InclusiveIntRange.fromTo(21, 23)); 836 addSupportedSigAlg( 837 OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_ECDSA, 838 InclusiveIntRange.fromTo(21, 23)); 839 addSupportedSigAlg( 840 OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_ECDSA, 841 InclusiveIntRange.fromTo(21, 23)); 842 addSupportedSigAlg( 843 OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_ECDSA, 844 InclusiveIntRange.from(21)); 845 addSupportedSigAlg( 846 OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_ECDSA, 847 InclusiveIntRange.fromTo(21, 23)); 848 849 addSupportedSigAlg( 850 OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_ECDSA, 851 InclusiveIntRange.fromTo(21, 23)); 852 addSupportedSigAlg( 853 OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_ECDSA, 854 InclusiveIntRange.fromTo(21, 23)); 855 addSupportedSigAlg( 856 OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_ECDSA, 857 InclusiveIntRange.fromTo(21, 23)); 858 addSupportedSigAlg( 859 OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_ECDSA, 860 InclusiveIntRange.fromTo(21, 23)); 861 addSupportedSigAlg( 862 OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_ECDSA, 863 InclusiveIntRange.from(21)); 864 } 865 866 private static void addSupportedSigAlg( 867 String digestAlgorithmOid, 868 String signatureAlgorithmOid, 869 InclusiveIntRange... supportedApiLevels) { 870 SUPPORTED_SIG_ALG_OIDS.put( 871 digestAlgorithmOid + "with" + signatureAlgorithmOid, 872 Arrays.asList(supportedApiLevels)); 873 } 874 875 private List<InclusiveIntRange> getSigAlgSupportedApiLevels( 876 String digestAlgorithmOid, 877 String signatureAlgorithmOid) { 878 List<InclusiveIntRange> result = 879 SUPPORTED_SIG_ALG_OIDS.get(digestAlgorithmOid + "with" + signatureAlgorithmOid); 880 return (result != null) ? result : Collections.emptyList(); 881 } 882 883 public void verifySigFileAgainstManifest( 884 byte[] manifestBytes, 885 ManifestParser.Section manifestMainSection, 886 Map<String, ManifestParser.Section> entryNameToManifestSection, 887 Map<Integer, String> supportedApkSigSchemeNames, 888 Set<Integer> foundApkSigSchemeIds, 889 int minSdkVersion, 890 int maxSdkVersion) throws NoSuchAlgorithmException { 891 // Inspect the main section of the .SF file. 892 ManifestParser sf = new ManifestParser(mSigFileBytes); 893 ManifestParser.Section sfMainSection = sf.readSection(); 894 if (sfMainSection.getAttributeValue(Attributes.Name.SIGNATURE_VERSION) == null) { 895 mResult.addError( 896 Issue.JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE, 897 mSignatureFileEntry.getName()); 898 setIgnored(); 899 return; 900 } 901 902 if (maxSdkVersion >= AndroidSdkVersion.N) { 903 // Android N and newer rejects APKs whose .SF file says they were supposed to be 904 // signed with APK Signature Scheme v2 (or newer) and yet no such signature was 905 // found. 906 checkForStrippedApkSignatures( 907 sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds); 908 if (mResult.containsErrors()) { 909 return; 910 } 911 } 912 913 boolean createdBySigntool = false; 914 String createdBy = sfMainSection.getAttributeValue("Created-By"); 915 if (createdBy != null) { 916 createdBySigntool = createdBy.indexOf("signtool") != -1; 917 } 918 boolean manifestDigestVerified = 919 verifyManifestDigest( 920 sfMainSection, 921 createdBySigntool, 922 manifestBytes, 923 minSdkVersion, 924 maxSdkVersion); 925 if (!createdBySigntool) { 926 verifyManifestMainSectionDigest( 927 sfMainSection, 928 manifestMainSection, 929 manifestBytes, 930 minSdkVersion, 931 maxSdkVersion); 932 } 933 if (mResult.containsErrors()) { 934 return; 935 } 936 937 // Inspect per-entry sections of .SF file. Technically, if the digest of JAR manifest 938 // verifies, per-entry sections should be ignored. However, most Android platform 939 // implementations require that such sections exist. 940 List<ManifestParser.Section> sfSections = sf.readAllSections(); 941 Set<String> sfEntryNames = new HashSet<>(sfSections.size()); 942 int sfSectionNumber = 0; 943 for (ManifestParser.Section sfSection : sfSections) { 944 sfSectionNumber++; 945 String entryName = sfSection.getName(); 946 if (entryName == null) { 947 mResult.addError( 948 Issue.JAR_SIG_UNNNAMED_SIG_FILE_SECTION, 949 mSignatureFileEntry.getName(), 950 sfSectionNumber); 951 setIgnored(); 952 return; 953 } 954 if (!sfEntryNames.add(entryName)) { 955 mResult.addError( 956 Issue.JAR_SIG_DUPLICATE_SIG_FILE_SECTION, 957 mSignatureFileEntry.getName(), 958 entryName); 959 setIgnored(); 960 return; 961 } 962 if (manifestDigestVerified) { 963 // No need to verify this entry's corresponding JAR manifest entry because the 964 // JAR manifest verifies in full. 965 continue; 966 } 967 // Whole-file digest of JAR manifest hasn't been verified. Thus, we need to verify 968 // the digest of the JAR manifest section corresponding to this .SF section. 969 ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName); 970 if (manifestSection == null) { 971 mResult.addError( 972 Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, 973 entryName, 974 mSignatureFileEntry.getName()); 975 setIgnored(); 976 continue; 977 } 978 verifyManifestIndividualSectionDigest( 979 sfSection, 980 createdBySigntool, 981 manifestSection, 982 manifestBytes, 983 minSdkVersion, 984 maxSdkVersion); 985 } 986 mSigFileEntryNames = sfEntryNames; 987 } 988 989 990 /** 991 * Returns {@code true} if the whole-file digest of the manifest against the main section of 992 * the .SF file. 993 */ 994 private boolean verifyManifestDigest( 995 ManifestParser.Section sfMainSection, 996 boolean createdBySigntool, 997 byte[] manifestBytes, 998 int minSdkVersion, 999 int maxSdkVersion) throws NoSuchAlgorithmException { 1000 Collection<NamedDigest> expectedDigests = 1001 getDigestsToVerify( 1002 sfMainSection, 1003 ((createdBySigntool) ? "-Digest" : "-Digest-Manifest"), 1004 minSdkVersion, 1005 maxSdkVersion); 1006 boolean digestFound = !expectedDigests.isEmpty(); 1007 if (!digestFound) { 1008 mResult.addWarning( 1009 Issue.JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE, 1010 mSignatureFileEntry.getName()); 1011 return false; 1012 } 1013 1014 boolean verified = true; 1015 for (NamedDigest expectedDigest : expectedDigests) { 1016 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; 1017 byte[] actual = digest(jcaDigestAlgorithm, manifestBytes); 1018 byte[] expected = expectedDigest.digest; 1019 if (!Arrays.equals(expected, actual)) { 1020 mResult.addWarning( 1021 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY, 1022 V1SchemeSigner.MANIFEST_ENTRY_NAME, 1023 jcaDigestAlgorithm, 1024 mSignatureFileEntry.getName(), 1025 Base64.getEncoder().encodeToString(actual), 1026 Base64.getEncoder().encodeToString(expected)); 1027 verified = false; 1028 } 1029 } 1030 return verified; 1031 } 1032 1033 /** 1034 * Verifies the digest of the manifest's main section against the main section of the .SF 1035 * file. 1036 */ 1037 private void verifyManifestMainSectionDigest( 1038 ManifestParser.Section sfMainSection, 1039 ManifestParser.Section manifestMainSection, 1040 byte[] manifestBytes, 1041 int minSdkVersion, 1042 int maxSdkVersion) throws NoSuchAlgorithmException { 1043 Collection<NamedDigest> expectedDigests = 1044 getDigestsToVerify( 1045 sfMainSection, 1046 "-Digest-Manifest-Main-Attributes", 1047 minSdkVersion, 1048 maxSdkVersion); 1049 if (expectedDigests.isEmpty()) { 1050 return; 1051 } 1052 1053 for (NamedDigest expectedDigest : expectedDigests) { 1054 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; 1055 byte[] actual = 1056 digest( 1057 jcaDigestAlgorithm, 1058 manifestBytes, 1059 manifestMainSection.getStartOffset(), 1060 manifestMainSection.getSizeBytes()); 1061 byte[] expected = expectedDigest.digest; 1062 if (!Arrays.equals(expected, actual)) { 1063 mResult.addError( 1064 Issue.JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY, 1065 jcaDigestAlgorithm, 1066 mSignatureFileEntry.getName(), 1067 Base64.getEncoder().encodeToString(actual), 1068 Base64.getEncoder().encodeToString(expected)); 1069 } 1070 } 1071 } 1072 1073 /** 1074 * Verifies the digest of the manifest's individual section against the corresponding 1075 * individual section of the .SF file. 1076 */ 1077 private void verifyManifestIndividualSectionDigest( 1078 ManifestParser.Section sfIndividualSection, 1079 boolean createdBySigntool, 1080 ManifestParser.Section manifestIndividualSection, 1081 byte[] manifestBytes, 1082 int minSdkVersion, 1083 int maxSdkVersion) throws NoSuchAlgorithmException { 1084 String entryName = sfIndividualSection.getName(); 1085 Collection<NamedDigest> expectedDigests = 1086 getDigestsToVerify( 1087 sfIndividualSection, "-Digest", minSdkVersion, maxSdkVersion); 1088 if (expectedDigests.isEmpty()) { 1089 mResult.addError( 1090 Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, 1091 entryName, 1092 mSignatureFileEntry.getName()); 1093 return; 1094 } 1095 1096 int sectionStartIndex = manifestIndividualSection.getStartOffset(); 1097 int sectionSizeBytes = manifestIndividualSection.getSizeBytes(); 1098 if (createdBySigntool) { 1099 int sectionEndIndex = sectionStartIndex + sectionSizeBytes; 1100 if ((manifestBytes[sectionEndIndex - 1] == '\n') 1101 && (manifestBytes[sectionEndIndex - 2] == '\n')) { 1102 sectionSizeBytes--; 1103 } 1104 } 1105 for (NamedDigest expectedDigest : expectedDigests) { 1106 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; 1107 byte[] actual = 1108 digest( 1109 jcaDigestAlgorithm, 1110 manifestBytes, 1111 sectionStartIndex, 1112 sectionSizeBytes); 1113 byte[] expected = expectedDigest.digest; 1114 if (!Arrays.equals(expected, actual)) { 1115 mResult.addError( 1116 Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY, 1117 entryName, 1118 jcaDigestAlgorithm, 1119 mSignatureFileEntry.getName(), 1120 Base64.getEncoder().encodeToString(actual), 1121 Base64.getEncoder().encodeToString(expected)); 1122 } 1123 } 1124 } 1125 1126 private void checkForStrippedApkSignatures( 1127 ManifestParser.Section sfMainSection, 1128 Map<Integer, String> supportedApkSigSchemeNames, 1129 Set<Integer> foundApkSigSchemeIds) { 1130 String signedWithApkSchemes = 1131 sfMainSection.getAttributeValue( 1132 V1SchemeSigner.SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR); 1133 // This field contains a comma-separated list of APK signature scheme IDs which were 1134 // used to sign this APK. Android rejects APKs where an ID is known to the platform but 1135 // the APK didn't verify using that scheme. 1136 1137 if (signedWithApkSchemes == null) { 1138 // APK signature (e.g., v2 scheme) stripping protections not enabled. 1139 if (!foundApkSigSchemeIds.isEmpty()) { 1140 // APK is signed with an APK signature scheme such as v2 scheme. 1141 mResult.addWarning( 1142 Issue.JAR_SIG_NO_APK_SIG_STRIP_PROTECTION, 1143 mSignatureFileEntry.getName()); 1144 } 1145 return; 1146 } 1147 1148 if (supportedApkSigSchemeNames.isEmpty()) { 1149 return; 1150 } 1151 1152 Set<Integer> supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet(); 1153 Set<Integer> supportedExpectedApkSigSchemeIds = new HashSet<>(1); 1154 StringTokenizer tokenizer = new StringTokenizer(signedWithApkSchemes, ","); 1155 while (tokenizer.hasMoreTokens()) { 1156 String idText = tokenizer.nextToken().trim(); 1157 if (idText.isEmpty()) { 1158 continue; 1159 } 1160 int id; 1161 try { 1162 id = Integer.parseInt(idText); 1163 } catch (Exception ignored) { 1164 continue; 1165 } 1166 // This APK was supposed to be signed with the APK signature scheme having 1167 // this ID. 1168 if (supportedApkSigSchemeIds.contains(id)) { 1169 supportedExpectedApkSigSchemeIds.add(id); 1170 } else { 1171 mResult.addWarning( 1172 Issue.JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID, 1173 mSignatureFileEntry.getName(), 1174 id); 1175 } 1176 } 1177 1178 for (int id : supportedExpectedApkSigSchemeIds) { 1179 if (!foundApkSigSchemeIds.contains(id)) { 1180 String apkSigSchemeName = supportedApkSigSchemeNames.get(id); 1181 mResult.addError( 1182 Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED, 1183 mSignatureFileEntry.getName(), 1184 id, 1185 apkSigSchemeName); 1186 } 1187 } 1188 } 1189 } 1190 1191 private static Collection<NamedDigest> getDigestsToVerify( 1192 ManifestParser.Section section, 1193 String digestAttrSuffix, 1194 int minSdkVersion, 1195 int maxSdkVersion) { 1196 Decoder base64Decoder = Base64.getDecoder(); 1197 List<NamedDigest> result = new ArrayList<>(1); 1198 if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) { 1199 // Prior to JB MR2, Android platform's logic for picking a digest algorithm to verify is 1200 // to rely on the ancient Digest-Algorithms attribute which contains 1201 // whitespace-separated list of digest algorithms (defaulting to SHA-1) to try. The 1202 // first digest attribute (with supported digest algorithm) found using the list is 1203 // used. 1204 String algs = section.getAttributeValue("Digest-Algorithms"); 1205 if (algs == null) { 1206 algs = "SHA SHA1"; 1207 } 1208 StringTokenizer tokens = new StringTokenizer(algs); 1209 while (tokens.hasMoreTokens()) { 1210 String alg = tokens.nextToken(); 1211 String attrName = alg + digestAttrSuffix; 1212 String digestBase64 = section.getAttributeValue(attrName); 1213 if (digestBase64 == null) { 1214 // Attribute not found 1215 continue; 1216 } 1217 alg = getCanonicalJcaMessageDigestAlgorithm(alg); 1218 if ((alg == null) 1219 || (getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile(alg) 1220 > minSdkVersion)) { 1221 // Unsupported digest algorithm 1222 continue; 1223 } 1224 // Supported digest algorithm 1225 result.add(new NamedDigest(alg, base64Decoder.decode(digestBase64))); 1226 break; 1227 } 1228 // No supported digests found -- this will fail to verify on pre-JB MR2 Androids. 1229 if (result.isEmpty()) { 1230 return result; 1231 } 1232 } 1233 1234 if (maxSdkVersion >= AndroidSdkVersion.JELLY_BEAN_MR2) { 1235 // On JB MR2 and newer, Android platform picks the strongest algorithm out of: 1236 // SHA-512, SHA-384, SHA-256, SHA-1. 1237 for (String alg : JB_MR2_AND_NEWER_DIGEST_ALGS) { 1238 String attrName = getJarDigestAttributeName(alg, digestAttrSuffix); 1239 String digestBase64 = section.getAttributeValue(attrName); 1240 if (digestBase64 == null) { 1241 // Attribute not found 1242 continue; 1243 } 1244 byte[] digest = base64Decoder.decode(digestBase64); 1245 byte[] digestInResult = getDigest(result, alg); 1246 if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) { 1247 result.add(new NamedDigest(alg, digest)); 1248 } 1249 break; 1250 } 1251 } 1252 1253 return result; 1254 } 1255 1256 private static final String[] JB_MR2_AND_NEWER_DIGEST_ALGS = { 1257 "SHA-512", 1258 "SHA-384", 1259 "SHA-256", 1260 "SHA-1", 1261 }; 1262 1263 private static String getCanonicalJcaMessageDigestAlgorithm(String algorithm) { 1264 return UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.get(algorithm.toUpperCase(Locale.US)); 1265 } 1266 1267 public static int getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile( 1268 String jcaAlgorithmName) { 1269 Integer result = 1270 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.get( 1271 jcaAlgorithmName.toUpperCase(Locale.US)); 1272 return (result != null) ? result : Integer.MAX_VALUE; 1273 } 1274 1275 private static String getJarDigestAttributeName( 1276 String jcaDigestAlgorithm, String attrNameSuffix) { 1277 if ("SHA-1".equalsIgnoreCase(jcaDigestAlgorithm)) { 1278 return "SHA1" + attrNameSuffix; 1279 } else { 1280 return jcaDigestAlgorithm + attrNameSuffix; 1281 } 1282 } 1283 1284 private static final Map<String, String> UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL; 1285 static { 1286 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL = new HashMap<>(8); 1287 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("MD5", "MD5"); 1288 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA", "SHA-1"); 1289 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA1", "SHA-1"); 1290 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-1", "SHA-1"); 1291 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-256", "SHA-256"); 1292 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-384", "SHA-384"); 1293 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-512", "SHA-512"); 1294 } 1295 1296 private static final Map<String, Integer> 1297 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST; 1298 static { 1299 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST = new HashMap<>(5); 1300 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("MD5", 0); 1301 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-1", 0); 1302 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-256", 0); 1303 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put( 1304 "SHA-384", AndroidSdkVersion.GINGERBREAD); 1305 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put( 1306 "SHA-512", AndroidSdkVersion.GINGERBREAD); 1307 } 1308 1309 private static byte[] getDigest(Collection<NamedDigest> digests, String jcaDigestAlgorithm) { 1310 for (NamedDigest digest : digests) { 1311 if (digest.jcaDigestAlgorithm.equalsIgnoreCase(jcaDigestAlgorithm)) { 1312 return digest.digest; 1313 } 1314 } 1315 return null; 1316 } 1317 1318 public static List<CentralDirectoryRecord> parseZipCentralDirectory( 1319 DataSource apk, 1320 ApkUtils.ZipSections apkSections) 1321 throws IOException, ApkFormatException { 1322 // Read the ZIP Central Directory 1323 long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes(); 1324 if (cdSizeBytes > Integer.MAX_VALUE) { 1325 throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes); 1326 } 1327 long cdOffset = apkSections.getZipCentralDirectoryOffset(); 1328 ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes); 1329 cd.order(ByteOrder.LITTLE_ENDIAN); 1330 1331 // Parse the ZIP Central Directory 1332 int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount(); 1333 List<CentralDirectoryRecord> cdRecords = new ArrayList<>(expectedCdRecordCount); 1334 for (int i = 0; i < expectedCdRecordCount; i++) { 1335 CentralDirectoryRecord cdRecord; 1336 int offsetInsideCd = cd.position(); 1337 try { 1338 cdRecord = CentralDirectoryRecord.getRecord(cd); 1339 } catch (ZipFormatException e) { 1340 throw new ApkFormatException( 1341 "Malformed ZIP Central Directory record #" + (i + 1) 1342 + " at file offset " + (cdOffset + offsetInsideCd), 1343 e); 1344 } 1345 String entryName = cdRecord.getName(); 1346 if (entryName.endsWith("/")) { 1347 // Ignore directory entries 1348 continue; 1349 } 1350 cdRecords.add(cdRecord); 1351 } 1352 // There may be more data in Central Directory, but we don't warn or throw because Android 1353 // ignores unused CD data. 1354 1355 return cdRecords; 1356 } 1357 1358 /** 1359 * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's 1360 * manifest for the APK to verify on Android. 1361 */ 1362 private static boolean isJarEntryDigestNeededInManifest(String entryName) { 1363 // NOTE: This logic is different from what's required by the JAR signing scheme. This is 1364 // because Android's APK verification logic differs from that spec. In particular, JAR 1365 // signing spec includes into JAR manifest all files in subdirectories of META-INF and 1366 // any files inside META-INF not related to signatures. 1367 if (entryName.startsWith("META-INF/")) { 1368 return false; 1369 } 1370 return !entryName.endsWith("/"); 1371 } 1372 1373 private static Set<Signer> verifyJarEntriesAgainstManifestAndSigners( 1374 DataSource apk, 1375 long cdOffsetInApk, 1376 Collection<CentralDirectoryRecord> cdRecords, 1377 Map<String, ManifestParser.Section> entryNameToManifestSection, 1378 List<Signer> signers, 1379 int minSdkVersion, 1380 int maxSdkVersion, 1381 Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { 1382 // Iterate over APK contents as sequentially as possible to improve performance. 1383 List<CentralDirectoryRecord> cdRecordsSortedByLocalFileHeaderOffset = 1384 new ArrayList<>(cdRecords); 1385 Collections.sort( 1386 cdRecordsSortedByLocalFileHeaderOffset, 1387 CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR); 1388 Set<String> manifestEntryNamesMissingFromApk = 1389 new HashSet<>(entryNameToManifestSection.keySet()); 1390 List<Signer> firstSignedEntrySigners = null; 1391 String firstSignedEntryName = null; 1392 for (CentralDirectoryRecord cdRecord : cdRecordsSortedByLocalFileHeaderOffset) { 1393 String entryName = cdRecord.getName(); 1394 manifestEntryNamesMissingFromApk.remove(entryName); 1395 if (!isJarEntryDigestNeededInManifest(entryName)) { 1396 continue; 1397 } 1398 1399 ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName); 1400 if (manifestSection == null) { 1401 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName); 1402 continue; 1403 } 1404 1405 List<Signer> entrySigners = new ArrayList<>(signers.size()); 1406 for (Signer signer : signers) { 1407 if (signer.getSigFileEntryNames().contains(entryName)) { 1408 entrySigners.add(signer); 1409 } 1410 } 1411 if (entrySigners.isEmpty()) { 1412 result.addError(Issue.JAR_SIG_ZIP_ENTRY_NOT_SIGNED, entryName); 1413 continue; 1414 } 1415 if (firstSignedEntrySigners == null) { 1416 firstSignedEntrySigners = entrySigners; 1417 firstSignedEntryName = entryName; 1418 } else if (!entrySigners.equals(firstSignedEntrySigners)) { 1419 result.addError( 1420 Issue.JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH, 1421 firstSignedEntryName, 1422 getSignerNames(firstSignedEntrySigners), 1423 entryName, 1424 getSignerNames(entrySigners)); 1425 continue; 1426 } 1427 1428 List<NamedDigest> expectedDigests = 1429 new ArrayList<>( 1430 getDigestsToVerify( 1431 manifestSection, "-Digest", minSdkVersion, maxSdkVersion)); 1432 if (expectedDigests.isEmpty()) { 1433 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName); 1434 continue; 1435 } 1436 1437 MessageDigest[] mds = new MessageDigest[expectedDigests.size()]; 1438 for (int i = 0; i < expectedDigests.size(); i++) { 1439 mds[i] = getMessageDigest(expectedDigests.get(i).jcaDigestAlgorithm); 1440 } 1441 1442 try { 1443 LocalFileRecord.outputUncompressedData( 1444 apk, 1445 cdRecord, 1446 cdOffsetInApk, 1447 new MessageDigestSink(mds)); 1448 } catch (ZipFormatException e) { 1449 throw new ApkFormatException("Malformed ZIP entry: " + entryName, e); 1450 } catch (IOException e) { 1451 throw new IOException("Failed to read entry: " + entryName, e); 1452 } 1453 1454 for (int i = 0; i < expectedDigests.size(); i++) { 1455 NamedDigest expectedDigest = expectedDigests.get(i); 1456 byte[] actualDigest = mds[i].digest(); 1457 if (!Arrays.equals(expectedDigest.digest, actualDigest)) { 1458 result.addError( 1459 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY, 1460 entryName, 1461 expectedDigest.jcaDigestAlgorithm, 1462 V1SchemeSigner.MANIFEST_ENTRY_NAME, 1463 Base64.getEncoder().encodeToString(actualDigest), 1464 Base64.getEncoder().encodeToString(expectedDigest.digest)); 1465 } 1466 } 1467 } 1468 1469 if (firstSignedEntrySigners == null) { 1470 result.addError(Issue.JAR_SIG_NO_SIGNED_ZIP_ENTRIES); 1471 return Collections.emptySet(); 1472 } else { 1473 return new HashSet<>(firstSignedEntrySigners); 1474 } 1475 } 1476 1477 private static List<String> getSignerNames(List<Signer> signers) { 1478 if (signers.isEmpty()) { 1479 return Collections.emptyList(); 1480 } 1481 List<String> result = new ArrayList<>(signers.size()); 1482 for (Signer signer : signers) { 1483 result.add(signer.getName()); 1484 } 1485 return result; 1486 } 1487 1488 private static MessageDigest getMessageDigest(String algorithm) 1489 throws NoSuchAlgorithmException { 1490 return MessageDigest.getInstance(algorithm); 1491 } 1492 1493 private static byte[] digest(String algorithm, byte[] data, int offset, int length) 1494 throws NoSuchAlgorithmException { 1495 MessageDigest md = getMessageDigest(algorithm); 1496 md.update(data, offset, length); 1497 return md.digest(); 1498 } 1499 1500 private static byte[] digest(String algorithm, byte[] data) throws NoSuchAlgorithmException { 1501 return getMessageDigest(algorithm).digest(data); 1502 } 1503 1504 private static class NamedDigest { 1505 private final String jcaDigestAlgorithm; 1506 private final byte[] digest; 1507 1508 private NamedDigest(String jcaDigestAlgorithm, byte[] digest) { 1509 this.jcaDigestAlgorithm = jcaDigestAlgorithm; 1510 this.digest = digest; 1511 } 1512 } 1513 1514 public static class Result { 1515 1516 /** Whether the APK's JAR signature verifies. */ 1517 public boolean verified; 1518 1519 /** List of APK's signers. These signers are used by Android. */ 1520 public final List<SignerInfo> signers = new ArrayList<>(); 1521 1522 /** 1523 * Signers encountered in the APK but not included in the set of the APK's signers. These 1524 * signers are ignored by Android. 1525 */ 1526 public final List<SignerInfo> ignoredSigners = new ArrayList<>(); 1527 1528 private final List<IssueWithParams> mWarnings = new ArrayList<>(); 1529 private final List<IssueWithParams> mErrors = new ArrayList<>(); 1530 1531 private boolean containsErrors() { 1532 if (!mErrors.isEmpty()) { 1533 return true; 1534 } 1535 for (SignerInfo signer : signers) { 1536 if (signer.containsErrors()) { 1537 return true; 1538 } 1539 } 1540 return false; 1541 } 1542 1543 private void addError(Issue msg, Object... parameters) { 1544 mErrors.add(new IssueWithParams(msg, parameters)); 1545 } 1546 1547 private void addWarning(Issue msg, Object... parameters) { 1548 mWarnings.add(new IssueWithParams(msg, parameters)); 1549 } 1550 1551 public List<IssueWithParams> getErrors() { 1552 return mErrors; 1553 } 1554 1555 public List<IssueWithParams> getWarnings() { 1556 return mWarnings; 1557 } 1558 1559 public static class SignerInfo { 1560 public final String name; 1561 public final String signatureFileName; 1562 public final String signatureBlockFileName; 1563 public final List<X509Certificate> certChain = new ArrayList<>(); 1564 1565 private final List<IssueWithParams> mWarnings = new ArrayList<>(); 1566 private final List<IssueWithParams> mErrors = new ArrayList<>(); 1567 1568 private SignerInfo( 1569 String name, String signatureBlockFileName, String signatureFileName) { 1570 this.name = name; 1571 this.signatureBlockFileName = signatureBlockFileName; 1572 this.signatureFileName = signatureFileName; 1573 } 1574 1575 private boolean containsErrors() { 1576 return !mErrors.isEmpty(); 1577 } 1578 1579 private void addError(Issue msg, Object... parameters) { 1580 mErrors.add(new IssueWithParams(msg, parameters)); 1581 } 1582 1583 private void addWarning(Issue msg, Object... parameters) { 1584 mWarnings.add(new IssueWithParams(msg, parameters)); 1585 } 1586 1587 public List<IssueWithParams> getErrors() { 1588 return mErrors; 1589 } 1590 1591 public List<IssueWithParams> getWarnings() { 1592 return mWarnings; 1593 } 1594 } 1595 } 1596 } 1597