Search in sources :

Example 1 with ZipFormatException

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

the class SourceStampVerifier 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 SourceStampVerifier.Result verifySourceStamp(DataSource apk, String expectedCertDigest) {
    Result result = new Result();
    try {
        ZipSections zipSections = ApkUtilsLite.findZipSections(apk);
        // Attempt to obtain the source stamp's certificate digest from the APK.
        List<CentralDirectoryRecord> cdRecords = ZipUtils.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 {
                ApkSigningBlockUtilsLite.findSignature(apk, zipSections, SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID);
                stampSigningBlockFound = true;
            } catch (SignatureNotFoundException e) {
                stampSigningBlockFound = false;
            }
            result.addVerificationError(stampSigningBlockFound ? ApkVerificationIssue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST : ApkVerificationIssue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING);
            return result;
        }
        // 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 = ApkSigningBlockUtilsLite.toHex(sourceStampCertificateDigest);
            if (!expectedCertDigest.equalsIgnoreCase(actualCertDigest)) {
                result.addVerificationError(ApkVerificationIssue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH, actualCertDigest, expectedCertDigest);
                return result;
            }
        }
        Map<Integer, Map<ContentDigestAlgorithm, byte[]>> signatureSchemeApkContentDigests = new HashMap<>();
        if (mMaxSdkVersion >= AndroidSdkVersion.P) {
            SignatureInfo signatureInfo;
            try {
                signatureInfo = ApkSigningBlockUtilsLite.findSignature(apk, zipSections, V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
            } catch (SignatureNotFoundException e) {
                signatureInfo = null;
            }
            if (signatureInfo != null) {
                Map<ContentDigestAlgorithm, byte[]> apkContentDigests = new EnumMap<>(ContentDigestAlgorithm.class);
                parseSigners(signatureInfo.signatureBlock, VERSION_APK_SIGNATURE_SCHEME_V3, apkContentDigests, result);
                signatureSchemeApkContentDigests.put(VERSION_APK_SIGNATURE_SCHEME_V3, apkContentDigests);
            }
        }
        if (mMaxSdkVersion >= AndroidSdkVersion.N && (mMinSdkVersion < AndroidSdkVersion.P || signatureSchemeApkContentDigests.isEmpty())) {
            SignatureInfo signatureInfo;
            try {
                signatureInfo = ApkSigningBlockUtilsLite.findSignature(apk, zipSections, V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
            } catch (SignatureNotFoundException e) {
                signatureInfo = null;
            }
            if (signatureInfo != null) {
                Map<ContentDigestAlgorithm, byte[]> apkContentDigests = new EnumMap<>(ContentDigestAlgorithm.class);
                parseSigners(signatureInfo.signatureBlock, VERSION_APK_SIGNATURE_SCHEME_V2, apkContentDigests, result);
                signatureSchemeApkContentDigests.put(VERSION_APK_SIGNATURE_SCHEME_V2, apkContentDigests);
            }
        }
        if (mMinSdkVersion < AndroidSdkVersion.N || signatureSchemeApkContentDigests.isEmpty()) {
            Map<ContentDigestAlgorithm, byte[]> apkContentDigests = getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections, result);
            signatureSchemeApkContentDigests.put(VERSION_JAR_SIGNATURE_SCHEME, apkContentDigests);
        }
        ApkSigResult sourceStampResult = V2SourceStampVerifier.verify(apk, zipSections, sourceStampCertificateDigest, signatureSchemeApkContentDigests, mMinSdkVersion, mMaxSdkVersion);
        result.mergeFrom(sourceStampResult);
        return result;
    } catch (ApkFormatException | IOException | ZipFormatException e) {
        result.addVerificationError(ApkVerificationIssue.MALFORMED_APK, e);
    } catch (NoSuchAlgorithmException e) {
        result.addVerificationError(ApkVerificationIssue.UNEXPECTED_EXCEPTION, e);
    } catch (SignatureNotFoundException e) {
        result.addVerificationError(ApkVerificationIssue.SOURCE_STAMP_SIG_MISSING);
    }
    return result;
}
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) IOException(java.io.IOException) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) ApkSigResult(com.android.apksig.internal.apk.ApkSigResult) SignatureInfo(com.android.apksig.internal.apk.SignatureInfo) ApkFormatException(com.android.apksig.apk.ApkFormatException) SignatureNotFoundException(com.android.apksig.internal.apk.SignatureNotFoundException) ContentDigestAlgorithm(com.android.apksig.internal.apk.ContentDigestAlgorithm) HashMap(java.util.HashMap) Map(java.util.Map) EnumMap(java.util.EnumMap) EnumMap(java.util.EnumMap) ZipSections(com.android.apksig.zip.ZipSections)

