Search in sources :

Example 1 with RunnablesExecutor

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;
}
Also used : HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ApkSigResult(com.android.apksig.internal.apk.ApkSigResult) ApkSigResult(com.android.apksig.internal.apk.ApkSigResult) List(java.util.List) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) V1SchemeVerifier(com.android.apksig.internal.apk.v1.V1SchemeVerifier) X509Certificate(java.security.cert.X509Certificate) SignatureNotFoundException(com.android.apksig.internal.apk.SignatureNotFoundException) Map(java.util.Map) EnumMap(java.util.EnumMap) HashMap(java.util.HashMap) CentralDirectoryRecord(com.android.apksig.internal.zip.CentralDirectoryRecord) ZipFormatException(com.android.apksig.zip.ZipFormatException) ApkSigningBlockUtils(com.android.apksig.internal.apk.ApkSigningBlockUtils) ApkFormatException(com.android.apksig.apk.ApkFormatException) RunnablesExecutor(com.android.apksig.util.RunnablesExecutor) CertificateEncodingException(java.security.cert.CertificateEncodingException) ByteBuffer(java.nio.ByteBuffer) ApkUtils(com.android.apksig.apk.ApkUtils)

Aggregations

ApkFormatException (com.android.apksig.apk.ApkFormatException)1 ApkUtils (com.android.apksig.apk.ApkUtils)1 ApkSigResult (com.android.apksig.internal.apk.ApkSigResult)1 ApkSigningBlockUtils (com.android.apksig.internal.apk.ApkSigningBlockUtils)1 SignatureNotFoundException (com.android.apksig.internal.apk.SignatureNotFoundException)1 V1SchemeVerifier (com.android.apksig.internal.apk.v1.V1SchemeVerifier)1 CentralDirectoryRecord (com.android.apksig.internal.zip.CentralDirectoryRecord)1 RunnablesExecutor (com.android.apksig.util.RunnablesExecutor)1 ZipFormatException (com.android.apksig.zip.ZipFormatException)1 ByteBuffer (java.nio.ByteBuffer)1 CertificateEncodingException (java.security.cert.CertificateEncodingException)1 X509Certificate (java.security.cert.X509Certificate)1 ArrayList (java.util.ArrayList)1 EnumMap (java.util.EnumMap)1 HashMap (java.util.HashMap)1 HashSet (java.util.HashSet)1 List (java.util.List)1 Map (java.util.Map)1