Search in sources :

Example 11 with ZipFormatException

use of com.android.apksig.zip.ZipFormatException in project apksig by venshine.

the class ZipUtils method parseZipCentralDirectory.

public static List<CentralDirectoryRecord> parseZipCentralDirectory(DataSource apk, ZipSections apkSections) throws IOException, ApkFormatException {
    // Read the ZIP Central Directory
    long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes();
    if (cdSizeBytes > Integer.MAX_VALUE) {
        throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes);
    }
    long cdOffset = apkSections.getZipCentralDirectoryOffset();
    ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes);
    cd.order(ByteOrder.LITTLE_ENDIAN);
    // Parse the ZIP Central Directory
    int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount();
    List<CentralDirectoryRecord> cdRecords = new ArrayList<>(expectedCdRecordCount);
    for (int i = 0; i < expectedCdRecordCount; i++) {
        CentralDirectoryRecord cdRecord;
        int offsetInsideCd = cd.position();
        try {
            cdRecord = CentralDirectoryRecord.getRecord(cd);
        } catch (ZipFormatException e) {
            throw new ApkFormatException("Malformed ZIP Central Directory record #" + (i + 1) + " at file offset " + (cdOffset + offsetInsideCd), e);
        }
        String entryName = cdRecord.getName();
        if (entryName.endsWith("/")) {
            // Ignore directory entries
            continue;
        }
        cdRecords.add(cdRecord);
    }
    return cdRecords;
}
Also used : ApkFormatException(com.android.apksig.apk.ApkFormatException) ArrayList(java.util.ArrayList) ZipFormatException(com.android.apksig.zip.ZipFormatException) ByteBuffer(java.nio.ByteBuffer)

Example 12 with ZipFormatException

use of com.android.apksig.zip.ZipFormatException in project apksig by venshine.

the class SigningCertificateLineage method readFromApkDataSource.

/**
 * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3
 * signature block of the provided APK DataSource.
 *
 * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block,
 * or if the V3 signature block does not contain a valid lineage.
 */
public static SigningCertificateLineage readFromApkDataSource(DataSource apk) throws IOException, ApkFormatException {
    SignatureInfo signatureInfo;
    try {
        ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
        ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
        signatureInfo = ApkSigningBlockUtils.findSignature(apk, zipSections, V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result);
    } catch (ZipFormatException e) {
        throw new ApkFormatException(e.getMessage());
    } catch (ApkSigningBlockUtils.SignatureNotFoundException e) {
        throw new IllegalArgumentException("The provided APK does not contain a valid V3 signature block.");
    }
    // FORMAT:
    // * length-prefixed sequence of length-prefixed signers:
    // * length-prefixed signed data
    // * minSDK
    // * maxSDK
    // * length-prefixed sequence of length-prefixed signatures
    // * length-prefixed public key
    ByteBuffer signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
    List<SigningCertificateLineage> lineages = new ArrayList<>(1);
    while (signers.hasRemaining()) {
        ByteBuffer signer = getLengthPrefixedSlice(signers);
        ByteBuffer signedData = getLengthPrefixedSlice(signer);
        try {
            SigningCertificateLineage lineage = readFromSignedData(signedData);
            lineages.add(lineage);
        } catch (IllegalArgumentException ignored) {
        // The current signer block does not contain a valid lineage, but it is possible
        // another block will.
        }
    }
    SigningCertificateLineage result;
    if (lineages.isEmpty()) {
        throw new IllegalArgumentException("The provided APK does not contain a valid lineage.");
    } else if (lineages.size() > 1) {
        result = consolidateLineages(lineages);
    } else {
        result = lineages.get(0);
    }
    return result;
}
Also used : ArrayList(java.util.ArrayList) ZipFormatException(com.android.apksig.zip.ZipFormatException) ApkSigningBlockUtils(com.android.apksig.internal.apk.ApkSigningBlockUtils) ByteBuffer(java.nio.ByteBuffer) SignatureInfo(com.android.apksig.internal.apk.SignatureInfo) V3SigningCertificateLineage(com.android.apksig.internal.apk.v3.V3SigningCertificateLineage) ApkUtils(com.android.apksig.apk.ApkUtils) ApkFormatException(com.android.apksig.apk.ApkFormatException)

Example 13 with ZipFormatException

use of com.android.apksig.zip.ZipFormatException in project apksig by venshine.

the class ApkVerifier method getApkContentDigestFromV1SigningScheme.

