use of com.android.apksig.util.RunnablesExecutor in project apksig by venshine.
the class ApkVerifier method verify.
/**
* Verifies the APK's signatures and returns the result of verification. The APK can be
* considered verified iff the result's {@link Result#isVerified()} returns {@code true}.
* The verification result also includes errors, warnings, and information about signers.
*
* @param apk APK file contents
* @throws IOException if an I/O error is encountered while reading the APK
* @throws ApkFormatException if the APK is malformed
* @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
* required cryptographic algorithm implementation is missing
*/
private Result verify(DataSource apk) throws IOException, ApkFormatException, NoSuchAlgorithmException {
int maxSdkVersion = mMaxSdkVersion;
ApkUtils.ZipSections zipSections;
try {
zipSections = ApkUtils.findZipSections(apk);
} catch (ZipFormatException e) {
throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
}
ByteBuffer androidManifest = null;
int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections);
Result result = new Result();
Map<Integer, Map<ContentDigestAlgorithm, byte[]>> signatureSchemeApkContentDigests = new HashMap<>();
// The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme
// name, but the verifiers use this parameter as the schemes supported by the target SDK
// range. Since the code below skips signature verification based on max SDK the mapping of
// supported schemes needs to be modified to ensure the verifiers do not report a stripped
// signature for an SDK range that does not support that signature version. For instance an
// APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature
// verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2
// verification is performed it would see the stripping protection attribute, see that V3
// is in the list of supported signatures, and report a stripped signature.
Map<Integer, String> supportedSchemeNames = getSupportedSchemeNames(maxSdkVersion);
// Android N and newer attempts to verify APKs using the APK Signing Block, which can
// include v2 and/or v3 signatures. If none is found, it falls back to JAR signature
// verification. If the signature is found but does not verify, the APK is rejected.
Set<Integer> foundApkSigSchemeIds = new HashSet<>(2);
if (maxSdkVersion >= AndroidSdkVersion.N) {
RunnablesExecutor executor = RunnablesExecutor.SINGLE_THREADED;
// Android P and newer attempts to verify APKs using APK Signature Scheme v3
if (maxSdkVersion >= AndroidSdkVersion.P) {
try {
ApkSigningBlockUtils.Result v3Result = V3SchemeVerifier.verify(executor, apk, zipSections, Math.max(minSdkVersion, AndroidSdkVersion.P), maxSdkVersion);
foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
result.mergeFrom(v3Result);
signatureSchemeApkContentDigests.put(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3, getApkContentDigestsFromSigningSchemeResult(v3Result));
} catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) {
// v3 signature not required
}
if (result.containsErrors()) {
return result;
}
}
// no APK Signature Scheme v3 (or newer scheme) signatures were found.
if (minSdkVersion < AndroidSdkVersion.P || foundApkSigSchemeIds.isEmpty()) {
try {
ApkSigningBlockUtils.Result v2Result = V2SchemeVerifier.verify(executor, apk, zipSections, supportedSchemeNames, foundApkSigSchemeIds, Math.max(minSdkVersion, AndroidSdkVersion.N), maxSdkVersion);
foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
result.mergeFrom(v2Result);
signatureSchemeApkContentDigests.put(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, getApkContentDigestsFromSigningSchemeResult(v2Result));
} catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) {
// v2 signature not required
}
if (result.containsErrors()) {
return result;
}
}
// If v4 file is specified, use additional verification on it
if (mV4SignatureFile != null) {
final ApkSigningBlockUtils.Result v4Result = V4SchemeVerifier.verify(apk, mV4SignatureFile);
foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
result.mergeFrom(v4Result);
if (result.containsErrors()) {
return result;
}
}
}
// are signed using APK Signature Scheme v2 or newer.
if (maxSdkVersion >= AndroidSdkVersion.O) {
if (androidManifest == null) {
androidManifest = getAndroidManifestFromApk(apk, zipSections);
}
int targetSandboxVersion = getTargetSandboxVersionFromBinaryAndroidManifest(androidManifest.slice());
if (targetSandboxVersion > 1) {
if (foundApkSigSchemeIds.isEmpty()) {
result.addError(Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION, targetSandboxVersion);
}
}
}
List<CentralDirectoryRecord> cdRecords = V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
// scheme) signatures were found.
if ((minSdkVersion < AndroidSdkVersion.N) || (foundApkSigSchemeIds.isEmpty())) {
V1SchemeVerifier.Result v1Result = V1SchemeVerifier.verify(apk, zipSections, supportedSchemeNames, foundApkSigSchemeIds, minSdkVersion, maxSdkVersion);
result.mergeFrom(v1Result);
signatureSchemeApkContentDigests.put(ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME, getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections));
}
if (result.containsErrors()) {
return result;
}
// Verify the SourceStamp, if found in the APK.
try {
CentralDirectoryRecord sourceStampCdRecord = null;
for (CentralDirectoryRecord cdRecord : cdRecords) {
if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
sourceStampCdRecord = cdRecord;
break;
}
}
// block in the APK signing block as well.
if (sourceStampCdRecord != null) {
byte[] sourceStampCertificateDigest = LocalFileRecord.getUncompressedData(apk, sourceStampCdRecord, zipSections.getZipCentralDirectoryOffset());
ApkSigResult sourceStampResult = V2SourceStampVerifier.verify(apk, zipSections, sourceStampCertificateDigest, signatureSchemeApkContentDigests, Math.max(minSdkVersion, AndroidSdkVersion.R), maxSdkVersion);
result.mergeFrom(sourceStampResult);
}
} catch (SignatureNotFoundException ignored) {
result.addWarning(Issue.SOURCE_STAMP_SIG_MISSING);
} catch (ZipFormatException e) {
throw new ApkFormatException("Failed to read APK", e);
}
if (result.containsErrors()) {
return result;
}
// signatures verified.
if ((result.isVerifiedUsingV1Scheme()) && (result.isVerifiedUsingV2Scheme())) {
ArrayList<Result.V1SchemeSignerInfo> v1Signers = new ArrayList<>(result.getV1SchemeSigners());
ArrayList<Result.V2SchemeSignerInfo> v2Signers = new ArrayList<>(result.getV2SchemeSigners());
ArrayList<ByteArray> v1SignerCerts = new ArrayList<>();
ArrayList<ByteArray> v2SignerCerts = new ArrayList<>();
for (Result.V1SchemeSignerInfo signer : v1Signers) {
try {
v1SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded()));
} catch (CertificateEncodingException e) {
throw new IllegalStateException("Failed to encode JAR signer " + signer.getName() + " certs", e);
}
}
for (Result.V2SchemeSignerInfo signer : v2Signers) {
try {
v2SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded()));
} catch (CertificateEncodingException e) {
throw new IllegalStateException("Failed to encode APK Signature Scheme v2 signer (index: " + signer.getIndex() + ") certs", e);
}
}
for (int i = 0; i < v1SignerCerts.size(); i++) {
ByteArray v1Cert = v1SignerCerts.get(i);
if (!v2SignerCerts.contains(v1Cert)) {
Result.V1SchemeSignerInfo v1Signer = v1Signers.get(i);
v1Signer.addError(Issue.V2_SIG_MISSING);
break;
}
}
for (int i = 0; i < v2SignerCerts.size(); i++) {
ByteArray v2Cert = v2SignerCerts.get(i);
if (!v1SignerCerts.contains(v2Cert)) {
Result.V2SchemeSignerInfo v2Signer = v2Signers.get(i);
v2Signer.addError(Issue.JAR_SIG_MISSING);
break;
}
}
}
// matches the oldest signing certificate in the provided SigningCertificateLineage
if (result.isVerifiedUsingV3Scheme() && (result.isVerifiedUsingV1Scheme() || result.isVerifiedUsingV2Scheme())) {
SigningCertificateLineage lineage = result.getSigningCertificateLineage();
X509Certificate oldSignerCert;
if (result.isVerifiedUsingV1Scheme()) {
List<Result.V1SchemeSignerInfo> v1Signers = result.getV1SchemeSigners();
if (v1Signers.size() != 1) {
// APK Signature Scheme v3 only supports single-signers, error to sign with
// multiple and then only one
result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS);
}
oldSignerCert = v1Signers.get(0).mCertChain.get(0);
} else {
List<Result.V2SchemeSignerInfo> v2Signers = result.getV2SchemeSigners();
if (v2Signers.size() != 1) {
// APK Signature Scheme v3 only supports single-signers, error to sign with
// multiple and then only one
result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS);
}
oldSignerCert = v2Signers.get(0).mCerts.get(0);
}
if (lineage == null) {
// no signing certificate history with which to contend, just make sure that v3
// matches previous versions
List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners();
if (v3Signers.size() != 1) {
// multiple v3 signers should never exist without rotation history, since
// multiple signers implies a different signer for different platform versions
result.addError(Issue.V3_SIG_MULTIPLE_SIGNERS);
}
try {
if (!Arrays.equals(oldSignerCert.getEncoded(), v3Signers.get(0).mCerts.get(0).getEncoded())) {
result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
}
} catch (CertificateEncodingException e) {
// we just go the encoding for the v1/v2 certs above, so must be v3
throw new RuntimeException("Failed to encode APK Signature Scheme v3 signer cert", e);
}
} else {
// as our v1/v2 signer
try {
lineage = lineage.getSubLineage(oldSignerCert);
if (lineage.size() != 1) {
// the v1/v2 signer was found, but not at the root of the lineage
result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
}
} catch (IllegalArgumentException e) {
// the v1/v2 signer was not found in the lineage
result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
}
}
}
// The apkDigest field in the v4 signature should match the selected v2/v3.
if (result.isVerifiedUsingV4Scheme()) {
List<Result.V4SchemeSignerInfo> v4Signers = result.getV4SchemeSigners();
if (v4Signers.size() != 1) {
result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
}
List<ApkSigningBlockUtils.Result.SignerInfo.ContentDigest> digestsFromV4 = v4Signers.get(0).getContentDigests();
if (digestsFromV4.size() != 1) {
result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
}
final byte[] digestFromV4 = digestsFromV4.get(0).getValue();
if (result.isVerifiedUsingV3Scheme()) {
List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners();
if (v3Signers.size() != 1) {
result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
}
// Compare certificates.
checkV4Certificate(v4Signers.get(0).mCerts, v3Signers.get(0).mCerts, result);
// Compare digests.
final byte[] digestFromV3 = pickBestDigestForV4(v3Signers.get(0).getContentDigests());
if (!Arrays.equals(digestFromV4, digestFromV3)) {
result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
}
} else if (result.isVerifiedUsingV2Scheme()) {
List<Result.V2SchemeSignerInfo> v2Signers = result.getV2SchemeSigners();
if (v2Signers.size() != 1) {
result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
}
// Compare certificates.
checkV4Certificate(v4Signers.get(0).mCerts, v2Signers.get(0).mCerts, result);
// Compare digests.
final byte[] digestFromV2 = pickBestDigestForV4(v2Signers.get(0).getContentDigests());
if (!Arrays.equals(digestFromV4, digestFromV2)) {
result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
}
} else {
throw new RuntimeException("V4 signature must be also verified with V2/V3");
}
}
// that the APK was signed with at least that version.
try {
if (androidManifest == null) {
androidManifest = getAndroidManifestFromApk(apk, zipSections);
}
} catch (ApkFormatException e) {
// If the manifest is not available then skip the minimum signature scheme requirement
// to support bundle verification.
}
if (androidManifest != null) {
int targetSdkVersion = getTargetSdkVersionFromBinaryAndroidManifest(androidManifest.slice());
int minSchemeVersion = getMinimumSignatureSchemeVersionForTargetSdk(targetSdkVersion);
// expanded to verify the minimum based on the target and maximum SDK version.
if (minSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME && maxSdkVersion >= targetSdkVersion) {
switch(minSchemeVersion) {
case VERSION_APK_SIGNATURE_SCHEME_V2:
if (result.isVerifiedUsingV2Scheme()) {
break;
}
// later scheme version will also satisfy this requirement.
case VERSION_APK_SIGNATURE_SCHEME_V3:
if (result.isVerifiedUsingV3Scheme()) {
break;
}
result.addError(Issue.MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET, targetSdkVersion, minSchemeVersion);
}
}
}
if (result.containsErrors()) {
return result;
}
// Verified
result.setVerified();
if (result.isVerifiedUsingV3Scheme()) {
List<Result.V3SchemeSignerInfo> v3Signers = result.getV3SchemeSigners();
result.addSignerCertificate(v3Signers.get(v3Signers.size() - 1).getCertificate());
} else if (result.isVerifiedUsingV2Scheme()) {
for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) {
result.addSignerCertificate(signerInfo.getCertificate());
}
} else if (result.isVerifiedUsingV1Scheme()) {
for (Result.V1SchemeSignerInfo signerInfo : result.getV1SchemeSigners()) {
result.addSignerCertificate(signerInfo.getCertificate());
}
} else {
throw new RuntimeException("APK verified, but has not verified using any of v1, v2 or v3 schemes");
}
return result;
}
Aggregations