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;
}
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);
}
}
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);
}
}
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;
}
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);
}
Aggregations