package com.leon.channel.common; import com.leon.channel.common.verify.ApkSignatureSchemeV2Verifier; import com.leon.channel.common.verify.ZipUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.LinkedHashMap; import java.util.Map; /** * Created by leontli on 17/1/18. */ public class V2SchemeUtil { /** * find all Id-Value Pair from ApkSignatureBlock * 参考ApkSignatureSchemeV2Verifier.findApkSignatureSchemeV2Block()方法 * * @param apkSchemeBlock * @return * @throws ApkSignatureSchemeV2Verifier.SignatureNotFoundException */ public static Map<Integer, ByteBuffer> getAllIdValue(ByteBuffer apkSchemeBlock) throws ApkSignatureSchemeV2Verifier.SignatureNotFoundException { ApkSignatureSchemeV2Verifier.checkByteOrderLittleEndian(apkSchemeBlock); // FORMAT: // OFFSET DATA TYPE DESCRIPTION // * @+0 bytes uint64: size in bytes (excluding this field) // * @+8 bytes pairs // * @-24 bytes uint64: size in bytes (same as the one above) // * @-16 bytes uint128: magic ByteBuffer pairs = ApkSignatureSchemeV2Verifier.sliceFromTo(apkSchemeBlock, 8, apkSchemeBlock.capacity() - 24); Map<Integer, ByteBuffer> idValues = new LinkedHashMap<Integer, ByteBuffer>(); // keep order int entryCount = 0; while (pairs.hasRemaining()) { entryCount++; if (pairs.remaining() < 8) { throw new ApkSignatureSchemeV2Verifier.SignatureNotFoundException( "Insufficient data to read size of APK Signing Block entry #" + entryCount); } long lenLong = pairs.getLong(); if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { throw new ApkSignatureSchemeV2Verifier.SignatureNotFoundException( "APK Signing Block entry #" + entryCount + " size out of range: " + lenLong); } int len = (int) lenLong; int nextEntryPos = pairs.position() + len; if (len > pairs.remaining()) { throw new ApkSignatureSchemeV2Verifier.SignatureNotFoundException( "APK Signing Block entry #" + entryCount + " size out of range: " + len + ", available: " + pairs.remaining()); } int id = pairs.getInt(); idValues.put(id, ApkSignatureSchemeV2Verifier.getByteBuffer(pairs, len - 4));//4 is length of id if (id == ApkSignatureSchemeV2Verifier.APK_SIGNATURE_SCHEME_V2_BLOCK_ID) { System.out.println("find V2 signature block Id : " + ApkSignatureSchemeV2Verifier.APK_SIGNATURE_SCHEME_V2_BLOCK_ID); } pairs.position(nextEntryPos); } if (idValues.isEmpty()) { throw new ApkSignatureSchemeV2Verifier.SignatureNotFoundException( "not have Id-Value Pair in APK Signing Block entry #" + entryCount); } return idValues; } /** * get apk signature block from apk * * @param channelFile * @return * @throws IOException * @throws ApkSignatureSchemeV2Verifier.SignatureNotFoundException */ public static ByteBuffer getApkSigningBlock(File channelFile) throws ApkSignatureSchemeV2Verifier.SignatureNotFoundException, IOException { if (channelFile == null || !channelFile.exists() || !channelFile.isFile()) { return null; } RandomAccessFile apk = new RandomAccessFile(channelFile, "r"); //1.find the EOCD Pair<ByteBuffer, Long> eocdAndOffsetInFile = ApkSignatureSchemeV2Verifier.getEocd(apk); ByteBuffer eocd = eocdAndOffsetInFile.getFirst(); long eocdOffset = eocdAndOffsetInFile.getSecond(); if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { throw new ApkSignatureSchemeV2Verifier.SignatureNotFoundException("ZIP64 APK not supported"); } //2.find the APK Signing Block. The block immediately precedes the Central Directory. long centralDirOffset = ApkSignatureSchemeV2Verifier.getCentralDirOffset(eocd, eocdOffset);//通过eocd找到中央目录的偏移量 //3. find the apk V2 signature block Pair<ByteBuffer, Long> apkSignatureBlock = ApkSignatureSchemeV2Verifier.findApkSigningBlock(apk, centralDirOffset);//找到V2签名块的内容和偏移量 return apkSignatureBlock.getFirst(); } /** * get the all Apk Section info from apk which is signatured by v2 * * @param baseApk * @return * @throws IOException * @throws ApkSignatureSchemeV2Verifier.SignatureNotFoundException not have v2 sinature */ public static ApkSectionInfo getApkSectionInfo(File baseApk) throws IOException, ApkSignatureSchemeV2Verifier.SignatureNotFoundException { RandomAccessFile apk = new RandomAccessFile(baseApk, "r"); //1.find the EOCD and offset Pair<ByteBuffer, Long> eocdAndOffsetInFile = ApkSignatureSchemeV2Verifier.getEocd(apk); ByteBuffer eocd = eocdAndOffsetInFile.getFirst(); long eocdOffset = eocdAndOffsetInFile.getSecond(); if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { throw new ApkSignatureSchemeV2Verifier.SignatureNotFoundException("ZIP64 APK not supported"); } //2.find the APK Signing Block. The block immediately precedes the Central Directory. long centralDirOffset = ApkSignatureSchemeV2Verifier.getCentralDirOffset(eocd, eocdOffset);//通过eocd找到中央目录的偏移量 Pair<ByteBuffer, Long> apkSchemeV2Block = ApkSignatureSchemeV2Verifier.findApkSigningBlock(apk, centralDirOffset);//找到V2签名块的内容和偏移量 //3.find the centralDir Pair<ByteBuffer, Long> centralDir = findCentralDir(apk, centralDirOffset, (int) (eocdOffset - centralDirOffset)); //4.find the contentEntry Pair<ByteBuffer, Long> contentEntry = findContentEntry(apk, (int) apkSchemeV2Block.getSecond().longValue()); ApkSectionInfo apkSectionInfo = new ApkSectionInfo(); apkSectionInfo.mContentEntry = contentEntry; apkSectionInfo.mSchemeV2Block = apkSchemeV2Block; apkSectionInfo.mCentralDir = centralDir; apkSectionInfo.mEocd = eocdAndOffsetInFile; System.out.println("baseApk : " + baseApk.getAbsolutePath() + " , ApkSectionInfo = " + apkSectionInfo); return apkSectionInfo; } /** * get the CentralDir of apk * * @param baseApk * @param centralDirOffset * @param length * @return * @throws IOException */ public static Pair<ByteBuffer, Long> findCentralDir(RandomAccessFile baseApk, long centralDirOffset, int length) throws IOException { ByteBuffer byteBuffer = getByteBuffer(baseApk, centralDirOffset, length); return Pair.create(byteBuffer, centralDirOffset); } /** * get the ContentEntry of apk * * @param baseApk * @param length * @return * @throws IOException */ public static Pair<ByteBuffer, Long> findContentEntry(RandomAccessFile baseApk, int length) throws IOException { ByteBuffer byteBuffer = getByteBuffer(baseApk, 0, length); return Pair.create(byteBuffer, 0L); } private static ByteBuffer getByteBuffer(RandomAccessFile baseApk, long offset, int length) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocate(length); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); baseApk.seek(offset); baseApk.readFully(byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.capacity()); return byteBuffer; } /** * generate the new ApkSigningBlock(contain v2 schema block) * reference ApkSignerV2.generateApkSigningBlock * * @param idValueMap * @return */ public static ByteBuffer generateApkSigningBlock(Map<Integer, ByteBuffer> idValueMap) { if (idValueMap == null || idValueMap.isEmpty()) { throw new RuntimeException("getNewApkV2SchemeBlock , id value pair is empty"); } // FORMAT: // uint64: size (excluding this field) // repeated ID-value pairs: // uint64: size (excluding this field) // uint32: ID // (size - 4) bytes: value // uint64: size (same as the one above) // uint128: magic long length = 16 + 8;//length is size (excluding this field) , 24 = 16 byte (magic) + 8 byte (length of the signing block excluding first 8 byte) for (Map.Entry<Integer, ByteBuffer> entry : idValueMap.entrySet()) { ByteBuffer byteBuffer = entry.getValue(); length += 8 + 4 + (byteBuffer.remaining()); } ByteBuffer newApkV2Scheme = ByteBuffer.allocate((int) (length + 8)); newApkV2Scheme.order(ByteOrder.LITTLE_ENDIAN); newApkV2Scheme.putLong(length);//1.write size (excluding this field) for (Map.Entry<Integer, ByteBuffer> entry : idValueMap.entrySet()) { ByteBuffer byteBuffer = entry.getValue(); //2.1 write length of id-value newApkV2Scheme.putLong(byteBuffer.remaining() + 4);//4 is length of id //2.2 write id newApkV2Scheme.putInt(entry.getKey()); //2.3 write value newApkV2Scheme.put(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining()); } newApkV2Scheme.putLong(length);//3.write size (same as the one above) newApkV2Scheme.putLong(ApkSignatureSchemeV2Verifier.APK_SIG_BLOCK_MAGIC_LO);//4. write magic newApkV2Scheme.putLong(ApkSignatureSchemeV2Verifier.APK_SIG_BLOCK_MAGIC_HI);//4. write magic if (newApkV2Scheme.remaining() > 0) { throw new RuntimeException("generateNewApkV2SchemeBlock error"); } newApkV2Scheme.flip(); return newApkV2Scheme; } /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. * <p> * NOTE: This method does not verify the signature.</b> * * @param apkPath * @return * @throws Exception */ public static boolean verifyChannelApk(String apkPath) throws Exception { return ApkSignatureSchemeV2Verifier.hasSignature(apkPath); } /** * judge whether apk contain v2 signature block * * @param apk * @return */ public static boolean containV2Signature(File apk) { try { ByteBuffer apkSigningBlock = getApkSigningBlock(apk); Map<Integer, ByteBuffer> idValueMap = getAllIdValue(apkSigningBlock); if (idValueMap.containsKey(ApkSignatureSchemeV2Verifier.APK_SIGNATURE_SCHEME_V2_BLOCK_ID)) { return true; } } catch (IOException e) { e.printStackTrace(); } catch (ApkSignatureSchemeV2Verifier.SignatureNotFoundException e) { e.printStackTrace(); } return false; } }