private static Map<ContentDigestAlgorithm, byte[]> getApkContentDigestFromV1SigningScheme(List<CentralDirectoryRecord> cdRecords, DataSource apk, ApkUtils.ZipSections zipSections) throws IOException, ApkFormatException {
    CentralDirectoryRecord manifestCdRecord = null;
    Map<ContentDigestAlgorithm, byte[]> v1ContentDigest = new EnumMap<>(ContentDigestAlgorithm.class);
    for (CentralDirectoryRecord cdRecord : cdRecords) {
        if (MANIFEST_ENTRY_NAME.equals(cdRecord.getName())) {
            manifestCdRecord = cdRecord;
            break;
        }
    }
    if (manifestCdRecord == null) {
        // thus an empty digest will invalidate that signature.
        return v1ContentDigest;
    }
    try {
        byte[] manifestBytes = LocalFileRecord.getUncompressedData(apk, manifestCdRecord, zipSections.getZipCentralDirectoryOffset());
        v1ContentDigest.put(ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(manifestBytes));
        return v1ContentDigest;
    } catch (ZipFormatException e) {
        throw new ApkFormatException("Failed to read APK", e);
    }
}
Also used : CentralDirectoryRecord(com.android.apksig.internal.zip.CentralDirectoryRecord) ApkFormatException(com.android.apksig.apk.ApkFormatException) ZipFormatException(com.android.apksig.zip.ZipFormatException) ContentDigestAlgorithm(com.android.apksig.internal.apk.ContentDigestAlgorithm) EnumMap(java.util.EnumMap)

Example 14 with ZipFormatException

use of com.android.apksig.zip.ZipFormatException 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)

Example 15 with ZipFormatException

use of com.android.apksig.zip.ZipFormatException in project apksig by venshine.

the class ApkVerifier method verifySourceStamp.

/**
 * Verifies the provided {@code apk}'s source stamp signature, including verification of the
 * SHA-256 digest of the stamp signing certificate matches the {@code expectedCertDigest}, and
 * returns the result of the verification.
 *
 * @see #verifySourceStamp(String)
 */