Example 2 with ZipFormatException

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

the class SourceStampVerifier method getApkContentDigestFromV1SigningScheme.

/**
 * Returns a mapping of the {@link ContentDigestAlgorithm} to the {@code byte[]} digest of the
 * V1 / jar signing META-INF/MANIFEST.MF; if this file is not found then an empty {@code Map} is
 * returned.
 *
 * <p>If any errors are encountered while parsing the V1 signers the provided {@code result}
 * will be updated to include a warning, but the source stamp verification can still proceed.
 */
private static Map<ContentDigestAlgorithm, byte[]> getApkContentDigestFromV1SigningScheme(List<CentralDirectoryRecord> cdRecords, DataSource apk, ZipSections zipSections, Result result) throws IOException, ApkFormatException {
    CentralDirectoryRecord manifestCdRecord = null;
    List<CentralDirectoryRecord> signatureBlockRecords = new ArrayList<>(1);
    Map<ContentDigestAlgorithm, byte[]> v1ContentDigest = new EnumMap<>(ContentDigestAlgorithm.class);
    for (CentralDirectoryRecord cdRecord : cdRecords) {
        String cdRecordName = cdRecord.getName();
        if (cdRecordName == null) {
            continue;
        }
        if (manifestCdRecord == null && MANIFEST_ENTRY_NAME.equals(cdRecordName)) {
            manifestCdRecord = cdRecord;
            continue;
        }
        if (cdRecordName.startsWith("META-INF/") && (cdRecordName.endsWith(".RSA") || cdRecordName.endsWith(".DSA") || cdRecordName.endsWith(".EC"))) {
            signatureBlockRecords.add(cdRecord);
        }
    }
    if (manifestCdRecord == null) {
        // thus an empty digest will invalidate that signature.
        return v1ContentDigest;
    }
    if (signatureBlockRecords.isEmpty()) {
        result.addVerificationWarning(ApkVerificationIssue.JAR_SIG_NO_SIGNATURES);
    } else {
        for (CentralDirectoryRecord signatureBlockRecord : signatureBlockRecords) {
            try {
                CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
                byte[] signatureBlockBytes = LocalFileRecord.getUncompressedData(apk, signatureBlockRecord, zipSections.getZipCentralDirectoryOffset());
                for (Certificate certificate : certFactory.generateCertificates(new ByteArrayInputStream(signatureBlockBytes))) {
                    // first is used as the signer of this block.
                    if (certificate instanceof X509Certificate) {
                        Result.SignerInfo signerInfo = new Result.SignerInfo();
                        signerInfo.setSigningCertificate((X509Certificate) certificate);
                        result.addV1Signer(signerInfo);
                        break;
                    }
                }
            } catch (CertificateException e) {
                // Log a warning for the parsing exception but still proceed with the stamp
                // verification.
                result.addVerificationWarning(ApkVerificationIssue.JAR_SIG_PARSE_EXCEPTION, signatureBlockRecord.getName(), e);
                break;
            } catch (ZipFormatException e) {
                throw new ApkFormatException("Failed to read APK", e);
            }
        }
    }
    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) ArrayList(java.util.ArrayList) CertificateException(java.security.cert.CertificateException) ZipFormatException(com.android.apksig.zip.ZipFormatException) CertificateFactory(java.security.cert.CertificateFactory) X509Certificate(java.security.cert.X509Certificate) GuaranteedEncodedFormX509Certificate(com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate) ApkSigResult(com.android.apksig.internal.apk.ApkSigResult) ApkSignerInfo(com.android.apksig.internal.apk.ApkSignerInfo) ByteArrayInputStream(java.io.ByteArrayInputStream) ApkFormatException(com.android.apksig.apk.ApkFormatException) ContentDigestAlgorithm(com.android.apksig.internal.apk.ContentDigestAlgorithm) EnumMap(java.util.EnumMap) X509Certificate(java.security.cert.X509Certificate) GuaranteedEncodedFormX509Certificate(com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate) Certificate(java.security.cert.Certificate)

