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.asn1.Asn1BerParser; 24 import com.android.apksig.internal.asn1.Asn1Class; 25 import com.android.apksig.internal.asn1.Asn1DecodingException; 26 import com.android.apksig.internal.asn1.Asn1Field; 27 import com.android.apksig.internal.asn1.Asn1OpaqueObject; 28 import com.android.apksig.internal.asn1.Asn1Type; 29 import com.android.apksig.internal.jar.ManifestParser; 30 import com.android.apksig.internal.pkcs7.Attribute; 31 import com.android.apksig.internal.pkcs7.ContentInfo; 32 import com.android.apksig.internal.pkcs7.IssuerAndSerialNumber; 33 import com.android.apksig.internal.pkcs7.Pkcs7Constants; 34 import com.android.apksig.internal.pkcs7.Pkcs7DecodingException; 35 import com.android.apksig.internal.pkcs7.SignedData; 36 import com.android.apksig.internal.pkcs7.SignerIdentifier; 37 import com.android.apksig.internal.pkcs7.SignerInfo; 38 import com.android.apksig.internal.util.AndroidSdkVersion; 39 import com.android.apksig.internal.util.ByteBufferUtils; 40 import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; 41 import com.android.apksig.internal.util.InclusiveIntRange; 42 import com.android.apksig.internal.zip.CentralDirectoryRecord; 43 import com.android.apksig.internal.zip.LocalFileRecord; 44 import com.android.apksig.util.DataSinks; 45 import com.android.apksig.util.DataSource; 46 import com.android.apksig.zip.ZipFormatException; 47 import java.io.ByteArrayInputStream; 48 import java.io.IOException; 49 import java.math.BigInteger; 50 import java.nio.ByteBuffer; 51 import java.nio.ByteOrder; 52 import java.security.InvalidKeyException; 53 import java.security.MessageDigest; 54 import java.security.NoSuchAlgorithmException; 55 import java.security.Principal; 56 import java.security.Signature; 57 import java.security.SignatureException; 58 import java.security.cert.CertificateException; 59 import java.security.cert.CertificateFactory; 60 import java.security.cert.X509Certificate; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.Base64; 64 import java.util.Base64.Decoder; 65 import java.util.Collection; 66 import java.util.Collections; 67 import java.util.HashMap; 68 import java.util.HashSet; 69 import java.util.List; 70 import java.util.Locale; 71 import java.util.Map; 72 import java.util.Set; 73 import java.util.StringTokenizer; 74 import java.util.jar.Attributes; 75 import javax.security.auth.x500.X500Principal; 76 77 /** 78 * APK verifier which uses JAR signing (aka v1 signing scheme). 79 * 80 * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a> 81 */ 82 public abstract class V1SchemeVerifier { 83 84 private static final String MANIFEST_ENTRY_NAME = V1SchemeSigner.MANIFEST_ENTRY_NAME; 85 V1SchemeVerifier()86 private V1SchemeVerifier() {} 87 88 /** 89 * Verifies the provided APK's JAR signatures and returns the result of verification. APK is 90 * considered verified only if {@link Result#verified} is {@code true}. If verification fails, 91 * the result will contain errors -- see {@link Result#getErrors()}. 92 * 93 * <p>Verification succeeds iff the APK's JAR signatures are expected to verify on all Android 94 * platform versions in the {@code [minSdkVersion, maxSdkVersion]} range. If the APK's signature 95 * is expected to not verify on any of the specified platform versions, this method returns a 96 * result with one or more errors and whose {@code Result.verified == false}, or this method 97 * throws an exception. 98 * 99 * @throws ApkFormatException if the APK is malformed 100 * @throws IOException if an I/O error occurs when reading the APK 101 * @throws NoSuchAlgorithmException if the APK's JAR signatures cannot be verified because a 102 * required cryptographic algorithm implementation is missing 103 */ verify( DataSource apk, ApkUtils.ZipSections apkSections, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion)104 public static Result verify( 105 DataSource apk, 106 ApkUtils.ZipSections apkSections, 107 Map<Integer, String> supportedApkSigSchemeNames, 108 Set<Integer> foundApkSigSchemeIds, 109 int minSdkVersion, 110 int maxSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException { 111 if (minSdkVersion > maxSdkVersion) { 112 throw new IllegalArgumentException( 113 "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion 114 + ")"); 115 } 116 117 Result result = new Result(); 118 119 // Parse the ZIP Central Directory and check that there are no entries with duplicate names. 120 List<CentralDirectoryRecord> cdRecords = parseZipCentralDirectory(apk, apkSections); 121 Set<String> cdEntryNames = checkForDuplicateEntries(cdRecords, result); 122 if (result.containsErrors()) { 123 return result; 124 } 125 126 // Verify JAR signature(s). 127 Signers.verify( 128 apk, 129 apkSections.getZipCentralDirectoryOffset(), 130 cdRecords, 131 cdEntryNames, 132 supportedApkSigSchemeNames, 133 foundApkSigSchemeIds, 134 minSdkVersion, 135 maxSdkVersion, 136 result); 137 138 return result; 139 } 140 141 /** 142 * Returns the set of entry names and reports any duplicate entry names in the {@code result} 143 * as errors. 144 */ checkForDuplicateEntries( List<CentralDirectoryRecord> cdRecords, Result result)145 private static Set<String> checkForDuplicateEntries( 146 List<CentralDirectoryRecord> cdRecords, Result result) { 147 Set<String> cdEntryNames = new HashSet<>(cdRecords.size()); 148 Set<String> duplicateCdEntryNames = null; 149 for (CentralDirectoryRecord cdRecord : cdRecords) { 150 String entryName = cdRecord.getName(); 151 if (!cdEntryNames.add(entryName)) { 152 // This is an error. Report this once per duplicate name. 153 if (duplicateCdEntryNames == null) { 154 duplicateCdEntryNames = new HashSet<>(); 155 } 156 if (duplicateCdEntryNames.add(entryName)) { 157 result.addError(Issue.JAR_SIG_DUPLICATE_ZIP_ENTRY, entryName); 158 } 159 } 160 } 161 return cdEntryNames; 162 } 163 164 /** 165 * All JAR signers of an APK. 166 */ 167 private static class Signers { 168 169 /** 170 * Verifies JAR signatures of the provided APK and populates the provided result container 171 * with errors, warnings, and information about signers. The APK is considered verified if 172 * the {@link Result#verified} is {@code true}. 173 */ verify( DataSource apk, long cdStartOffset, List<CentralDirectoryRecord> cdRecords, Set<String> cdEntryNames, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion, Result result)174 private static void verify( 175 DataSource apk, 176 long cdStartOffset, 177 List<CentralDirectoryRecord> cdRecords, 178 Set<String> cdEntryNames, 179 Map<Integer, String> supportedApkSigSchemeNames, 180 Set<Integer> foundApkSigSchemeIds, 181 int minSdkVersion, 182 int maxSdkVersion, 183 Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { 184 185 // Find JAR manifest and signature block files. 186 CentralDirectoryRecord manifestEntry = null; 187 Map<String, CentralDirectoryRecord> sigFileEntries = new HashMap<>(1); 188 List<CentralDirectoryRecord> sigBlockEntries = new ArrayList<>(1); 189 for (CentralDirectoryRecord cdRecord : cdRecords) { 190 String entryName = cdRecord.getName(); 191 if (!entryName.startsWith("META-INF/")) { 192 continue; 193 } 194 if ((manifestEntry == null) && (MANIFEST_ENTRY_NAME.equals(entryName))) { 195 manifestEntry = cdRecord; 196 continue; 197 } 198 if (entryName.endsWith(".SF")) { 199 sigFileEntries.put(entryName, cdRecord); 200 continue; 201 } 202 if ((entryName.endsWith(".RSA")) 203 || (entryName.endsWith(".DSA")) 204 || (entryName.endsWith(".EC"))) { 205 sigBlockEntries.add(cdRecord); 206 continue; 207 } 208 } 209 if (manifestEntry == null) { 210 result.addError(Issue.JAR_SIG_NO_MANIFEST); 211 return; 212 } 213 214 // Parse the JAR manifest and check that all JAR entries it references exist in the APK. 215 byte[] manifestBytes; 216 try { 217 manifestBytes = 218 LocalFileRecord.getUncompressedData(apk, manifestEntry, cdStartOffset); 219 } catch (ZipFormatException e) { 220 throw new ApkFormatException("Malformed ZIP entry: " + manifestEntry.getName(), e); 221 } 222 Map<String, ManifestParser.Section> entryNameToManifestSection = null; 223 ManifestParser manifest = new ManifestParser(manifestBytes); 224 ManifestParser.Section manifestMainSection = manifest.readSection(); 225 List<ManifestParser.Section> manifestIndividualSections = manifest.readAllSections(); 226 entryNameToManifestSection = new HashMap<>(manifestIndividualSections.size()); 227 int manifestSectionNumber = 0; 228 for (ManifestParser.Section manifestSection : manifestIndividualSections) { 229 manifestSectionNumber++; 230 String entryName = manifestSection.getName(); 231 if (entryName == null) { 232 result.addError(Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION, manifestSectionNumber); 233 continue; 234 } 235 if (entryNameToManifestSection.put(entryName, manifestSection) != null) { 236 result.addError(Issue.JAR_SIG_DUPLICATE_MANIFEST_SECTION, entryName); 237 continue; 238 } 239 if (!cdEntryNames.contains(entryName)) { 240 result.addError( 241 Issue.JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST, entryName); 242 continue; 243 } 244 } 245 if (result.containsErrors()) { 246 return; 247 } 248 // STATE OF AFFAIRS: 249 // * All JAR entries listed in JAR manifest are present in the APK. 250 251 // Identify signers 252 List<Signer> signers = new ArrayList<>(sigBlockEntries.size()); 253 for (CentralDirectoryRecord sigBlockEntry : sigBlockEntries) { 254 String sigBlockEntryName = sigBlockEntry.getName(); 255 int extensionDelimiterIndex = sigBlockEntryName.lastIndexOf('.'); 256 if (extensionDelimiterIndex == -1) { 257 throw new RuntimeException( 258 "Signature block file name does not contain extension: " 259 + sigBlockEntryName); 260 } 261 String sigFileEntryName = 262 sigBlockEntryName.substring(0, extensionDelimiterIndex) + ".SF"; 263 CentralDirectoryRecord sigFileEntry = sigFileEntries.get(sigFileEntryName); 264 if (sigFileEntry == null) { 265 result.addWarning( 266 Issue.JAR_SIG_MISSING_FILE, sigBlockEntryName, sigFileEntryName); 267 continue; 268 } 269 String signerName = sigBlockEntryName.substring("META-INF/".length()); 270 Result.SignerInfo signerInfo = 271 new Result.SignerInfo( 272 signerName, sigBlockEntryName, sigFileEntry.getName()); 273 Signer signer = new Signer(signerName, sigBlockEntry, sigFileEntry, signerInfo); 274 signers.add(signer); 275 } 276 if (signers.isEmpty()) { 277 result.addError(Issue.JAR_SIG_NO_SIGNATURES); 278 return; 279 } 280 281 // Verify each signer's signature block file .(RSA|DSA|EC) against the corresponding 282 // signature file .SF. Any error encountered for any signer terminates verification, to 283 // mimic Android's behavior. 284 for (Signer signer : signers) { 285 signer.verifySigBlockAgainstSigFile( 286 apk, cdStartOffset, minSdkVersion, maxSdkVersion); 287 if (signer.getResult().containsErrors()) { 288 result.signers.add(signer.getResult()); 289 } 290 } 291 if (result.containsErrors()) { 292 return; 293 } 294 // STATE OF AFFAIRS: 295 // * All JAR entries listed in JAR manifest are present in the APK. 296 // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). 297 298 // Verify each signer's signature file (.SF) against the JAR manifest. 299 List<Signer> remainingSigners = new ArrayList<>(signers.size()); 300 for (Signer signer : signers) { 301 signer.verifySigFileAgainstManifest( 302 manifestBytes, 303 manifestMainSection, 304 entryNameToManifestSection, 305 supportedApkSigSchemeNames, 306 foundApkSigSchemeIds, 307 minSdkVersion, 308 maxSdkVersion); 309 if (signer.isIgnored()) { 310 result.ignoredSigners.add(signer.getResult()); 311 } else { 312 if (signer.getResult().containsErrors()) { 313 result.signers.add(signer.getResult()); 314 } else { 315 remainingSigners.add(signer); 316 } 317 } 318 } 319 if (result.containsErrors()) { 320 return; 321 } 322 signers = remainingSigners; 323 if (signers.isEmpty()) { 324 result.addError(Issue.JAR_SIG_NO_SIGNATURES); 325 return; 326 } 327 // STATE OF AFFAIRS: 328 // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). 329 // * Contents of all JAR manifest sections listed in .SF files verify against .SF files. 330 // * All JAR entries listed in JAR manifest are present in the APK. 331 332 // Verify data of JAR entries against JAR manifest and .SF files. On Android, an APK's 333 // JAR entry is considered signed by signers associated with an .SF file iff the entry 334 // is mentioned in the .SF file and the entry's digest(s) mentioned in the JAR manifest 335 // match theentry's uncompressed data. Android requires that all such JAR entries are 336 // signed by the same set of signers. This set may be smaller than the set of signers 337 // we've identified so far. 338 Set<Signer> apkSigners = 339 verifyJarEntriesAgainstManifestAndSigners( 340 apk, 341 cdStartOffset, 342 cdRecords, 343 entryNameToManifestSection, 344 signers, 345 minSdkVersion, 346 maxSdkVersion, 347 result); 348 if (result.containsErrors()) { 349 return; 350 } 351 // STATE OF AFFAIRS: 352 // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). 353 // * Contents of all JAR manifest sections listed in .SF files verify against .SF files. 354 // * All JAR entries listed in JAR manifest are present in the APK. 355 // * All JAR entries present in the APK and supposed to be covered by JAR signature 356 // (i.e., reside outside of META-INF/) are covered by signatures from the same set 357 // of signers. 358 359 // Report any JAR entries which aren't covered by signature. 360 Set<String> signatureEntryNames = new HashSet<>(1 + result.signers.size() * 2); 361 signatureEntryNames.add(manifestEntry.getName()); 362 for (Signer signer : apkSigners) { 363 signatureEntryNames.add(signer.getSignatureBlockEntryName()); 364 signatureEntryNames.add(signer.getSignatureFileEntryName()); 365 } 366 for (CentralDirectoryRecord cdRecord : cdRecords) { 367 String entryName = cdRecord.getName(); 368 if ((entryName.startsWith("META-INF/")) 369 && (!entryName.endsWith("/")) 370 && (!signatureEntryNames.contains(entryName))) { 371 result.addWarning(Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY, entryName); 372 } 373 } 374 375 // Reflect the sets of used signers and ignored signers in the result. 376 for (Signer signer : signers) { 377 if (apkSigners.contains(signer)) { 378 result.signers.add(signer.getResult()); 379 } else { 380 result.ignoredSigners.add(signer.getResult()); 381 } 382 } 383 384 result.verified = true; 385 } 386 } 387 388 static class Signer { 389 private final String mName; 390 private final Result.SignerInfo mResult; 391 private final CentralDirectoryRecord mSignatureFileEntry; 392 private final CentralDirectoryRecord mSignatureBlockEntry; 393 private boolean mIgnored; 394 395 private byte[] mSigFileBytes; 396 private Set<String> mSigFileEntryNames; 397 Signer( String name, CentralDirectoryRecord sigBlockEntry, CentralDirectoryRecord sigFileEntry, Result.SignerInfo result)398 private Signer( 399 String name, 400 CentralDirectoryRecord sigBlockEntry, 401 CentralDirectoryRecord sigFileEntry, 402 Result.SignerInfo result) { 403 mName = name; 404 mResult = result; 405 mSignatureBlockEntry = sigBlockEntry; 406 mSignatureFileEntry = sigFileEntry; 407 } 408 getName()409 public String getName() { 410 return mName; 411 } 412 getSignatureFileEntryName()413 public String getSignatureFileEntryName() { 414 return mSignatureFileEntry.getName(); 415 } 416 getSignatureBlockEntryName()417 public String getSignatureBlockEntryName() { 418 return mSignatureBlockEntry.getName(); 419 } 420 setIgnored()421 void setIgnored() { 422 mIgnored = true; 423 } 424 isIgnored()425 public boolean isIgnored() { 426 return mIgnored; 427 } 428 getSigFileEntryNames()429 public Set<String> getSigFileEntryNames() { 430 return mSigFileEntryNames; 431 } 432 getResult()433 public Result.SignerInfo getResult() { 434 return mResult; 435 } 436 verifySigBlockAgainstSigFile( DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion)437 public void verifySigBlockAgainstSigFile( 438 DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion) 439 throws IOException, ApkFormatException, NoSuchAlgorithmException { 440 // Obtain the signature block from the APK 441 byte[] sigBlockBytes; 442 try { 443 sigBlockBytes = 444 LocalFileRecord.getUncompressedData( 445 apk, mSignatureBlockEntry, cdStartOffset); 446 } catch (ZipFormatException e) { 447 throw new ApkFormatException( 448 "Malformed ZIP entry: " + mSignatureBlockEntry.getName(), e); 449 } 450 // Obtain the signature file from the APK 451 try { 452 mSigFileBytes = 453 LocalFileRecord.getUncompressedData( 454 apk, mSignatureFileEntry, cdStartOffset); 455 } catch (ZipFormatException e) { 456 throw new ApkFormatException( 457 "Malformed ZIP entry: " + mSignatureFileEntry.getName(), e); 458 } 459 460 // Extract PKCS #7 SignedData from the signature block 461 SignedData signedData; 462 try { 463 ContentInfo contentInfo = 464 Asn1BerParser.parse(ByteBuffer.wrap(sigBlockBytes), ContentInfo.class); 465 if (!Pkcs7Constants.OID_SIGNED_DATA.equals(contentInfo.contentType)) { 466 throw new Asn1DecodingException( 467 "Unsupported ContentInfo.contentType: " + contentInfo.contentType); 468 } 469 signedData = 470 Asn1BerParser.parse(contentInfo.content.getEncoded(), SignedData.class); 471 } catch (Asn1DecodingException e) { 472 e.printStackTrace(); 473 mResult.addError( 474 Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); 475 return; 476 } 477 478 if (signedData.signerInfos.isEmpty()) { 479 mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName()); 480 return; 481 } 482 483 // Find the first SignedData.SignerInfos element which verifies against the signature 484 // file 485 SignerInfo firstVerifiedSignerInfo = null; 486 X509Certificate firstVerifiedSignerInfoSigningCertificate = null; 487 // Prior to Android N, Android attempts to verify only the first SignerInfo. From N 488 // onwards, Android attempts to verify all SignerInfos and then picks the first verified 489 // SignerInfo. 490 List<SignerInfo> unverifiedSignerInfosToTry; 491 if (minSdkVersion < AndroidSdkVersion.N) { 492 unverifiedSignerInfosToTry = 493 Collections.singletonList(signedData.signerInfos.get(0)); 494 } else { 495 unverifiedSignerInfosToTry = signedData.signerInfos; 496 } 497 List<X509Certificate> signedDataCertificates = null; 498 for (SignerInfo unverifiedSignerInfo : unverifiedSignerInfosToTry) { 499 // Parse SignedData.certificates -- they are needed to verify SignerInfo 500 if (signedDataCertificates == null) { 501 try { 502 signedDataCertificates = parseCertificates(signedData.certificates); 503 } catch (CertificateException e) { 504 mResult.addError( 505 Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); 506 return; 507 } 508 } 509 510 // Verify SignerInfo 511 X509Certificate signingCertificate; 512 try { 513 signingCertificate = 514 verifySignerInfoAgainstSigFile( 515 signedData, 516 signedDataCertificates, 517 unverifiedSignerInfo, 518 mSigFileBytes, 519 minSdkVersion, 520 maxSdkVersion); 521 if (mResult.containsErrors()) { 522 return; 523 } 524 if (signingCertificate != null) { 525 // SignerInfo verified 526 if (firstVerifiedSignerInfo == null) { 527 firstVerifiedSignerInfo = unverifiedSignerInfo; 528 firstVerifiedSignerInfoSigningCertificate = signingCertificate; 529 } 530 } 531 } catch (Pkcs7DecodingException e) { 532 mResult.addError( 533 Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); 534 return; 535 } catch (InvalidKeyException | SignatureException e) { 536 mResult.addError( 537 Issue.JAR_SIG_VERIFY_EXCEPTION, 538 mSignatureBlockEntry.getName(), 539 mSignatureFileEntry.getName(), 540 e); 541 return; 542 } 543 } 544 if (firstVerifiedSignerInfo == null) { 545 // No SignerInfo verified 546 mResult.addError( 547 Issue.JAR_SIG_DID_NOT_VERIFY, 548 mSignatureBlockEntry.getName(), 549 mSignatureFileEntry.getName()); 550 return; 551 } 552 // Verified 553 List<X509Certificate> signingCertChain = 554 getCertificateChain( 555 signedDataCertificates, firstVerifiedSignerInfoSigningCertificate); 556 mResult.certChain.clear(); 557 mResult.certChain.addAll(signingCertChain); 558 } 559 560 /** 561 * Returns the signing certificate if the provided {@link SignerInfo} verifies against the 562 * contents of the provided signature file, or {@code null} if it does not verify. 563 */ verifySignerInfoAgainstSigFile( SignedData signedData, Collection<X509Certificate> signedDataCertificates, SignerInfo signerInfo, byte[] signatureFile, int minSdkVersion, int maxSdkVersion)564 private X509Certificate verifySignerInfoAgainstSigFile( 565 SignedData signedData, 566 Collection<X509Certificate> signedDataCertificates, 567 SignerInfo signerInfo, 568 byte[] signatureFile, 569 int minSdkVersion, 570 int maxSdkVersion) 571 throws Pkcs7DecodingException, NoSuchAlgorithmException, 572 InvalidKeyException, SignatureException { 573 String digestAlgorithmOid = signerInfo.digestAlgorithm.algorithm; 574 String signatureAlgorithmOid = signerInfo.signatureAlgorithm.algorithm; 575 InclusiveIntRange desiredApiLevels = 576 InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion); 577 List<InclusiveIntRange> apiLevelsWhereDigestAndSigAlgorithmSupported = 578 getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid); 579 List<InclusiveIntRange> apiLevelsWhereDigestAlgorithmNotSupported = 580 desiredApiLevels.getValuesNotIn(apiLevelsWhereDigestAndSigAlgorithmSupported); 581 if (!apiLevelsWhereDigestAlgorithmNotSupported.isEmpty()) { 582 String digestAlgorithmUserFriendly = 583 OidToUserFriendlyNameMapper.getUserFriendlyNameForOid( 584 digestAlgorithmOid); 585 if (digestAlgorithmUserFriendly == null) { 586 digestAlgorithmUserFriendly = digestAlgorithmOid; 587 } 588 String signatureAlgorithmUserFriendly = 589 OidToUserFriendlyNameMapper.getUserFriendlyNameForOid( 590 signatureAlgorithmOid); 591 if (signatureAlgorithmUserFriendly == null) { 592 signatureAlgorithmUserFriendly = signatureAlgorithmOid; 593 } 594 StringBuilder apiLevelsUserFriendly = new StringBuilder(); 595 for (InclusiveIntRange range : apiLevelsWhereDigestAlgorithmNotSupported) { 596 if (apiLevelsUserFriendly.length() > 0) { 597 apiLevelsUserFriendly.append(", "); 598 } 599 if (range.getMin() == range.getMax()) { 600 apiLevelsUserFriendly.append(String.valueOf(range.getMin())); 601 } else if (range.getMax() == Integer.MAX_VALUE) { 602 apiLevelsUserFriendly.append(range.getMin() + "+"); 603 } else { 604 apiLevelsUserFriendly.append(range.getMin() + "-" + range.getMax()); 605 } 606 } 607 mResult.addError( 608 Issue.JAR_SIG_UNSUPPORTED_SIG_ALG, 609 mSignatureBlockEntry.getName(), 610 digestAlgorithmOid, 611 signatureAlgorithmOid, 612 apiLevelsUserFriendly.toString(), 613 digestAlgorithmUserFriendly, 614 signatureAlgorithmUserFriendly); 615 return null; 616 } 617 618 // From the bag of certs, obtain the certificate referenced by the SignerInfo, 619 // and verify the cryptographic signature in the SignerInfo against the certificate. 620 621 // Locate the signing certificate referenced by the SignerInfo 622 X509Certificate signingCertificate = 623 findCertificate(signedDataCertificates, signerInfo.sid); 624 if (signingCertificate == null) { 625 throw new SignatureException( 626 "Signing certificate referenced in SignerInfo not found in" 627 + " SignedData"); 628 } 629 630 // Check whether the signing certificate is acceptable. Android performs these 631 // checks explicitly, instead of delegating this to 632 // Signature.initVerify(Certificate). 633 if (signingCertificate.hasUnsupportedCriticalExtension()) { 634 throw new SignatureException( 635 "Signing certificate has unsupported critical extensions"); 636 } 637 boolean[] keyUsageExtension = signingCertificate.getKeyUsage(); 638 if (keyUsageExtension != null) { 639 boolean digitalSignature = 640 (keyUsageExtension.length >= 1) && (keyUsageExtension[0]); 641 boolean nonRepudiation = 642 (keyUsageExtension.length >= 2) && (keyUsageExtension[1]); 643 if ((!digitalSignature) && (!nonRepudiation)) { 644 throw new SignatureException( 645 "Signing certificate not authorized for use in digital signatures" 646 + ": keyUsage extension missing digitalSignature and" 647 + " nonRepudiation"); 648 } 649 } 650 651 // Verify the cryptographic signature in SignerInfo against the certificate's 652 // public key 653 String jcaSignatureAlgorithm = 654 getJcaSignatureAlgorithm(digestAlgorithmOid, signatureAlgorithmOid); 655 Signature s = Signature.getInstance(jcaSignatureAlgorithm); 656 s.initVerify(signingCertificate.getPublicKey()); 657 if (signerInfo.signedAttrs != null) { 658 // Signed attributes present -- verify signature against the ASN.1 DER encoded form 659 // of signed attributes. This verifies integrity of the signature file because 660 // signed attributes must contain the digest of the signature file. 661 if (minSdkVersion < AndroidSdkVersion.KITKAT) { 662 // Prior to Android KitKat, APKs with signed attributes are unsafe: 663 // * The APK's contents are not protected by the JAR signature because the 664 // digest in signed attributes is not verified. This means an attacker can 665 // arbitrarily modify the APK without invalidating its signature. 666 // * Luckily, the signature over signed attributes was verified incorrectly 667 // (over the verbatim IMPLICIT [0] form rather than over re-encoded 668 // UNIVERSAL SET form) which means that JAR signatures which would verify on 669 // pre-KitKat Android and yet do not protect the APK from modification could 670 // be generated only by broken tools or on purpose by the entity signing the 671 // APK. 672 // 673 // We thus reject such unsafe APKs, even if they verify on platforms before 674 // KitKat. 675 throw new SignatureException( 676 "APKs with Signed Attributes broken on platforms with API Level < " 677 + AndroidSdkVersion.KITKAT); 678 } 679 try { 680 List<Attribute> signedAttributes = 681 Asn1BerParser.parseImplicitSetOf( 682 signerInfo.signedAttrs.getEncoded(), Attribute.class); 683 SignedAttributes signedAttrs = new SignedAttributes(signedAttributes); 684 if (maxSdkVersion >= AndroidSdkVersion.N) { 685 // Content Type attribute is checked only on Android N and newer 686 String contentType = 687 signedAttrs.getSingleObjectIdentifierValue( 688 Pkcs7Constants.OID_CONTENT_TYPE); 689 if (contentType == null) { 690 throw new SignatureException("No Content Type in signed attributes"); 691 } 692 if (!contentType.equals(signedData.encapContentInfo.contentType)) { 693 // Did not verify: Content type signed attribute does not match 694 // SignedData.encapContentInfo.eContentType. This fails verification of 695 // this SignerInfo but should not prevent verification of other 696 // SignerInfos. Hence, no exception is thrown. 697 return null; 698 } 699 } 700 byte[] expectedSignatureFileDigest = 701 signedAttrs.getSingleOctetStringValue( 702 Pkcs7Constants.OID_MESSAGE_DIGEST); 703 if (expectedSignatureFileDigest == null) { 704 throw new SignatureException("No content digest in signed attributes"); 705 } 706 byte[] actualSignatureFileDigest = 707 MessageDigest.getInstance( 708 getJcaDigestAlgorithm(digestAlgorithmOid)) 709 .digest(signatureFile); 710 if (!Arrays.equals( 711 expectedSignatureFileDigest, actualSignatureFileDigest)) { 712 // Skip verification: signature file digest in signed attributes does not 713 // match the signature file. This fails verification of 714 // this SignerInfo but should not prevent verification of other 715 // SignerInfos. Hence, no exception is thrown. 716 return null; 717 } 718 } catch (Asn1DecodingException e) { 719 throw new SignatureException("Failed to parse signed attributes", e); 720 } 721 // PKCS #7 requires that signature is over signed attributes re-encoded as 722 // ASN.1 DER. However, Android does not re-encode except for changing the 723 // first byte of encoded form from IMPLICIT [0] to UNIVERSAL SET. We do the 724 // same for maximum compatibility. 725 ByteBuffer signedAttrsOriginalEncoding = signerInfo.signedAttrs.getEncoded(); 726 s.update((byte) 0x31); // UNIVERSAL SET 727 signedAttrsOriginalEncoding.position(1); 728 s.update(signedAttrsOriginalEncoding); 729 } else { 730 // No signed attributes present -- verify signature against the contents of the 731 // signature file 732 s.update(signatureFile); 733 } 734 byte[] sigBytes = ByteBufferUtils.toByteArray(signerInfo.signature.slice()); 735 if (!s.verify(sigBytes)) { 736 // Cryptographic signature did not verify. This fails verification of this 737 // SignerInfo but should not prevent verification of other SignerInfos. Hence, no 738 // exception is thrown. 739 return null; 740 } 741 // Cryptographic signature verified 742 return signingCertificate; 743 } 744 parseCertificates( List<Asn1OpaqueObject> encodedCertificates)745 private static List<X509Certificate> parseCertificates( 746 List<Asn1OpaqueObject> encodedCertificates) throws CertificateException { 747 if (encodedCertificates.isEmpty()) { 748 return Collections.emptyList(); 749 } 750 751 CertificateFactory certFactory; 752 try { 753 certFactory = CertificateFactory.getInstance("X.509"); 754 } catch (CertificateException e) { 755 throw new RuntimeException("Failed to create X.509 CertificateFactory", e); 756 } 757 758 List<X509Certificate> result = new ArrayList<>(encodedCertificates.size()); 759 for (int i = 0; i < encodedCertificates.size(); i++) { 760 Asn1OpaqueObject encodedCertificate = encodedCertificates.get(i); 761 X509Certificate certificate; 762 byte[] encodedForm = ByteBufferUtils.toByteArray(encodedCertificate.getEncoded()); 763 try { 764 certificate = 765 (X509Certificate) certFactory.generateCertificate( 766 new ByteArrayInputStream(encodedForm)); 767 } catch (CertificateException e) { 768 throw new CertificateException("Failed to parse certificate #" + (i + 1), e); 769 } 770 // Wrap the cert so that the result's getEncoded returns exactly the original 771 // encoded form. Without this, getEncoded may return a different form from what was 772 // stored in the signature. This is because some X509Certificate(Factory) 773 // implementations re-encode certificates and/or some implementations of 774 // X509Certificate.getEncoded() re-encode certificates. 775 certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedForm); 776 result.add(certificate); 777 } 778 return result; 779 } 780 findCertificate( Collection<X509Certificate> certs, SignerIdentifier id)781 public static X509Certificate findCertificate( 782 Collection<X509Certificate> certs, SignerIdentifier id) { 783 for (X509Certificate cert : certs) { 784 if (isMatchingCerticicate(cert, id)) { 785 return cert; 786 } 787 } 788 return null; 789 } 790 getCertificateChain( List<X509Certificate> certs, X509Certificate leaf)791 public static List<X509Certificate> getCertificateChain( 792 List<X509Certificate> certs, X509Certificate leaf) { 793 List<X509Certificate> unusedCerts = new ArrayList<>(certs); 794 List<X509Certificate> result = new ArrayList<>(1); 795 result.add(leaf); 796 unusedCerts.remove(leaf); 797 X509Certificate root = leaf; 798 while (!root.getSubjectDN().equals(root.getIssuerDN())) { 799 Principal targetDn = root.getIssuerDN(); 800 boolean issuerFound = false; 801 for (int i = 0; i < unusedCerts.size(); i++) { 802 X509Certificate unusedCert = unusedCerts.get(i); 803 if (targetDn.equals(unusedCert.getSubjectDN())) { 804 issuerFound = true; 805 unusedCerts.remove(i); 806 result.add(unusedCert); 807 root = unusedCert; 808 break; 809 } 810 } 811 if (!issuerFound) { 812 break; 813 } 814 } 815 return result; 816 } 817 isMatchingCerticicate(X509Certificate cert, SignerIdentifier id)818 private static boolean isMatchingCerticicate(X509Certificate cert, SignerIdentifier id) { 819 if (id.issuerAndSerialNumber == null) { 820 // Android doesn't support any other means of identifying the signing certificate 821 return false; 822 } 823 IssuerAndSerialNumber issuerAndSerialNumber = id.issuerAndSerialNumber; 824 byte[] encodedIssuer = 825 ByteBufferUtils.toByteArray(issuerAndSerialNumber.issuer.getEncoded()); 826 X500Principal idIssuer = new X500Principal(encodedIssuer); 827 BigInteger idSerialNumber = issuerAndSerialNumber.certificateSerialNumber; 828 return idSerialNumber.equals(cert.getSerialNumber()) 829 && idIssuer.equals(cert.getIssuerX500Principal()); 830 } 831 832 private static final String OID_DIGEST_MD5 = "1.2.840.113549.2.5"; 833 static final String OID_DIGEST_SHA1 = "1.3.14.3.2.26"; 834 private static final String OID_DIGEST_SHA224 = "2.16.840.1.101.3.4.2.4"; 835 static final String OID_DIGEST_SHA256 = "2.16.840.1.101.3.4.2.1"; 836 private static final String OID_DIGEST_SHA384 = "2.16.840.1.101.3.4.2.2"; 837 private static final String OID_DIGEST_SHA512 = "2.16.840.1.101.3.4.2.3"; 838 839 static final String OID_SIG_RSA = "1.2.840.113549.1.1.1"; 840 private static final String OID_SIG_MD5_WITH_RSA = "1.2.840.113549.1.1.4"; 841 private static final String OID_SIG_SHA1_WITH_RSA = "1.2.840.113549.1.1.5"; 842 private static final String OID_SIG_SHA224_WITH_RSA = "1.2.840.113549.1.1.14"; 843 private static final String OID_SIG_SHA256_WITH_RSA = "1.2.840.113549.1.1.11"; 844 private static final String OID_SIG_SHA384_WITH_RSA = "1.2.840.113549.1.1.12"; 845 private static final String OID_SIG_SHA512_WITH_RSA = "1.2.840.113549.1.1.13"; 846 847 static final String OID_SIG_DSA = "1.2.840.10040.4.1"; 848 private static final String OID_SIG_SHA1_WITH_DSA = "1.2.840.10040.4.3"; 849 private static final String OID_SIG_SHA224_WITH_DSA = "2.16.840.1.101.3.4.3.1"; 850 static final String OID_SIG_SHA256_WITH_DSA = "2.16.840.1.101.3.4.3.2"; 851 852 static final String OID_SIG_EC_PUBLIC_KEY = "1.2.840.10045.2.1"; 853 private static final String OID_SIG_SHA1_WITH_ECDSA = "1.2.840.10045.4.1"; 854 private static final String OID_SIG_SHA224_WITH_ECDSA = "1.2.840.10045.4.3.1"; 855 private static final String OID_SIG_SHA256_WITH_ECDSA = "1.2.840.10045.4.3.2"; 856 private static final String OID_SIG_SHA384_WITH_ECDSA = "1.2.840.10045.4.3.3"; 857 private static final String OID_SIG_SHA512_WITH_ECDSA = "1.2.840.10045.4.3.4"; 858 859 private static final Map<String, List<InclusiveIntRange>> SUPPORTED_SIG_ALG_OIDS = 860 new HashMap<>(); 861 { addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_RSA, InclusiveIntRange.from(0))862 addSupportedSigAlg( 863 OID_DIGEST_MD5, OID_SIG_RSA, 864 InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21))865 addSupportedSigAlg( 866 OID_DIGEST_MD5, OID_SIG_MD5_WITH_RSA, 867 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23))868 addSupportedSigAlg( 869 OID_DIGEST_MD5, OID_SIG_SHA1_WITH_RSA, 870 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23))871 addSupportedSigAlg( 872 OID_DIGEST_MD5, OID_SIG_SHA224_WITH_RSA, 873 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23))874 addSupportedSigAlg( 875 OID_DIGEST_MD5, OID_SIG_SHA256_WITH_RSA, 876 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23))877 addSupportedSigAlg( 878 OID_DIGEST_MD5, OID_SIG_SHA384_WITH_RSA, 879 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23))880 addSupportedSigAlg( 881 OID_DIGEST_MD5, OID_SIG_SHA512_WITH_RSA, 882 InclusiveIntRange.fromTo(21, 23)); 883 addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_RSA, InclusiveIntRange.from(0))884 addSupportedSigAlg( 885 OID_DIGEST_SHA1, OID_SIG_RSA, 886 InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23))887 addSupportedSigAlg( 888 OID_DIGEST_SHA1, OID_SIG_MD5_WITH_RSA, 889 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.from(0))890 addSupportedSigAlg( 891 OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_RSA, 892 InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23))893 addSupportedSigAlg( 894 OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_RSA, 895 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23))896 addSupportedSigAlg( 897 OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_RSA, 898 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23))899 addSupportedSigAlg( 900 OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_RSA, 901 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23))902 addSupportedSigAlg( 903 OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_RSA, 904 InclusiveIntRange.fromTo(21, 23)); 905 addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21))906 addSupportedSigAlg( 907 OID_DIGEST_SHA224, OID_SIG_RSA, 908 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23))909 addSupportedSigAlg( 910 OID_DIGEST_SHA224, OID_SIG_MD5_WITH_RSA, 911 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23))912 addSupportedSigAlg( 913 OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_RSA, 914 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21))915 addSupportedSigAlg( 916 OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_RSA, 917 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 21))918 addSupportedSigAlg( 919 OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_RSA, 920 InclusiveIntRange.fromTo(21, 21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23))921 addSupportedSigAlg( 922 OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_RSA, 923 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23))924 addSupportedSigAlg( 925 OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_RSA, 926 InclusiveIntRange.fromTo(21, 23)); 927 addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18))928 addSupportedSigAlg( 929 OID_DIGEST_SHA256, OID_SIG_RSA, 930 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23))931 addSupportedSigAlg( 932 OID_DIGEST_SHA256, OID_SIG_MD5_WITH_RSA, 933 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 21))934 addSupportedSigAlg( 935 OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_RSA, 936 InclusiveIntRange.fromTo(21, 21)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23))937 addSupportedSigAlg( 938 OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_RSA, 939 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18))940 addSupportedSigAlg( 941 OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_RSA, 942 InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23))943 addSupportedSigAlg( 944 OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_RSA, 945 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23))946 addSupportedSigAlg( 947 OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_RSA, 948 InclusiveIntRange.fromTo(21, 23)); 949 addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_RSA, InclusiveIntRange.from(18))950 addSupportedSigAlg( 951 OID_DIGEST_SHA384, OID_SIG_RSA, 952 InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23))953 addSupportedSigAlg( 954 OID_DIGEST_SHA384, OID_SIG_MD5_WITH_RSA, 955 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23))956 addSupportedSigAlg( 957 OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_RSA, 958 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23))959 addSupportedSigAlg( 960 OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_RSA, 961 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23))962 addSupportedSigAlg( 963 OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_RSA, 964 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.from(21))965 addSupportedSigAlg( 966 OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_RSA, 967 InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23))968 addSupportedSigAlg( 969 OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_RSA, 970 InclusiveIntRange.fromTo(21, 23)); 971 addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_RSA, InclusiveIntRange.from(18))972 addSupportedSigAlg( 973 OID_DIGEST_SHA512, OID_SIG_RSA, 974 InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23))975 addSupportedSigAlg( 976 OID_DIGEST_SHA512, OID_SIG_MD5_WITH_RSA, 977 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23))978 addSupportedSigAlg( 979 OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_RSA, 980 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23))981 addSupportedSigAlg( 982 OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_RSA, 983 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23))984 addSupportedSigAlg( 985 OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_RSA, 986 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 21))987 addSupportedSigAlg( 988 OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_RSA, 989 InclusiveIntRange.fromTo(21, 21)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.from(21))990 addSupportedSigAlg( 991 OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_RSA, 992 InclusiveIntRange.from(21)); 993 addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23))994 addSupportedSigAlg( 995 OID_DIGEST_MD5, OID_SIG_SHA1_WITH_DSA, 996 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23))997 addSupportedSigAlg( 998 OID_DIGEST_MD5, OID_SIG_SHA224_WITH_DSA, 999 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1000 addSupportedSigAlg( 1001 OID_DIGEST_MD5, OID_SIG_SHA256_WITH_DSA, 1002 InclusiveIntRange.fromTo(21, 23)); 1003 addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_DSA, InclusiveIntRange.from(0))1004 addSupportedSigAlg( 1005 OID_DIGEST_SHA1, OID_SIG_DSA, 1006 InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.from(9))1007 addSupportedSigAlg( 1008 OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_DSA, 1009 InclusiveIntRange.from(9)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1010 addSupportedSigAlg( 1011 OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_DSA, 1012 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1013 addSupportedSigAlg( 1014 OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_DSA, 1015 InclusiveIntRange.fromTo(21, 23)); 1016 addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_DSA, InclusiveIntRange.from(22))1017 addSupportedSigAlg( 1018 OID_DIGEST_SHA224, OID_SIG_DSA, 1019 InclusiveIntRange.from(22)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1020 addSupportedSigAlg( 1021 OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_DSA, 1022 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.from(21))1023 addSupportedSigAlg( 1024 OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_DSA, 1025 InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1026 addSupportedSigAlg( 1027 OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_DSA, 1028 InclusiveIntRange.fromTo(21, 23)); 1029 addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_DSA, InclusiveIntRange.from(22))1030 addSupportedSigAlg( 1031 OID_DIGEST_SHA256, OID_SIG_DSA, 1032 InclusiveIntRange.from(22)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1033 addSupportedSigAlg( 1034 OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_DSA, 1035 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1036 addSupportedSigAlg( 1037 OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_DSA, 1038 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.from(21))1039 addSupportedSigAlg( 1040 OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_DSA, 1041 InclusiveIntRange.from(21)); 1042 addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1043 addSupportedSigAlg( 1044 OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_DSA, 1045 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1046 addSupportedSigAlg( 1047 OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_DSA, 1048 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1049 addSupportedSigAlg( 1050 OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_DSA, 1051 InclusiveIntRange.fromTo(21, 23)); 1052 addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1053 addSupportedSigAlg( 1054 OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_DSA, 1055 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1056 addSupportedSigAlg( 1057 OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_DSA, 1058 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23))1059 addSupportedSigAlg( 1060 OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_DSA, 1061 InclusiveIntRange.fromTo(21, 23)); 1062 addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18))1063 addSupportedSigAlg( 1064 OID_DIGEST_SHA1, OID_SIG_EC_PUBLIC_KEY, 1065 InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(21))1066 addSupportedSigAlg( 1067 OID_DIGEST_SHA224, OID_SIG_EC_PUBLIC_KEY, 1068 InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18))1069 addSupportedSigAlg( 1070 OID_DIGEST_SHA256, OID_SIG_EC_PUBLIC_KEY, 1071 InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18))1072 addSupportedSigAlg( 1073 OID_DIGEST_SHA384, OID_SIG_EC_PUBLIC_KEY, 1074 InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18))1075 addSupportedSigAlg( 1076 OID_DIGEST_SHA512, OID_SIG_EC_PUBLIC_KEY, 1077 InclusiveIntRange.from(18)); 1078 addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1079 addSupportedSigAlg( 1080 OID_DIGEST_MD5, OID_SIG_SHA1_WITH_ECDSA, 1081 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1082 addSupportedSigAlg( 1083 OID_DIGEST_MD5, OID_SIG_SHA224_WITH_ECDSA, 1084 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1085 addSupportedSigAlg( 1086 OID_DIGEST_MD5, OID_SIG_SHA256_WITH_ECDSA, 1087 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1088 addSupportedSigAlg( 1089 OID_DIGEST_MD5, OID_SIG_SHA384_WITH_ECDSA, 1090 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1091 addSupportedSigAlg( 1092 OID_DIGEST_MD5, OID_SIG_SHA512_WITH_ECDSA, 1093 InclusiveIntRange.fromTo(21, 23)); 1094 addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.from(18))1095 addSupportedSigAlg( 1096 OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_ECDSA, 1097 InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1098 addSupportedSigAlg( 1099 OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_ECDSA, 1100 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1101 addSupportedSigAlg( 1102 OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_ECDSA, 1103 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1104 addSupportedSigAlg( 1105 OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_ECDSA, 1106 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1107 addSupportedSigAlg( 1108 OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_ECDSA, 1109 InclusiveIntRange.fromTo(21, 23)); 1110 addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1111 addSupportedSigAlg( 1112 OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_ECDSA, 1113 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.from(21))1114 addSupportedSigAlg( 1115 OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_ECDSA, 1116 InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1117 addSupportedSigAlg( 1118 OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_ECDSA, 1119 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1120 addSupportedSigAlg( 1121 OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_ECDSA, 1122 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1123 addSupportedSigAlg( 1124 OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_ECDSA, 1125 InclusiveIntRange.fromTo(21, 23)); 1126 addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1127 addSupportedSigAlg( 1128 OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_ECDSA, 1129 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1130 addSupportedSigAlg( 1131 OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_ECDSA, 1132 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.from(21))1133 addSupportedSigAlg( 1134 OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_ECDSA, 1135 InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1136 addSupportedSigAlg( 1137 OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_ECDSA, 1138 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1139 addSupportedSigAlg( 1140 OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_ECDSA, 1141 InclusiveIntRange.fromTo(21, 23)); 1142 addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1143 addSupportedSigAlg( 1144 OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_ECDSA, 1145 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1146 addSupportedSigAlg( 1147 OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_ECDSA, 1148 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1149 addSupportedSigAlg( 1150 OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_ECDSA, 1151 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.from(21))1152 addSupportedSigAlg( 1153 OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_ECDSA, 1154 InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1155 addSupportedSigAlg( 1156 OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_ECDSA, 1157 InclusiveIntRange.fromTo(21, 23)); 1158 addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1159 addSupportedSigAlg( 1160 OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_ECDSA, 1161 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1162 addSupportedSigAlg( 1163 OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_ECDSA, 1164 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1165 addSupportedSigAlg( 1166 OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_ECDSA, 1167 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23))1168 addSupportedSigAlg( 1169 OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_ECDSA, 1170 InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.from(21))1171 addSupportedSigAlg( 1172 OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_ECDSA, 1173 InclusiveIntRange.from(21)); 1174 } 1175 addSupportedSigAlg( String digestAlgorithmOid, String signatureAlgorithmOid, InclusiveIntRange... supportedApiLevels)1176 private static void addSupportedSigAlg( 1177 String digestAlgorithmOid, 1178 String signatureAlgorithmOid, 1179 InclusiveIntRange... supportedApiLevels) { 1180 SUPPORTED_SIG_ALG_OIDS.put( 1181 digestAlgorithmOid + "with" + signatureAlgorithmOid, 1182 Arrays.asList(supportedApiLevels)); 1183 } 1184 getSigAlgSupportedApiLevels( String digestAlgorithmOid, String signatureAlgorithmOid)1185 private List<InclusiveIntRange> getSigAlgSupportedApiLevels( 1186 String digestAlgorithmOid, 1187 String signatureAlgorithmOid) { 1188 List<InclusiveIntRange> result = 1189 SUPPORTED_SIG_ALG_OIDS.get(digestAlgorithmOid + "with" + signatureAlgorithmOid); 1190 return (result != null) ? result : Collections.emptyList(); 1191 } 1192 1193 private static class OidToUserFriendlyNameMapper { OidToUserFriendlyNameMapper()1194 private OidToUserFriendlyNameMapper() {} 1195 1196 private static final Map<String, String> OID_TO_USER_FRIENDLY_NAME = new HashMap<>(); 1197 static { OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_MD5, "MD5")1198 OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_MD5, "MD5"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA1, "SHA-1")1199 OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA1, "SHA-1"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA224, "SHA-224")1200 OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA224, "SHA-224"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA256, "SHA-256")1201 OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA256, "SHA-256"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA384, "SHA-384")1202 OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA384, "SHA-384"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA512, "SHA-512")1203 OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA512, "SHA-512"); 1204 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_RSA, "RSA")1205 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_RSA, "RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_MD5_WITH_RSA, "MD5 with RSA")1206 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_MD5_WITH_RSA, "MD5 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_RSA, "SHA-1 with RSA")1207 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_RSA, "SHA-1 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_RSA, "SHA-224 with RSA")1208 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_RSA, "SHA-224 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_RSA, "SHA-256 with RSA")1209 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_RSA, "SHA-256 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_RSA, "SHA-384 with RSA")1210 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_RSA, "SHA-384 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_RSA, "SHA-512 with RSA")1211 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_RSA, "SHA-512 with RSA"); 1212 1213 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_DSA, "DSA")1214 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_DSA, "DSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_DSA, "SHA-1 with DSA")1215 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_DSA, "SHA-1 with DSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_DSA, "SHA-224 with DSA")1216 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_DSA, "SHA-224 with DSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_DSA, "SHA-256 with DSA")1217 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_DSA, "SHA-256 with DSA"); 1218 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_EC_PUBLIC_KEY, "ECDSA")1219 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_EC_PUBLIC_KEY, "ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_ECDSA, "SHA-1 with ECDSA")1220 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_ECDSA, "SHA-1 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_ECDSA, "SHA-224 with ECDSA")1221 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_ECDSA, "SHA-224 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_ECDSA, "SHA-256 with ECDSA")1222 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_ECDSA, "SHA-256 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_ECDSA, "SHA-384 with ECDSA")1223 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_ECDSA, "SHA-384 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_ECDSA, "SHA-512 with ECDSA")1224 OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_ECDSA, "SHA-512 with ECDSA"); 1225 } 1226 getUserFriendlyNameForOid(String oid)1227 public static String getUserFriendlyNameForOid(String oid) { 1228 return OID_TO_USER_FRIENDLY_NAME.get(oid); 1229 } 1230 } 1231 1232 private static final Map<String, String> OID_TO_JCA_DIGEST_ALG = new HashMap<>(); 1233 static { OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_MD5, "MD5")1234 OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_MD5, "MD5"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA1, "SHA-1")1235 OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA1, "SHA-1"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA224, "SHA-224")1236 OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA224, "SHA-224"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA256, "SHA-256")1237 OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA256, "SHA-256"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA384, "SHA-384")1238 OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA384, "SHA-384"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA512, "SHA-512")1239 OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA512, "SHA-512"); 1240 } 1241 getJcaDigestAlgorithm(String oid)1242 private static String getJcaDigestAlgorithm(String oid) 1243 throws SignatureException { 1244 String result = OID_TO_JCA_DIGEST_ALG.get(oid); 1245 if (result == null) { 1246 throw new SignatureException("Unsupported digest algorithm: " + oid); 1247 } 1248 return result; 1249 } 1250 1251 private static final Map<String, String> OID_TO_JCA_SIGNATURE_ALG = new HashMap<>(); 1252 static { OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_MD5_WITH_RSA, "MD5withRSA")1253 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_MD5_WITH_RSA, "MD5withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_RSA, "SHA1withRSA")1254 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_RSA, "SHA1withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_RSA, "SHA224withRSA")1255 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_RSA, "SHA224withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_RSA, "SHA256withRSA")1256 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_RSA, "SHA256withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_RSA, "SHA384withRSA")1257 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_RSA, "SHA384withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_RSA, "SHA512withRSA")1258 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_RSA, "SHA512withRSA"); 1259 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_DSA, "SHA1withDSA")1260 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_DSA, "SHA1withDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_DSA, "SHA224withDSA")1261 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_DSA, "SHA224withDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_DSA, "SHA256withDSA")1262 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_DSA, "SHA256withDSA"); 1263 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_ECDSA, "SHA1withECDSA")1264 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_ECDSA, "SHA1withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_ECDSA, "SHA224withECDSA")1265 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_ECDSA, "SHA224withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_ECDSA, "SHA256withECDSA")1266 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_ECDSA, "SHA256withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_ECDSA, "SHA384withECDSA")1267 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_ECDSA, "SHA384withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_ECDSA, "SHA512withECDSA")1268 OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_ECDSA, "SHA512withECDSA"); 1269 } 1270 getJcaSignatureAlgorithm( String digestAlgorithmOid, String signatureAlgorithmOid)1271 private static String getJcaSignatureAlgorithm( 1272 String digestAlgorithmOid, 1273 String signatureAlgorithmOid) throws SignatureException { 1274 // First check whether the signature algorithm OID alone is sufficient 1275 String result = OID_TO_JCA_SIGNATURE_ALG.get(signatureAlgorithmOid); 1276 if (result != null) { 1277 return result; 1278 } 1279 1280 // Signature algorithm OID alone is insufficient. Need to combine digest algorithm OID 1281 // with signature algorithm OID. 1282 String suffix; 1283 if (OID_SIG_RSA.equals(signatureAlgorithmOid)) { 1284 suffix = "RSA"; 1285 } else if (OID_SIG_DSA.equals(signatureAlgorithmOid)) { 1286 suffix = "DSA"; 1287 } else if (OID_SIG_EC_PUBLIC_KEY.equals(signatureAlgorithmOid)) { 1288 suffix = "ECDSA"; 1289 } else { 1290 throw new SignatureException( 1291 "Unsupported JCA Signature algorithm" 1292 + " . Digest algorithm: " + digestAlgorithmOid 1293 + ", signature algorithm: " + signatureAlgorithmOid); 1294 } 1295 String jcaDigestAlg = getJcaDigestAlgorithm(digestAlgorithmOid); 1296 // Canonical name for SHA-1 with ... is SHA1with, rather than SHA1. Same for all other 1297 // SHA algorithms. 1298 if (jcaDigestAlg.startsWith("SHA-")) { 1299 jcaDigestAlg = "SHA" + jcaDigestAlg.substring("SHA-".length()); 1300 } 1301 return jcaDigestAlg + "with" + suffix; 1302 } 1303 verifySigFileAgainstManifest( byte[] manifestBytes, ManifestParser.Section manifestMainSection, Map<String, ManifestParser.Section> entryNameToManifestSection, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion)1304 public void verifySigFileAgainstManifest( 1305 byte[] manifestBytes, 1306 ManifestParser.Section manifestMainSection, 1307 Map<String, ManifestParser.Section> entryNameToManifestSection, 1308 Map<Integer, String> supportedApkSigSchemeNames, 1309 Set<Integer> foundApkSigSchemeIds, 1310 int minSdkVersion, 1311 int maxSdkVersion) throws NoSuchAlgorithmException { 1312 // Inspect the main section of the .SF file. 1313 ManifestParser sf = new ManifestParser(mSigFileBytes); 1314 ManifestParser.Section sfMainSection = sf.readSection(); 1315 if (sfMainSection.getAttributeValue(Attributes.Name.SIGNATURE_VERSION) == null) { 1316 mResult.addError( 1317 Issue.JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE, 1318 mSignatureFileEntry.getName()); 1319 setIgnored(); 1320 return; 1321 } 1322 1323 if (maxSdkVersion >= AndroidSdkVersion.N) { 1324 // Android N and newer rejects APKs whose .SF file says they were supposed to be 1325 // signed with APK Signature Scheme v2 (or newer) and yet no such signature was 1326 // found. 1327 checkForStrippedApkSignatures( 1328 sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds); 1329 if (mResult.containsErrors()) { 1330 return; 1331 } 1332 } 1333 1334 boolean createdBySigntool = false; 1335 String createdBy = sfMainSection.getAttributeValue("Created-By"); 1336 if (createdBy != null) { 1337 createdBySigntool = createdBy.indexOf("signtool") != -1; 1338 } 1339 boolean manifestDigestVerified = 1340 verifyManifestDigest( 1341 sfMainSection, 1342 createdBySigntool, 1343 manifestBytes, 1344 minSdkVersion, 1345 maxSdkVersion); 1346 if (!createdBySigntool) { 1347 verifyManifestMainSectionDigest( 1348 sfMainSection, 1349 manifestMainSection, 1350 manifestBytes, 1351 minSdkVersion, 1352 maxSdkVersion); 1353 } 1354 if (mResult.containsErrors()) { 1355 return; 1356 } 1357 1358 // Inspect per-entry sections of .SF file. Technically, if the digest of JAR manifest 1359 // verifies, per-entry sections should be ignored. However, most Android platform 1360 // implementations require that such sections exist. 1361 List<ManifestParser.Section> sfSections = sf.readAllSections(); 1362 Set<String> sfEntryNames = new HashSet<>(sfSections.size()); 1363 int sfSectionNumber = 0; 1364 for (ManifestParser.Section sfSection : sfSections) { 1365 sfSectionNumber++; 1366 String entryName = sfSection.getName(); 1367 if (entryName == null) { 1368 mResult.addError( 1369 Issue.JAR_SIG_UNNNAMED_SIG_FILE_SECTION, 1370 mSignatureFileEntry.getName(), 1371 sfSectionNumber); 1372 setIgnored(); 1373 return; 1374 } 1375 if (!sfEntryNames.add(entryName)) { 1376 mResult.addError( 1377 Issue.JAR_SIG_DUPLICATE_SIG_FILE_SECTION, 1378 mSignatureFileEntry.getName(), 1379 entryName); 1380 setIgnored(); 1381 return; 1382 } 1383 if (manifestDigestVerified) { 1384 // No need to verify this entry's corresponding JAR manifest entry because the 1385 // JAR manifest verifies in full. 1386 continue; 1387 } 1388 // Whole-file digest of JAR manifest hasn't been verified. Thus, we need to verify 1389 // the digest of the JAR manifest section corresponding to this .SF section. 1390 ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName); 1391 if (manifestSection == null) { 1392 mResult.addError( 1393 Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, 1394 entryName, 1395 mSignatureFileEntry.getName()); 1396 setIgnored(); 1397 continue; 1398 } 1399 verifyManifestIndividualSectionDigest( 1400 sfSection, 1401 createdBySigntool, 1402 manifestSection, 1403 manifestBytes, 1404 minSdkVersion, 1405 maxSdkVersion); 1406 } 1407 mSigFileEntryNames = sfEntryNames; 1408 } 1409 1410 1411 /** 1412 * Returns {@code true} if the whole-file digest of the manifest against the main section of 1413 * the .SF file. 1414 */ verifyManifestDigest( ManifestParser.Section sfMainSection, boolean createdBySigntool, byte[] manifestBytes, int minSdkVersion, int maxSdkVersion)1415 private boolean verifyManifestDigest( 1416 ManifestParser.Section sfMainSection, 1417 boolean createdBySigntool, 1418 byte[] manifestBytes, 1419 int minSdkVersion, 1420 int maxSdkVersion) throws NoSuchAlgorithmException { 1421 Collection<NamedDigest> expectedDigests = 1422 getDigestsToVerify( 1423 sfMainSection, 1424 ((createdBySigntool) ? "-Digest" : "-Digest-Manifest"), 1425 minSdkVersion, 1426 maxSdkVersion); 1427 boolean digestFound = !expectedDigests.isEmpty(); 1428 if (!digestFound) { 1429 mResult.addWarning( 1430 Issue.JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE, 1431 mSignatureFileEntry.getName()); 1432 return false; 1433 } 1434 1435 boolean verified = true; 1436 for (NamedDigest expectedDigest : expectedDigests) { 1437 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; 1438 byte[] actual = digest(jcaDigestAlgorithm, manifestBytes); 1439 byte[] expected = expectedDigest.digest; 1440 if (!Arrays.equals(expected, actual)) { 1441 mResult.addWarning( 1442 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY, 1443 V1SchemeSigner.MANIFEST_ENTRY_NAME, 1444 jcaDigestAlgorithm, 1445 mSignatureFileEntry.getName(), 1446 Base64.getEncoder().encodeToString(actual), 1447 Base64.getEncoder().encodeToString(expected)); 1448 verified = false; 1449 } 1450 } 1451 return verified; 1452 } 1453 1454 /** 1455 * Verifies the digest of the manifest's main section against the main section of the .SF 1456 * file. 1457 */ verifyManifestMainSectionDigest( ManifestParser.Section sfMainSection, ManifestParser.Section manifestMainSection, byte[] manifestBytes, int minSdkVersion, int maxSdkVersion)1458 private void verifyManifestMainSectionDigest( 1459 ManifestParser.Section sfMainSection, 1460 ManifestParser.Section manifestMainSection, 1461 byte[] manifestBytes, 1462 int minSdkVersion, 1463 int maxSdkVersion) throws NoSuchAlgorithmException { 1464 Collection<NamedDigest> expectedDigests = 1465 getDigestsToVerify( 1466 sfMainSection, 1467 "-Digest-Manifest-Main-Attributes", 1468 minSdkVersion, 1469 maxSdkVersion); 1470 if (expectedDigests.isEmpty()) { 1471 return; 1472 } 1473 1474 for (NamedDigest expectedDigest : expectedDigests) { 1475 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; 1476 byte[] actual = 1477 digest( 1478 jcaDigestAlgorithm, 1479 manifestBytes, 1480 manifestMainSection.getStartOffset(), 1481 manifestMainSection.getSizeBytes()); 1482 byte[] expected = expectedDigest.digest; 1483 if (!Arrays.equals(expected, actual)) { 1484 mResult.addError( 1485 Issue.JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY, 1486 jcaDigestAlgorithm, 1487 mSignatureFileEntry.getName(), 1488 Base64.getEncoder().encodeToString(actual), 1489 Base64.getEncoder().encodeToString(expected)); 1490 } 1491 } 1492 } 1493 1494 /** 1495 * Verifies the digest of the manifest's individual section against the corresponding 1496 * individual section of the .SF file. 1497 */ verifyManifestIndividualSectionDigest( ManifestParser.Section sfIndividualSection, boolean createdBySigntool, ManifestParser.Section manifestIndividualSection, byte[] manifestBytes, int minSdkVersion, int maxSdkVersion)1498 private void verifyManifestIndividualSectionDigest( 1499 ManifestParser.Section sfIndividualSection, 1500 boolean createdBySigntool, 1501 ManifestParser.Section manifestIndividualSection, 1502 byte[] manifestBytes, 1503 int minSdkVersion, 1504 int maxSdkVersion) throws NoSuchAlgorithmException { 1505 String entryName = sfIndividualSection.getName(); 1506 Collection<NamedDigest> expectedDigests = 1507 getDigestsToVerify( 1508 sfIndividualSection, "-Digest", minSdkVersion, maxSdkVersion); 1509 if (expectedDigests.isEmpty()) { 1510 mResult.addError( 1511 Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, 1512 entryName, 1513 mSignatureFileEntry.getName()); 1514 return; 1515 } 1516 1517 int sectionStartIndex = manifestIndividualSection.getStartOffset(); 1518 int sectionSizeBytes = manifestIndividualSection.getSizeBytes(); 1519 if (createdBySigntool) { 1520 int sectionEndIndex = sectionStartIndex + sectionSizeBytes; 1521 if ((manifestBytes[sectionEndIndex - 1] == '\n') 1522 && (manifestBytes[sectionEndIndex - 2] == '\n')) { 1523 sectionSizeBytes--; 1524 } 1525 } 1526 for (NamedDigest expectedDigest : expectedDigests) { 1527 String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; 1528 byte[] actual = 1529 digest( 1530 jcaDigestAlgorithm, 1531 manifestBytes, 1532 sectionStartIndex, 1533 sectionSizeBytes); 1534 byte[] expected = expectedDigest.digest; 1535 if (!Arrays.equals(expected, actual)) { 1536 mResult.addError( 1537 Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY, 1538 entryName, 1539 jcaDigestAlgorithm, 1540 mSignatureFileEntry.getName(), 1541 Base64.getEncoder().encodeToString(actual), 1542 Base64.getEncoder().encodeToString(expected)); 1543 } 1544 } 1545 } 1546 checkForStrippedApkSignatures( ManifestParser.Section sfMainSection, Map<Integer, String> supportedApkSigSchemeNames, Set<Integer> foundApkSigSchemeIds)1547 private void checkForStrippedApkSignatures( 1548 ManifestParser.Section sfMainSection, 1549 Map<Integer, String> supportedApkSigSchemeNames, 1550 Set<Integer> foundApkSigSchemeIds) { 1551 String signedWithApkSchemes = 1552 sfMainSection.getAttributeValue( 1553 V1SchemeSigner.SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR); 1554 // This field contains a comma-separated list of APK signature scheme IDs which were 1555 // used to sign this APK. Android rejects APKs where an ID is known to the platform but 1556 // the APK didn't verify using that scheme. 1557 1558 if (signedWithApkSchemes == null) { 1559 // APK signature (e.g., v2 scheme) stripping protections not enabled. 1560 if (!foundApkSigSchemeIds.isEmpty()) { 1561 // APK is signed with an APK signature scheme such as v2 scheme. 1562 mResult.addWarning( 1563 Issue.JAR_SIG_NO_APK_SIG_STRIP_PROTECTION, 1564 mSignatureFileEntry.getName()); 1565 } 1566 return; 1567 } 1568 1569 if (supportedApkSigSchemeNames.isEmpty()) { 1570 return; 1571 } 1572 1573 Set<Integer> supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet(); 1574 Set<Integer> supportedExpectedApkSigSchemeIds = new HashSet<>(1); 1575 StringTokenizer tokenizer = new StringTokenizer(signedWithApkSchemes, ","); 1576 while (tokenizer.hasMoreTokens()) { 1577 String idText = tokenizer.nextToken().trim(); 1578 if (idText.isEmpty()) { 1579 continue; 1580 } 1581 int id; 1582 try { 1583 id = Integer.parseInt(idText); 1584 } catch (Exception ignored) { 1585 continue; 1586 } 1587 // This APK was supposed to be signed with the APK signature scheme having 1588 // this ID. 1589 if (supportedApkSigSchemeIds.contains(id)) { 1590 supportedExpectedApkSigSchemeIds.add(id); 1591 } else { 1592 mResult.addWarning( 1593 Issue.JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID, 1594 mSignatureFileEntry.getName(), 1595 id); 1596 } 1597 } 1598 1599 for (int id : supportedExpectedApkSigSchemeIds) { 1600 if (!foundApkSigSchemeIds.contains(id)) { 1601 String apkSigSchemeName = supportedApkSigSchemeNames.get(id); 1602 mResult.addError( 1603 Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED, 1604 mSignatureFileEntry.getName(), 1605 id, 1606 apkSigSchemeName); 1607 } 1608 } 1609 } 1610 } 1611 getDigestsToVerify( ManifestParser.Section section, String digestAttrSuffix, int minSdkVersion, int maxSdkVersion)1612 private static Collection<NamedDigest> getDigestsToVerify( 1613 ManifestParser.Section section, 1614 String digestAttrSuffix, 1615 int minSdkVersion, 1616 int maxSdkVersion) { 1617 Decoder base64Decoder = Base64.getDecoder(); 1618 List<NamedDigest> result = new ArrayList<>(1); 1619 if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) { 1620 // Prior to JB MR2, Android platform's logic for picking a digest algorithm to verify is 1621 // to rely on the ancient Digest-Algorithms attribute which contains 1622 // whitespace-separated list of digest algorithms (defaulting to SHA-1) to try. The 1623 // first digest attribute (with supported digest algorithm) found using the list is 1624 // used. 1625 String algs = section.getAttributeValue("Digest-Algorithms"); 1626 if (algs == null) { 1627 algs = "SHA SHA1"; 1628 } 1629 StringTokenizer tokens = new StringTokenizer(algs); 1630 while (tokens.hasMoreTokens()) { 1631 String alg = tokens.nextToken(); 1632 String attrName = alg + digestAttrSuffix; 1633 String digestBase64 = section.getAttributeValue(attrName); 1634 if (digestBase64 == null) { 1635 // Attribute not found 1636 continue; 1637 } 1638 alg = getCanonicalJcaMessageDigestAlgorithm(alg); 1639 if ((alg == null) 1640 || (getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile(alg) 1641 > minSdkVersion)) { 1642 // Unsupported digest algorithm 1643 continue; 1644 } 1645 // Supported digest algorithm 1646 result.add(new NamedDigest(alg, base64Decoder.decode(digestBase64))); 1647 break; 1648 } 1649 // No supported digests found -- this will fail to verify on pre-JB MR2 Androids. 1650 if (result.isEmpty()) { 1651 return result; 1652 } 1653 } 1654 1655 if (maxSdkVersion >= AndroidSdkVersion.JELLY_BEAN_MR2) { 1656 // On JB MR2 and newer, Android platform picks the strongest algorithm out of: 1657 // SHA-512, SHA-384, SHA-256, SHA-1. 1658 for (String alg : JB_MR2_AND_NEWER_DIGEST_ALGS) { 1659 String attrName = getJarDigestAttributeName(alg, digestAttrSuffix); 1660 String digestBase64 = section.getAttributeValue(attrName); 1661 if (digestBase64 == null) { 1662 // Attribute not found 1663 continue; 1664 } 1665 byte[] digest = base64Decoder.decode(digestBase64); 1666 byte[] digestInResult = getDigest(result, alg); 1667 if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) { 1668 result.add(new NamedDigest(alg, digest)); 1669 } 1670 break; 1671 } 1672 } 1673 1674 return result; 1675 } 1676 1677 private static final String[] JB_MR2_AND_NEWER_DIGEST_ALGS = { 1678 "SHA-512", 1679 "SHA-384", 1680 "SHA-256", 1681 "SHA-1", 1682 }; 1683 getCanonicalJcaMessageDigestAlgorithm(String algorithm)1684 private static String getCanonicalJcaMessageDigestAlgorithm(String algorithm) { 1685 return UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.get(algorithm.toUpperCase(Locale.US)); 1686 } 1687 getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile( String jcaAlgorithmName)1688 public static int getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile( 1689 String jcaAlgorithmName) { 1690 Integer result = 1691 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.get( 1692 jcaAlgorithmName.toUpperCase(Locale.US)); 1693 return (result != null) ? result : Integer.MAX_VALUE; 1694 } 1695 getJarDigestAttributeName( String jcaDigestAlgorithm, String attrNameSuffix)1696 private static String getJarDigestAttributeName( 1697 String jcaDigestAlgorithm, String attrNameSuffix) { 1698 if ("SHA-1".equalsIgnoreCase(jcaDigestAlgorithm)) { 1699 return "SHA1" + attrNameSuffix; 1700 } else { 1701 return jcaDigestAlgorithm + attrNameSuffix; 1702 } 1703 } 1704 1705 private static final Map<String, String> UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL; 1706 static { 1707 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL = new HashMap<>(8); 1708 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("MD5", "MD5"); 1709 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA", "SHA-1"); 1710 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA1", "SHA-1"); 1711 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-1", "SHA-1"); 1712 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-256", "SHA-256"); 1713 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-384", "SHA-384"); 1714 UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-512", "SHA-512"); 1715 } 1716 1717 private static final Map<String, Integer> 1718 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST; 1719 static { 1720 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST = new HashMap<>(5); 1721 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("MD5", 0); 1722 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-1", 0); 1723 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-256", 0); 1724 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put( 1725 "SHA-384", AndroidSdkVersion.GINGERBREAD); 1726 MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put( 1727 "SHA-512", AndroidSdkVersion.GINGERBREAD); 1728 } 1729 getDigest(Collection<NamedDigest> digests, String jcaDigestAlgorithm)1730 private static byte[] getDigest(Collection<NamedDigest> digests, String jcaDigestAlgorithm) { 1731 for (NamedDigest digest : digests) { 1732 if (digest.jcaDigestAlgorithm.equalsIgnoreCase(jcaDigestAlgorithm)) { 1733 return digest.digest; 1734 } 1735 } 1736 return null; 1737 } 1738 parseZipCentralDirectory( DataSource apk, ApkUtils.ZipSections apkSections)1739 public static List<CentralDirectoryRecord> parseZipCentralDirectory( 1740 DataSource apk, 1741 ApkUtils.ZipSections apkSections) 1742 throws IOException, ApkFormatException { 1743 // Read the ZIP Central Directory 1744 long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes(); 1745 if (cdSizeBytes > Integer.MAX_VALUE) { 1746 throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes); 1747 } 1748 long cdOffset = apkSections.getZipCentralDirectoryOffset(); 1749 ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes); 1750 cd.order(ByteOrder.LITTLE_ENDIAN); 1751 1752 // Parse the ZIP Central Directory 1753 int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount(); 1754 List<CentralDirectoryRecord> cdRecords = new ArrayList<>(expectedCdRecordCount); 1755 for (int i = 0; i < expectedCdRecordCount; i++) { 1756 CentralDirectoryRecord cdRecord; 1757 int offsetInsideCd = cd.position(); 1758 try { 1759 cdRecord = CentralDirectoryRecord.getRecord(cd); 1760 } catch (ZipFormatException e) { 1761 throw new ApkFormatException( 1762 "Malformed ZIP Central Directory record #" + (i + 1) 1763 + " at file offset " + (cdOffset + offsetInsideCd), 1764 e); 1765 } 1766 String entryName = cdRecord.getName(); 1767 if (entryName.endsWith("/")) { 1768 // Ignore directory entries 1769 continue; 1770 } 1771 cdRecords.add(cdRecord); 1772 } 1773 // There may be more data in Central Directory, but we don't warn or throw because Android 1774 // ignores unused CD data. 1775 1776 return cdRecords; 1777 } 1778 1779 /** 1780 * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's 1781 * manifest for the APK to verify on Android. 1782 */ isJarEntryDigestNeededInManifest(String entryName)1783 private static boolean isJarEntryDigestNeededInManifest(String entryName) { 1784 // NOTE: This logic is different from what's required by the JAR signing scheme. This is 1785 // because Android's APK verification logic differs from that spec. In particular, JAR 1786 // signing spec includes into JAR manifest all files in subdirectories of META-INF and 1787 // any files inside META-INF not related to signatures. 1788 if (entryName.startsWith("META-INF/")) { 1789 return false; 1790 } 1791 return !entryName.endsWith("/"); 1792 } 1793 verifyJarEntriesAgainstManifestAndSigners( DataSource apk, long cdOffsetInApk, Collection<CentralDirectoryRecord> cdRecords, Map<String, ManifestParser.Section> entryNameToManifestSection, List<Signer> signers, int minSdkVersion, int maxSdkVersion, Result result)1794 private static Set<Signer> verifyJarEntriesAgainstManifestAndSigners( 1795 DataSource apk, 1796 long cdOffsetInApk, 1797 Collection<CentralDirectoryRecord> cdRecords, 1798 Map<String, ManifestParser.Section> entryNameToManifestSection, 1799 List<Signer> signers, 1800 int minSdkVersion, 1801 int maxSdkVersion, 1802 Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { 1803 // Iterate over APK contents as sequentially as possible to improve performance. 1804 List<CentralDirectoryRecord> cdRecordsSortedByLocalFileHeaderOffset = 1805 new ArrayList<>(cdRecords); 1806 Collections.sort( 1807 cdRecordsSortedByLocalFileHeaderOffset, 1808 CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR); 1809 Set<String> manifestEntryNamesMissingFromApk = 1810 new HashSet<>(entryNameToManifestSection.keySet()); 1811 List<Signer> firstSignedEntrySigners = null; 1812 String firstSignedEntryName = null; 1813 for (CentralDirectoryRecord cdRecord : cdRecordsSortedByLocalFileHeaderOffset) { 1814 String entryName = cdRecord.getName(); 1815 manifestEntryNamesMissingFromApk.remove(entryName); 1816 if (!isJarEntryDigestNeededInManifest(entryName)) { 1817 continue; 1818 } 1819 1820 ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName); 1821 if (manifestSection == null) { 1822 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName); 1823 continue; 1824 } 1825 1826 List<Signer> entrySigners = new ArrayList<>(signers.size()); 1827 for (Signer signer : signers) { 1828 if (signer.getSigFileEntryNames().contains(entryName)) { 1829 entrySigners.add(signer); 1830 } 1831 } 1832 if (entrySigners.isEmpty()) { 1833 result.addError(Issue.JAR_SIG_ZIP_ENTRY_NOT_SIGNED, entryName); 1834 continue; 1835 } 1836 if (firstSignedEntrySigners == null) { 1837 firstSignedEntrySigners = entrySigners; 1838 firstSignedEntryName = entryName; 1839 } else if (!entrySigners.equals(firstSignedEntrySigners)) { 1840 result.addError( 1841 Issue.JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH, 1842 firstSignedEntryName, 1843 getSignerNames(firstSignedEntrySigners), 1844 entryName, 1845 getSignerNames(entrySigners)); 1846 continue; 1847 } 1848 1849 List<NamedDigest> expectedDigests = 1850 new ArrayList<>( 1851 getDigestsToVerify( 1852 manifestSection, "-Digest", minSdkVersion, maxSdkVersion)); 1853 if (expectedDigests.isEmpty()) { 1854 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName); 1855 continue; 1856 } 1857 1858 MessageDigest[] mds = new MessageDigest[expectedDigests.size()]; 1859 for (int i = 0; i < expectedDigests.size(); i++) { 1860 mds[i] = getMessageDigest(expectedDigests.get(i).jcaDigestAlgorithm); 1861 } 1862 1863 try { 1864 LocalFileRecord.outputUncompressedData( 1865 apk, 1866 cdRecord, 1867 cdOffsetInApk, 1868 DataSinks.asDataSink(mds)); 1869 } catch (ZipFormatException e) { 1870 throw new ApkFormatException("Malformed ZIP entry: " + entryName, e); 1871 } catch (IOException e) { 1872 throw new IOException("Failed to read entry: " + entryName, e); 1873 } 1874 1875 for (int i = 0; i < expectedDigests.size(); i++) { 1876 NamedDigest expectedDigest = expectedDigests.get(i); 1877 byte[] actualDigest = mds[i].digest(); 1878 if (!Arrays.equals(expectedDigest.digest, actualDigest)) { 1879 result.addError( 1880 Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY, 1881 entryName, 1882 expectedDigest.jcaDigestAlgorithm, 1883 V1SchemeSigner.MANIFEST_ENTRY_NAME, 1884 Base64.getEncoder().encodeToString(actualDigest), 1885 Base64.getEncoder().encodeToString(expectedDigest.digest)); 1886 } 1887 } 1888 } 1889 1890 if (firstSignedEntrySigners == null) { 1891 result.addError(Issue.JAR_SIG_NO_SIGNED_ZIP_ENTRIES); 1892 return Collections.emptySet(); 1893 } else { 1894 return new HashSet<>(firstSignedEntrySigners); 1895 } 1896 } 1897 getSignerNames(List<Signer> signers)1898 private static List<String> getSignerNames(List<Signer> signers) { 1899 if (signers.isEmpty()) { 1900 return Collections.emptyList(); 1901 } 1902 List<String> result = new ArrayList<>(signers.size()); 1903 for (Signer signer : signers) { 1904 result.add(signer.getName()); 1905 } 1906 return result; 1907 } 1908 getMessageDigest(String algorithm)1909 private static MessageDigest getMessageDigest(String algorithm) 1910 throws NoSuchAlgorithmException { 1911 return MessageDigest.getInstance(algorithm); 1912 } 1913 digest(String algorithm, byte[] data, int offset, int length)1914 private static byte[] digest(String algorithm, byte[] data, int offset, int length) 1915 throws NoSuchAlgorithmException { 1916 MessageDigest md = getMessageDigest(algorithm); 1917 md.update(data, offset, length); 1918 return md.digest(); 1919 } 1920 digest(String algorithm, byte[] data)1921 private static byte[] digest(String algorithm, byte[] data) throws NoSuchAlgorithmException { 1922 return getMessageDigest(algorithm).digest(data); 1923 } 1924 1925 private static class NamedDigest { 1926 private final String jcaDigestAlgorithm; 1927 private final byte[] digest; 1928 NamedDigest(String jcaDigestAlgorithm, byte[] digest)1929 private NamedDigest(String jcaDigestAlgorithm, byte[] digest) { 1930 this.jcaDigestAlgorithm = jcaDigestAlgorithm; 1931 this.digest = digest; 1932 } 1933 } 1934 1935 public static class Result { 1936 1937 /** Whether the APK's JAR signature verifies. */ 1938 public boolean verified; 1939 1940 /** List of APK's signers. These signers are used by Android. */ 1941 public final List<SignerInfo> signers = new ArrayList<>(); 1942 1943 /** 1944 * Signers encountered in the APK but not included in the set of the APK's signers. These 1945 * signers are ignored by Android. 1946 */ 1947 public final List<SignerInfo> ignoredSigners = new ArrayList<>(); 1948 1949 private final List<IssueWithParams> mWarnings = new ArrayList<>(); 1950 private final List<IssueWithParams> mErrors = new ArrayList<>(); 1951 containsErrors()1952 private boolean containsErrors() { 1953 if (!mErrors.isEmpty()) { 1954 return true; 1955 } 1956 for (SignerInfo signer : signers) { 1957 if (signer.containsErrors()) { 1958 return true; 1959 } 1960 } 1961 return false; 1962 } 1963 addError(Issue msg, Object... parameters)1964 private void addError(Issue msg, Object... parameters) { 1965 mErrors.add(new IssueWithParams(msg, parameters)); 1966 } 1967 addWarning(Issue msg, Object... parameters)1968 private void addWarning(Issue msg, Object... parameters) { 1969 mWarnings.add(new IssueWithParams(msg, parameters)); 1970 } 1971 getErrors()1972 public List<IssueWithParams> getErrors() { 1973 return mErrors; 1974 } 1975 getWarnings()1976 public List<IssueWithParams> getWarnings() { 1977 return mWarnings; 1978 } 1979 1980 public static class SignerInfo { 1981 public final String name; 1982 public final String signatureFileName; 1983 public final String signatureBlockFileName; 1984 public final List<X509Certificate> certChain = new ArrayList<>(); 1985 1986 private final List<IssueWithParams> mWarnings = new ArrayList<>(); 1987 private final List<IssueWithParams> mErrors = new ArrayList<>(); 1988 SignerInfo( String name, String signatureBlockFileName, String signatureFileName)1989 private SignerInfo( 1990 String name, String signatureBlockFileName, String signatureFileName) { 1991 this.name = name; 1992 this.signatureBlockFileName = signatureBlockFileName; 1993 this.signatureFileName = signatureFileName; 1994 } 1995 containsErrors()1996 private boolean containsErrors() { 1997 return !mErrors.isEmpty(); 1998 } 1999 addError(Issue msg, Object... parameters)2000 private void addError(Issue msg, Object... parameters) { 2001 mErrors.add(new IssueWithParams(msg, parameters)); 2002 } 2003 addWarning(Issue msg, Object... parameters)2004 private void addWarning(Issue msg, Object... parameters) { 2005 mWarnings.add(new IssueWithParams(msg, parameters)); 2006 } 2007 getErrors()2008 public List<IssueWithParams> getErrors() { 2009 return mErrors; 2010 } 2011 getWarnings()2012 public List<IssueWithParams> getWarnings() { 2013 return mWarnings; 2014 } 2015 } 2016 } 2017 2018 private static class SignedAttributes { 2019 private Map<String, List<Asn1OpaqueObject>> mAttrs; 2020 SignedAttributes(Collection<Attribute> attrs)2021 public SignedAttributes(Collection<Attribute> attrs) throws Pkcs7DecodingException { 2022 Map<String, List<Asn1OpaqueObject>> result = new HashMap<>(attrs.size()); 2023 for (Attribute attr : attrs) { 2024 if (result.put(attr.attrType, attr.attrValues) != null) { 2025 throw new Pkcs7DecodingException("Duplicate signed attribute: " + attr.attrType); 2026 } 2027 } 2028 mAttrs = result; 2029 } 2030 getSingleValue(String attrOid)2031 private Asn1OpaqueObject getSingleValue(String attrOid) throws Pkcs7DecodingException { 2032 List<Asn1OpaqueObject> values = mAttrs.get(attrOid); 2033 if ((values == null) || (values.isEmpty())) { 2034 return null; 2035 } 2036 if (values.size() > 1) { 2037 throw new Pkcs7DecodingException("Attribute " + attrOid + " has multiple values"); 2038 } 2039 return values.get(0); 2040 } 2041 getSingleObjectIdentifierValue(String attrOid)2042 public String getSingleObjectIdentifierValue(String attrOid) throws Pkcs7DecodingException { 2043 Asn1OpaqueObject value = getSingleValue(attrOid); 2044 if (value == null) { 2045 return null; 2046 } 2047 try { 2048 return Asn1BerParser.parse(value.getEncoded(), ObjectIdentifierChoice.class).value; 2049 } catch (Asn1DecodingException e) { 2050 throw new Pkcs7DecodingException("Failed to decode OBJECT IDENTIFIER", e); 2051 } 2052 } 2053 getSingleOctetStringValue(String attrOid)2054 public byte[] getSingleOctetStringValue(String attrOid) throws Pkcs7DecodingException { 2055 Asn1OpaqueObject value = getSingleValue(attrOid); 2056 if (value == null) { 2057 return null; 2058 } 2059 try { 2060 return Asn1BerParser.parse(value.getEncoded(), OctetStringChoice.class).value; 2061 } catch (Asn1DecodingException e) { 2062 throw new Pkcs7DecodingException("Failed to decode OBJECT IDENTIFIER", e); 2063 } 2064 } 2065 } 2066 2067 @Asn1Class(type = Asn1Type.CHOICE) 2068 public static class OctetStringChoice { 2069 @Asn1Field(type = Asn1Type.OCTET_STRING) 2070 public byte[] value; 2071 } 2072 2073 @Asn1Class(type = Asn1Type.CHOICE) 2074 public static class ObjectIdentifierChoice { 2075 @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER) 2076 public String value; 2077 } 2078 } 2079