private Result verifySourceStamp(DataSource apk, String expectedCertDigest) {
    try {
        ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
        int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections);
        // Attempt to obtain the source stamp's certificate digest from the APK.
        List<CentralDirectoryRecord> cdRecords = V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
        CentralDirectoryRecord sourceStampCdRecord = null;
        for (CentralDirectoryRecord cdRecord : cdRecords) {
            if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
                sourceStampCdRecord = cdRecord;
                break;
            }
        }
        // APK's signature block to determine the appropriate status to return.
        if (sourceStampCdRecord == null) {
            boolean stampSigningBlockFound;
            try {
                ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
                ApkSigningBlockUtils.findSignature(apk, zipSections, SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID, result);
                stampSigningBlockFound = true;
            } catch (ApkSigningBlockUtils.SignatureNotFoundException e) {
                stampSigningBlockFound = false;
            }
            if (stampSigningBlockFound) {
                return createSourceStampResultWithError(Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_NOT_VERIFIED, Issue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST);
            } else {
                return createSourceStampResultWithError(Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_MISSING, Issue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING);
            }
        }
        // Verify that the contents of the source stamp certificate digest match the expected
        // value, if provided.
        byte[] sourceStampCertificateDigest = LocalFileRecord.getUncompressedData(apk, sourceStampCdRecord, zipSections.getZipCentralDirectoryOffset());
        if (expectedCertDigest != null) {
            String actualCertDigest = ApkSigningBlockUtils.toHex(sourceStampCertificateDigest);
            if (!expectedCertDigest.equalsIgnoreCase(actualCertDigest)) {
                return createSourceStampResultWithError(Result.SourceStampInfo.SourceStampVerificationStatus.CERT_DIGEST_MISMATCH, Issue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH, actualCertDigest, expectedCertDigest);
            }
        }
        Map<Integer, Map<ContentDigestAlgorithm, byte[]>> signatureSchemeApkContentDigests = new HashMap<>();
        Map<Integer, String> supportedSchemeNames = getSupportedSchemeNames(mMaxSdkVersion);
        Set<Integer> foundApkSigSchemeIds = new HashSet<>(2);
        Result result = new Result();
        ApkSigningBlockUtils.Result v3Result = null;
        if (mMaxSdkVersion >= AndroidSdkVersion.P) {
            v3Result = getApkContentDigests(apk, zipSections, foundApkSigSchemeIds, supportedSchemeNames, signatureSchemeApkContentDigests, VERSION_APK_SIGNATURE_SCHEME_V3, Math.max(minSdkVersion, AndroidSdkVersion.P));
            if (v3Result != null && v3Result.containsErrors()) {
                result.mergeFrom(v3Result);
                return mergeSourceStampResult(Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, result);
            }
        }
        ApkSigningBlockUtils.Result v2Result = null;
        if (mMaxSdkVersion >= AndroidSdkVersion.N && (minSdkVersion < AndroidSdkVersion.P || foundApkSigSchemeIds.isEmpty())) {
            v2Result = getApkContentDigests(apk, zipSections, foundApkSigSchemeIds, supportedSchemeNames, signatureSchemeApkContentDigests, VERSION_APK_SIGNATURE_SCHEME_V2, Math.max(minSdkVersion, AndroidSdkVersion.N));
            if (v2Result != null && v2Result.containsErrors()) {
                result.mergeFrom(v2Result);
                return mergeSourceStampResult(Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, result);
            }
        }
        if (minSdkVersion < AndroidSdkVersion.N || foundApkSigSchemeIds.isEmpty()) {
            signatureSchemeApkContentDigests.put(VERSION_JAR_SIGNATURE_SCHEME, getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections));
        }
        ApkSigResult sourceStampResult = V2SourceStampVerifier.verify(apk, zipSections, sourceStampCertificateDigest, signatureSchemeApkContentDigests, minSdkVersion, mMaxSdkVersion);
        result.mergeFrom(sourceStampResult);
        // as verified if the source stamp verification was successful.
        if (sourceStampResult.verified) {
            result.setVerified();
        } else {
            // To prevent APK signature verification with a failed / missing source stamp the
            // source stamp verification will only log warnings; to allow the caller to capture
            // the failure reason treat all warnings as errors.
            result.setWarningsAsErrors(true);
        }
        return result;
    } catch (ApkFormatException | IOException | ZipFormatException e) {
        return createSourceStampResultWithError(Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, Issue.MALFORMED_APK, e);
    } catch (NoSuchAlgorithmException e) {
        return createSourceStampResultWithError(Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, Issue.UNEXPECTED_EXCEPTION, e);
    } catch (SignatureNotFoundException e) {
        return createSourceStampResultWithError(Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_NOT_VERIFIED, Issue.SOURCE_STAMP_SIG_MISSING);
    }
}
Also used : CentralDirectoryRecord(com.android.apksig.internal.zip.CentralDirectoryRecord) HashMap(java.util.HashMap) ApkSigResult(com.android.apksig.internal.apk.ApkSigResult) ZipFormatException(com.android.apksig.zip.ZipFormatException) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) ApkSigningBlockUtils(com.android.apksig.internal.apk.ApkSigningBlockUtils) ApkSigResult(com.android.apksig.internal.apk.ApkSigResult) ApkFormatException(com.android.apksig.apk.ApkFormatException) HashSet(java.util.HashSet) IOException(java.io.IOException) ApkUtils(com.android.apksig.apk.ApkUtils) SignatureNotFoundException(com.android.apksig.internal.apk.SignatureNotFoundException) Map(java.util.Map) EnumMap(java.util.EnumMap) HashMap(java.util.HashMap)

Aggregations

ZipFormatException (com.android.apksig.zip.ZipFormatException)17 ApkFormatException (com.android.apksig.apk.ApkFormatException)11 CentralDirectoryRecord (com.android.apksig.internal.zip.CentralDirectoryRecord)10 IOException (java.io.IOException)7 ByteBuffer (java.nio.ByteBuffer)7 ArrayList (java.util.ArrayList)7 ApkUtils (com.android.apksig.apk.ApkUtils)5 EnumMap (java.util.EnumMap)5 ApkSigResult (com.android.apksig.internal.apk.ApkSigResult)4 HashMap (java.util.HashMap)4 HashSet (java.util.HashSet)4 ApkSigningBlockUtils (com.android.apksig.internal.apk.ApkSigningBlockUtils)3 ContentDigestAlgorithm (com.android.apksig.internal.apk.ContentDigestAlgorithm)3 SignatureNotFoundException (com.android.apksig.internal.apk.SignatureNotFoundException)3 Map (java.util.Map)3 SignatureInfo (com.android.apksig.internal.apk.SignatureInfo)2 DataSource (com.android.apksig.util.DataSource)2 ZipSections (com.android.apksig.zip.ZipSections)2 NoSuchAlgorithmException (java.security.NoSuchAlgorithmException)2 X509Certificate (java.security.cert.X509Certificate)2