Example 3 with ZipFormatException

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

the class ApkUtils method getAndroidManifest.

/**
 * Returns the contents of the APK's {@code AndroidManifest.xml}.
 *
 * @throws IOException if an I/O error occurs while reading the APK
 * @throws ApkFormatException if the APK is malformed
 */
public static ByteBuffer getAndroidManifest(DataSource apk) throws IOException, ApkFormatException {
    ZipSections zipSections;
    try {
        zipSections = findZipSections(apk);
    } catch (ZipFormatException e) {
        throw new ApkFormatException("Not a valid ZIP archive", e);
    }
    List<CentralDirectoryRecord> cdRecords = V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
    CentralDirectoryRecord androidManifestCdRecord = null;
    for (CentralDirectoryRecord cdRecord : cdRecords) {
        if (ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
            androidManifestCdRecord = cdRecord;
            break;
        }
    }
    if (androidManifestCdRecord == null) {
        throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME);
    }
    DataSource lfhSection = apk.slice(0, zipSections.getZipCentralDirectoryOffset());
    try {
        return ByteBuffer.wrap(LocalFileRecord.getUncompressedData(lfhSection, androidManifestCdRecord, lfhSection.size()));
    } catch (ZipFormatException e) {
        throw new ApkFormatException("Failed to read " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, e);
    }
}
Also used : CentralDirectoryRecord(com.android.apksig.internal.zip.CentralDirectoryRecord) ZipFormatException(com.android.apksig.zip.ZipFormatException) DataSource(com.android.apksig.util.DataSource)

Example 4 with ZipFormatException

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

the class ApkSigner method extractPinPatterns.

/**
 * Return list of pin patterns embedded in the pin pattern asset file. If no such file, return
 * {@code null}.
 */
private static List<Hints.PatternWithRange> extractPinPatterns(List<CentralDirectoryRecord> cdRecords, DataSource lhfSection) throws IOException, ApkFormatException {
    CentralDirectoryRecord pinListCdRecord = findCdRecord(cdRecords, Hints.PIN_HINT_ASSET_ZIP_ENTRY_NAME);
    List<Hints.PatternWithRange> pinPatterns = null;
    if (pinListCdRecord != null) {
        pinPatterns = new ArrayList<>();
        byte[] patternBlob;
        try {
            patternBlob = LocalFileRecord.getUncompressedData(lhfSection, pinListCdRecord, lhfSection.size());
        } catch (ZipFormatException ex) {
            throw new ApkFormatException("Bad " + pinListCdRecord);
        }
        pinPatterns = Hints.parsePinPatterns(patternBlob);
    }
    return pinPatterns;
}
Also used : CentralDirectoryRecord(com.android.apksig.internal.zip.CentralDirectoryRecord) ApkFormatException(com.android.apksig.apk.ApkFormatException) ZipFormatException(com.android.apksig.zip.ZipFormatException)

Example 5 with ZipFormatException

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

the class LocalFileRecord method getRecord.

/**
 * Returns the Local File record starting at the current position of the provided buffer
 * and advances the buffer's position immediately past the end of the record. The record
 * consists of the Local File Header, data, and (if present) Data Descriptor.
 */
private static LocalFileRecord getRecord(DataSource apk, CentralDirectoryRecord cdRecord, long cdStartOffset, boolean extraFieldContentsNeeded, boolean dataDescriptorIncluded) throws ZipFormatException, IOException {
    // IMPLEMENTATION NOTE: This method attempts to mimic the behavior of Android platform
    // exhibited when reading an APK for the purposes of verifying its signatures.
    String entryName = cdRecord.getName();
    int cdRecordEntryNameSizeBytes = cdRecord.getNameSizeBytes();
    int headerSizeWithName = HEADER_SIZE_BYTES + cdRecordEntryNameSizeBytes;
    long headerStartOffset = cdRecord.getLocalFileHeaderOffset();
    long headerEndOffset = headerStartOffset + headerSizeWithName;
    if (headerEndOffset > cdStartOffset) {
        throw new ZipFormatException("Local File Header of " + entryName + " extends beyond start of Central" + " Directory. LFH end: " + headerEndOffset + ", CD start: " + cdStartOffset);
    }
    ByteBuffer header;
    try {
        header = apk.getByteBuffer(headerStartOffset, headerSizeWithName);
    } catch (IOException e) {
        throw new IOException("Failed to read Local File Header of " + entryName, e);
    }
    header.order(ByteOrder.LITTLE_ENDIAN);
    int recordSignature = header.getInt();
    if (recordSignature != RECORD_SIGNATURE) {
        throw new ZipFormatException("Not a Local File Header record for entry " + entryName + ". Signature: 0x" + Long.toHexString(recordSignature & 0xffffffffL));
    }
    short gpFlags = header.getShort(GP_FLAGS_OFFSET);
    boolean dataDescriptorUsed = (gpFlags & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0;
    boolean cdDataDescriptorUsed = (cdRecord.getGpFlags() & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0;
    if (dataDescriptorUsed != cdDataDescriptorUsed) {
        throw new ZipFormatException("Data Descriptor presence mismatch between Local File Header and Central" + " Directory for entry " + entryName + ". LFH: " + dataDescriptorUsed + ", CD: " + cdDataDescriptorUsed);
    }
    long uncompressedDataCrc32FromCdRecord = cdRecord.getCrc32();
    long compressedDataSizeFromCdRecord = cdRecord.getCompressedSize();
    long uncompressedDataSizeFromCdRecord = cdRecord.getUncompressedSize();
    if (!dataDescriptorUsed) {
        long crc32 = ZipUtils.getUnsignedInt32(header, CRC32_OFFSET);
        if (crc32 != uncompressedDataCrc32FromCdRecord) {
            throw new ZipFormatException("CRC-32 mismatch between Local File Header and Central Directory for entry " + entryName + ". LFH: " + crc32 + ", CD: " + uncompressedDataCrc32FromCdRecord);
        }
        long compressedSize = ZipUtils.getUnsignedInt32(header, COMPRESSED_SIZE_OFFSET);
        if (compressedSize != compressedDataSizeFromCdRecord) {
            throw new ZipFormatException("Compressed size mismatch between Local File Header and Central Directory" + " for entry " + entryName + ". LFH: " + compressedSize + ", CD: " + compressedDataSizeFromCdRecord);
        }
        long uncompressedSize = ZipUtils.getUnsignedInt32(header, UNCOMPRESSED_SIZE_OFFSET);
        if (uncompressedSize != uncompressedDataSizeFromCdRecord) {
            throw new ZipFormatException("Uncompressed size mismatch between Local File Header and Central Directory" + " for entry " + entryName + ". LFH: " + uncompressedSize + ", CD: " + uncompressedDataSizeFromCdRecord);
        }
    }
    int nameLength = ZipUtils.getUnsignedInt16(header, NAME_LENGTH_OFFSET);
    if (nameLength > cdRecordEntryNameSizeBytes) {
        throw new ZipFormatException("Name mismatch between Local File Header and Central Directory for entry" + entryName + ". LFH: " + nameLength + " bytes, CD: " + cdRecordEntryNameSizeBytes + " bytes");
    }
    String name = CentralDirectoryRecord.getName(header, NAME_OFFSET, nameLength);
    if (!entryName.equals(name)) {
        throw new ZipFormatException("Name mismatch between Local File Header and Central Directory. LFH: \"" + name + "\", CD: \"" + entryName + "\"");
    }
    int extraLength = ZipUtils.getUnsignedInt16(header, EXTRA_LENGTH_OFFSET);
    long dataStartOffset = headerStartOffset + HEADER_SIZE_BYTES + nameLength + extraLength;
    long dataSize;
    boolean compressed = (cdRecord.getCompressionMethod() != ZipUtils.COMPRESSION_METHOD_STORED);
    if (compressed) {
        dataSize = compressedDataSizeFromCdRecord;
    } else {
        dataSize = uncompressedDataSizeFromCdRecord;
    }
    long dataEndOffset = dataStartOffset + dataSize;
    if (dataEndOffset > cdStartOffset) {
        throw new ZipFormatException("Local File Header data of " + entryName + " overlaps with Central Directory" + ". LFH data start: " + dataStartOffset + ", LFH data end: " + dataEndOffset + ", CD start: " + cdStartOffset);
    }
    ByteBuffer extra = EMPTY_BYTE_BUFFER;
    if ((extraFieldContentsNeeded) && (extraLength > 0)) {
        extra = apk.getByteBuffer(headerStartOffset + HEADER_SIZE_BYTES + nameLength, extraLength);
    }
    long recordEndOffset = dataEndOffset;
    // Include the Data Descriptor (if requested and present) into the record.
    if ((dataDescriptorIncluded) && ((gpFlags & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0)) {
        // The record's data is supposed to be followed by the Data Descriptor. Unfortunately,
        // the descriptor's size is not known in advance because the spec lets the signature
        // field (the first four bytes) be omitted. Thus, there's no 100% reliable way to tell
        // how long the Data Descriptor record is. Most parsers (including Android) check
        // whether the first four bytes look like Data Descriptor record signature and, if so,
        // assume that it is indeed the record's signature. However, this is the wrong
        // conclusion if the record's CRC-32 (next field after the signature) has the same value
        // as the signature. In any case, we're doing what Android is doing.
        long dataDescriptorEndOffset = dataEndOffset + DATA_DESCRIPTOR_SIZE_BYTES_WITHOUT_SIGNATURE;
        if (dataDescriptorEndOffset > cdStartOffset) {
            throw new ZipFormatException("Data Descriptor of " + entryName + " overlaps with Central Directory" + ". Data Descriptor end: " + dataEndOffset + ", CD start: " + cdStartOffset);
        }
        ByteBuffer dataDescriptorPotentialSig = apk.getByteBuffer(dataEndOffset, 4);
        dataDescriptorPotentialSig.order(ByteOrder.LITTLE_ENDIAN);
        if (dataDescriptorPotentialSig.getInt() == DATA_DESCRIPTOR_SIGNATURE) {
            dataDescriptorEndOffset += 4;
            if (dataDescriptorEndOffset > cdStartOffset) {
                throw new ZipFormatException("Data Descriptor of " + entryName + " overlaps with Central Directory" + ". Data Descriptor end: " + dataEndOffset + ", CD start: " + cdStartOffset);
            }
        }
        recordEndOffset = dataDescriptorEndOffset;
    }
    long recordSize = recordEndOffset - headerStartOffset;
    int dataStartOffsetInRecord = HEADER_SIZE_BYTES + nameLength + extraLength;
    return new LocalFileRecord(entryName, cdRecordEntryNameSizeBytes, extra, headerStartOffset, recordSize, dataStartOffsetInRecord, dataSize, compressed, uncompressedDataSizeFromCdRecord);
}
Also used : ZipFormatException(com.android.apksig.zip.ZipFormatException) IOException(java.io.IOException) ByteBuffer(java.nio.ByteBuffer)

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