diff --git a/CheckAndroidSignatureByAPKSig/README.md b/CheckAndroidSignatureByAPKSig/README.md index 328d999..08b2370 100644 --- a/CheckAndroidSignatureByAPKSig/README.md +++ b/CheckAndroidSignatureByAPKSig/README.md @@ -6,23 +6,23 @@ #### 查看帮助 - ➜ java -jar ./CheckAndroidV2SignatureByAPKSig.jar + ➜ java -jar ./CheckAndroidSignature.jar usage: - java -jar ./CheckAndroidV2SignatureByAPKSig.jar [filePath] - java -jar ./CheckAndroidV2SignatureByAPKSig.jar --version - java -jar ./CheckAndroidV2SignatureByAPKSig.jar --help + java -jar ./CheckAndroidSignature.jar [filePath] + java -jar ./CheckAndroidSignature.jar --version + java -jar ./CheckAndroidSignature.jar --help such as: - java -jar ./CheckAndroidV2SignatureByAPKSig.jar ./test.apk - java -jar ./CheckAndroidV2SignatureByAPKSig.jar --version - java -jar ./CheckAndroidV2SignatureByAPKSig.jar --help + java -jar ./CheckAndroidSignature.jar ./test.apk + java -jar ./CheckAndroidSignature.jar --version + java -jar ./CheckAndroidSignature.jar --help after check,the result will be a string json such as: - {"ret":0,"msg":"ok","isV1OK":true,,"isV2":true"isV2OK":true} + {"ret":0,"msg":"","isV1OK":true,"isV2":false,"isV2OK":false,"keystoreMd5":"252e3ded833125ed3e3bb010bc24f4dc"} ret: result code for check @@ -39,16 +39,16 @@ #### 查看版本 - ➜ java -jar ./CheckAndroidV2SignatureByAPKSig.jar --version -com.tencent.ysdk.CheckAndroidV2Signature version 1.1.0 (CheckAndroidV2Signature - 4) + ➜ java -jar ./CheckAndroidSignature.jar --version -homepage : https://github.com/bihe0832/AndroidGetAPKInfo -blog : http://blog.bihe0832.com -github : https://github.com/bihe0832 + com.bihe0832.CheckAndroidSignature version 2.0 (CheckAndroidSignature - 6) + + homepage : https://github.com/bihe0832/AndroidGetAPKInfo + blog : http://blog.bihe0832.com + github : https://github.com/bihe0832 #### 查看应用信息 - ➜ java -jar ./CheckAndroidV2SignatureByAPKSig.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk - {"ret":0,"msg":"","isV1OK":true,"isV2":false,"isV2OK":false,"keystoreMd5":"252e3ded833125ed3e3bb010bc24f4dc"} - + ➜ java -jar ./CheckAndroidSignature.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk + {"ret":0,"msg":"","isV1OK":false,"isV2":true,"isV2OK":true,"isV3":true,"isV3OK":true,"keystoreMd5":"80fa5a8552e418f6bd805c65bcddf4c8"} diff --git a/CheckAndroidV2Signature/.gitignore b/CheckAndroidV2Signature/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/CheckAndroidV2Signature/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/CheckAndroidV2Signature/README.md b/CheckAndroidV2Signature/README.md deleted file mode 100644 index 98f991b..0000000 --- a/CheckAndroidV2Signature/README.md +++ /dev/null @@ -1,173 +0,0 @@ -## 背景 - -### APK Signature Scheme v2官方介绍 - -Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。 - -如果您使用 APK Signature Scheme v2 签署您的应用,并对应用进行了进一步更改,则应用的签名将无效。出于这个原因,请在使用 APK Signature Scheme v2 签署您的应用之前、而非之后使用 zipalign 等工具。 - -**关于Android的APK Signature Scheme v2签名相关的资料汇总**: -[http://blog.bihe0832.com/android-v2.html](http://blog.bihe0832.com/android-v2.html) - -**官方关于v2的详细介绍:[https://source.android.com/security/apksigning/v2.html](https://source.android.com/security/apksigning/v2.html)** - -**个人关于V2签名以及V2签名引起的渠道打包失败分析的介绍:[http://blog.bihe0832.com/android-v2-signature.html](http://blog.bihe0832.com/android-v2-signature.html)** - -### 特别说明 - -**这几天得到官方jarsiger的开发者Alex指点,发现其实官方已经提供了相关的打包工具以及检测方法,在此一并附上,后续再补充比较详细的介绍**。 - -#### 官方打包或者检查工具 - -- 工具位置:Android SDK包中`build-tools//apksigner` -- 支持版本:Android SDK Build Tools 24.0.3及以上 -- 对应源码: - - - 官方地址:[https://android.googlesource.com/platform/tools/apksig](https://android.googlesource.com/platform/tools/apksig) - - 个人github:项目根目录的apksig目录 -- 使用方法: - - ➜ $ANDROID_HOME/build-tools/24.0.3/apksigner - USAGE: apksigner [options] - apksigner --version - apksigner --help - - EXAMPLE: - apksigner sign --ks release.jks app.apk - apksigner verify --verbose app.apk - - apksigner is a tool for signing Android APK files and for checking whether - signatures of APK files will verify on Android devices. - - - COMMANDS - - sign Sign the provided APK - - verify Check whether the provided APK is expected to verify on - Android - - version Show this tool's version number and exit - - help Show this usage page and exit - - -## 代码调整 - -总体上是对Android的源码的移植,没有太多调整。主要调整的部分就是在`feedIntoMessageDigests `函数中计算md5的时候,为了提升效率,源码使用内存映射的方式,源码中是直接内存映射,代码迁移的时候调整为调用Java系统函数来完成内存映射。对应代码如下: - - -- AOSP: - - @Override - public void feedIntoMessageDigests( - MessageDigest[] mds, long offset, int size) throws IOException { - // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this - // method was settled on a straightforward mmap with prefaulting. - // - // This method is not using FileChannel.map API because that API does not offset a way - // to "prefault" the resulting memory pages. Without prefaulting, performance is about - // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB - // range. FileChannel.load (which currently uses madvise) doesn't help. Finally, - // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of - // time, which is not compensated for by faster reads. - - // We mmap the smallest region of the file containing the requested data. mmap requires - // that the start offset in the file must be a multiple of memory page size. We thus may - // need to mmap from an offset less than the requested offset. - long filePosition = mFilePosition + offset; - long mmapFilePosition = - (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES; - int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition); - long mmapRegionSize = size + dataStartOffsetInMmapRegion; - long mmapPtr = 0; - try { - mmapPtr = OS.mmap( - 0, // let the OS choose the start address of the region in memory - mmapRegionSize, - OsConstants.PROT_READ, - OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages - mFd, - mmapFilePosition); - // Feeding a memory region into MessageDigest requires the region to be represented - // as a direct ByteBuffer. - ByteBuffer buf = new DirectByteBuffer( - size, - mmapPtr + dataStartOffsetInMmapRegion, - mFd, // not really needed, but just in case - null, // no need to clean up -- it's taken care of by the finally block - true // read only buffer - ); - for (MessageDigest md : mds) { - buf.position(0); - md.update(buf); - } - } catch (ErrnoException e) { - throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e); - } finally { - if (mmapPtr != 0) { - try { - OS.munmap(mmapPtr, mmapRegionSize); - } catch (ErrnoException ignored) {} - } - } - } - -- 修改后 - - - @Override - public void feedIntoMessageDigests(FileChannel channel, - MessageDigest[] mds, long offset, int size) throws IOException { - long filePosition = mFilePosition + offset; - MappedByteBuffer inputBuffer = channel.map(FileChannel.MapMode.READ_ONLY, filePosition, size);// 读取大文件 - for (MessageDigest md : mds) { - inputBuffer.position(0); - md.update(inputBuffer); - } - } - -## 使用事例 - -#### 查看帮助 - - ➜ java -jar CheckAndroidV2Signature.jar - - usage: java -jar ./CheckAndroidV2Signature.jar [--version] [--help] [filePath] - - such as: - - java -jar ./CheckAndroidV2Signature.jar --version - java -jar ./CheckAndroidV2Signature.jar --help - java -jar ./CheckAndroidV2Signature.jar ./test.apk - - after check,the result will be a string json such as: - - {"ret":0,"msg":"ok","isV2":true,"isV2OK":true} - - ret: result code for check - - 0 : command exec succ - -1 : file not found - -2 : file not an Android APK file - -3 : check File signature error ,retry again - - msg: result msg for check - isV2: whether the file is use Android-V2 signature or not - isV2OK: whether the file's Android-V2 signature is ok or not - - -#### 查看版本 - - ➜ java -jar ./CheckAndroidV2Signature.jar --version - com.tencent.ysdk.CheckAndroidV2Signature version 1.0.1 (CheckAndroidV2Signature - 2) - homepage : https://github.com/bihe0832/AndroidGetAPKInfo - blog : http://blog.bihe0832.com - github : https://github.com/bihe0832 - -#### 查看应用信息 - - ➜ java -jar ./CheckAndroidV2Signature.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk - {"ret":0,"msg":"ok","isV2":false,"isV2OK":false} - - diff --git a/CheckAndroidV2Signature/build.gradle b/CheckAndroidV2Signature/build.gradle deleted file mode 100644 index 9eae18c..0000000 --- a/CheckAndroidV2Signature/build.gradle +++ /dev/null @@ -1,44 +0,0 @@ -apply plugin: 'java' - -jar{ - //项目名,也是生成的jar的名字 - baseName = "CheckAndroidV2Signature" - //项目版本号,这部分内容会写进manifest - version = "1.0" - //项目的manifest定义,其中就包含最关键的入口类定义 - manifest { - attributes 'Main-Class': 'com.bihe0832.checksignature.CheckAndroidSignature' - } - //添加将引用的jar的源码打入最终的jar - from { - (configurations.runtime).collect { - it.isDirectory() ? it : zipTree(it) - } - } - //排除引用的jar中的签名信息 - exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF' -} - -//添加源码中引入的非代码文件,例如资源等 -sourceSets.main.resources { - srcDirs = [ - "src/main/java", - ]; - include "**/*.*" -} - -//代码依赖 -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) -} - -//导出未混淆Jar -task copyJar(dependsOn: "build") { - copy { - from 'build/libs/'+ jar.baseName +'-'+ jar.version +'.jar' - into './../' - rename { String fileName -> - fileName.replace(jar.baseName +'-'+ jar.version +'.jar', jar.baseName +'.jar') - } - } -} \ No newline at end of file diff --git a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/ApkSignatureSchemeV2Verifier.java b/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/ApkSignatureSchemeV2Verifier.java deleted file mode 100644 index 3547e81..0000000 --- a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/ApkSignatureSchemeV2Verifier.java +++ /dev/null @@ -1,1250 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.bihe0832.checksignature; - - -import java.io.ByteArrayInputStream; -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.math.BigInteger; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.security.DigestException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Principal; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateFactory; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.MGF1ParameterSpec; -import java.security.spec.PSSParameterSpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - - -/** - * APK Signature Scheme v2 verifier. - * - * @hide for internal use only. - */ -public class ApkSignatureSchemeV2Verifier { - - /** - * {@code .SF} file header section attribute indicating that the APK is signed not just with - * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute - * facilitates v2 signature stripping detection. - * - *

The attribute contains a comma-separated set of signature scheme IDs. - */ - public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed"; - public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2; - - /** - * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. - * - *

NOTE: This method does not verify the signature. - */ - public static boolean hasSignature(String apkFile) throws IOException { - try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { - findSignature(apk); - return true; - } catch (SignatureNotFoundException e) { - return false; - } - } - - /** - * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates - * associated with each signer. - * - * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. - * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify. - * @throws IOException if an I/O error occurs while reading the APK file. - */ - public static X509Certificate[][] verify(String apkFile) - throws SignatureNotFoundException, SecurityException, IOException { - try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { - return verify(apk); - } - } - - /** - * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates - * associated with each signer. - * - * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. - * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not - * verify. - * @throws IOException if an I/O error occurs while reading the APK file. - */ - private static X509Certificate[][] verify(RandomAccessFile apk) - throws SignatureNotFoundException, SecurityException, IOException { - SignatureInfo signatureInfo = findSignature(apk); - return verify(apk, signatureInfo); - } - - /** - * APK Signature Scheme v2 block and additional information relevant to verifying the signatures - * contained in the block against the file. - */ - private static class SignatureInfo { - /** Contents of APK Signature Scheme v2 block. */ - private final ByteBuffer signatureBlock; - - /** Position of the APK Signing Block in the file. */ - private final long apkSigningBlockOffset; - - /** Position of the ZIP Central Directory in the file. */ - private final long centralDirOffset; - - /** Position of the ZIP End of Central Directory (EoCD) in the file. */ - private final long eocdOffset; - - /** Contents of ZIP End of Central Directory (EoCD) of the file. */ - private final ByteBuffer eocd; - - private SignatureInfo( - ByteBuffer signatureBlock, - long apkSigningBlockOffset, - long centralDirOffset, - long eocdOffset, - ByteBuffer eocd) { - this.signatureBlock = signatureBlock; - this.apkSigningBlockOffset = apkSigningBlockOffset; - this.centralDirOffset = centralDirOffset; - this.eocdOffset = eocdOffset; - this.eocd = eocd; - } - } - - /** - * Returns the APK Signature Scheme v2 block contained in the provided APK file and the - * additional information relevant for verifying the block against the file. - * - * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. - * @throws IOException if an I/O error occurs while reading the APK file. - */ - private static SignatureInfo findSignature(RandomAccessFile apk) - throws IOException, SignatureNotFoundException { - // Find the ZIP End of Central Directory (EoCD) record. - Pair eocdAndOffsetInFile = getEocd(apk); - ByteBuffer eocd = eocdAndOffsetInFile.first; - long eocdOffset = eocdAndOffsetInFile.second; - if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { - throw new SignatureNotFoundException("ZIP64 APK not supported"); - } - - // Find the APK Signing Block. The block immediately precedes the Central Directory. - long centralDirOffset = getCentralDirOffset(eocd, eocdOffset); - Pair apkSigningBlockAndOffsetInFile = - findApkSigningBlock(apk, centralDirOffset); - ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first; - long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second; - - // Find the APK Signature Scheme v2 Block inside the APK Signing Block. - ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock); - - return new SignatureInfo( - apkSignatureSchemeV2Block, - apkSigningBlockOffset, - centralDirOffset, - eocdOffset, - eocd); - } - - /** - * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2 - * Block. - * - * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it - * against the APK file. - */ - private static X509Certificate[][] verify( - RandomAccessFile apk, - SignatureInfo signatureInfo) throws SecurityException { - - FileDescriptor apkFileDescriptor = null; - try { - apkFileDescriptor = apk.getFD(); - } catch (IOException e1) { - e1.printStackTrace(); - } - - int signerCount = 0; - Map contentDigests = new HashMap<>(); - List signerCerts = new ArrayList<>(); - CertificateFactory certFactory; - try { - certFactory = CertificateFactory.getInstance("X.509"); - } catch (CertificateException e) { - throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); - } - ByteBuffer signers; - try { - signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); - } catch (IOException e) { - throw new SecurityException("Failed to read list of signers", e); - } - while (signers.hasRemaining()) { - signerCount++; - try { - ByteBuffer signer = getLengthPrefixedSlice(signers); - X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory); - signerCerts.add(certs); - } catch (IOException | BufferUnderflowException | SecurityException e) { - throw new SecurityException( - "Failed to parse/verify signer #" + signerCount + " block", - e); - } - } - - if (signerCount < 1) { - throw new SecurityException("No signers found"); - } - - if (contentDigests.isEmpty()) { - throw new SecurityException("No content digests found"); - } - - verifyIntegrity(apk, - contentDigests, - apkFileDescriptor, - signatureInfo.apkSigningBlockOffset, - signatureInfo.centralDirOffset, - signatureInfo.eocdOffset, - signatureInfo.eocd); - - return signerCerts.toArray(new X509Certificate[signerCerts.size()][]); - } - - private static X509Certificate[] verifySigner( - ByteBuffer signerBlock, - Map contentDigests, - CertificateFactory certFactory) throws SecurityException, IOException { - ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); - ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); - byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); - - int signatureCount = 0; - int bestSigAlgorithm = -1; - byte[] bestSigAlgorithmSignatureBytes = null; - List signaturesSigAlgorithms = new ArrayList<>(); - while (signatures.hasRemaining()) { - signatureCount++; - try { - ByteBuffer signature = getLengthPrefixedSlice(signatures); - if (signature.remaining() < 8) { - throw new SecurityException("Signature record too short"); - } - int sigAlgorithm = signature.getInt(); - signaturesSigAlgorithms.add(sigAlgorithm); - if (!isSupportedSignatureAlgorithm(sigAlgorithm)) { - continue; - } - if ((bestSigAlgorithm == -1) - || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { - bestSigAlgorithm = sigAlgorithm; - bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature); - } - } catch (IOException | BufferUnderflowException e) { - throw new SecurityException( - "Failed to parse signature record #" + signatureCount, - e); - } - } - if (bestSigAlgorithm == -1) { - if (signatureCount == 0) { - throw new SecurityException("No signatures found"); - } else { - throw new SecurityException("No supported signatures found"); - } - } - - String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm); - Pair signatureAlgorithmParams = - getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm); - String jcaSignatureAlgorithm = signatureAlgorithmParams.first; - AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; - boolean sigVerified; - try { - PublicKey publicKey = - KeyFactory.getInstance(keyAlgorithm) - .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); - Signature sig = Signature.getInstance(jcaSignatureAlgorithm); - sig.initVerify(publicKey); - if (jcaSignatureAlgorithmParams != null) { - sig.setParameter(jcaSignatureAlgorithmParams); - } - sig.update(signedData); - sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); - } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException - | InvalidAlgorithmParameterException | SignatureException e) { - throw new SecurityException( - "Failed to verify " + jcaSignatureAlgorithm + " signature", e); - } - if (!sigVerified) { - throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); - } - - // Signature over signedData has verified. - - byte[] contentDigest = null; - signedData.clear(); - ByteBuffer digests = getLengthPrefixedSlice(signedData); - List digestsSigAlgorithms = new ArrayList<>(); - int digestCount = 0; - while (digests.hasRemaining()) { - digestCount++; - try { - ByteBuffer digest = getLengthPrefixedSlice(digests); - if (digest.remaining() < 8) { - throw new IOException("Record too short"); - } - int sigAlgorithm = digest.getInt(); - digestsSigAlgorithms.add(sigAlgorithm); - if (sigAlgorithm == bestSigAlgorithm) { - contentDigest = readLengthPrefixedByteArray(digest); - } - } catch (IOException | BufferUnderflowException e) { - throw new IOException("Failed to parse digest record #" + digestCount, e); - } - } - - if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) { - throw new SecurityException( - "Signature algorithms don't match between digests and signatures records"); - } - int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm); - byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest); - if ((previousSignerDigest != null) - && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) { - throw new SecurityException( - getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) - + " contents digest does not match the digest specified by a preceding signer"); - } - - ByteBuffer certificates = getLengthPrefixedSlice(signedData); - List certs = new ArrayList<>(); - int certificateCount = 0; - while (certificates.hasRemaining()) { - certificateCount++; - byte[] encodedCert = readLengthPrefixedByteArray(certificates); - X509Certificate certificate; - try { - certificate = (X509Certificate) - certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); - } catch (CertificateException e) { - throw new SecurityException("Failed to decode certificate #" + certificateCount, e); - } - certificate = new VerbatimX509Certificate(certificate, encodedCert); - certs.add(certificate); - } - - if (certs.isEmpty()) { - throw new SecurityException("No certificates listed"); - } - X509Certificate mainCertificate = certs.get(0); - byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); - if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { - throw new SecurityException( - "Public key mismatch between certificate and signature record"); - } - - return certs.toArray(new X509Certificate[certs.size()]); - } - - private static void verifyIntegrity(RandomAccessFile apk, - Map expectedDigests, - FileDescriptor apkFileDescriptor, - long apkSigningBlockOffset, - long centralDirOffset, - long eocdOffset, - ByteBuffer eocdBuf) throws SecurityException { - - if (expectedDigests.isEmpty()) { - throw new SecurityException("No digests provided"); - } - - // We need to verify the integrity of the following three sections of the file: - // 1. Everything up to the start of the APK Signing Block. - // 2. ZIP Central Directory. - // 3. ZIP End of Central Directory (EoCD). - // Each of these sections is represented as a separate DataSource instance below. - - // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to - // avoid wasting physical memory. In most APK verification scenarios, the contents of the - // APK are already there in the OS's page cache and thus mmap does not use additional - // physical memory. - DataSource beforeApkSigningBlock = - new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset); - DataSource centralDir = - new MemoryMappedFileDataSource( - apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset); - - // For the purposes of integrity verification, ZIP End of Central Directory's field Start of - // Central Directory must be considered to point to the offset of the APK Signing Block. - eocdBuf = eocdBuf.duplicate(); - eocdBuf.order(ByteOrder.LITTLE_ENDIAN); - ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset); - DataSource eocd = new ByteBufferDataSource(eocdBuf); - - int[] digestAlgorithms = new int[expectedDigests.size()]; - int digestAlgorithmCount = 0; - for (int digestAlgorithm : expectedDigests.keySet()) { - digestAlgorithms[digestAlgorithmCount] = digestAlgorithm; - digestAlgorithmCount++; - } - byte[][] actualDigests; - try { - actualDigests = - computeContentDigests(apk,apkFileDescriptor, - digestAlgorithms, - new DataSource[] {beforeApkSigningBlock, centralDir, eocd}); - } catch (DigestException e) { - throw new SecurityException("Failed to compute digest(s) of contents", e); - } - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] expectedDigest = expectedDigests.get(digestAlgorithm); - byte[] actualDigest = actualDigests[i]; - if (!MessageDigest.isEqual(expectedDigest, actualDigest)) { - throw new SecurityException( - getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) - + " digest of contents did not verify"); - } - } - } - - private static byte[][] computeContentDigests(RandomAccessFile apk,FileDescriptor apkFileDescriptor, - int[] digestAlgorithms, - DataSource[] contents) throws DigestException { - // For each digest algorithm the result is computed as follows: - // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. - // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. - // No chunks are produced for empty (zero length) segments. - // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's - // length in bytes (uint32 little-endian) and the chunk's contents. - // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of - // chunks (uint32 little-endian) and the concatenation of digests of chunks of all - // segments in-order. - - long totalChunkCountLong = 0; - for (DataSource input : contents) { - totalChunkCountLong += getChunkCount(input.size()); - } - if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) { - throw new DigestException("Too many chunks: " + totalChunkCountLong); - } - int totalChunkCount = (int) totalChunkCountLong; - - byte[][] digestsOfChunks = new byte[digestAlgorithms.length][]; - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); - byte[] concatenationOfChunkCountAndChunkDigests = - new byte[5 + totalChunkCount * digestOutputSizeBytes]; - concatenationOfChunkCountAndChunkDigests[0] = 0x5a; - setUnsignedInt32LittleEndian( - totalChunkCount, - concatenationOfChunkCountAndChunkDigests, - 1); - digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; - } - - byte[] chunkContentPrefix = new byte[5]; - chunkContentPrefix[0] = (byte) 0xa5; - int chunkIndex = 0; - MessageDigest[] mds = new MessageDigest[digestAlgorithms.length]; - for (int i = 0; i < digestAlgorithms.length; i++) { - String jcaAlgorithmName = - getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]); - try { - mds[i] = MessageDigest.getInstance(jcaAlgorithmName); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); - } - } - // TODO: Compute digests of chunks in parallel when beneficial. This requires some research - // into how to parallelize (if at all) based on the capabilities of the hardware on which - // this code is running and based on the size of input. - int dataSourceIndex = 0; - for (DataSource input : contents) { - long inputOffset = 0; - long inputRemaining = input.size(); - while (inputRemaining > 0) { - int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES); - setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); - for (int i = 0; i < mds.length; i++) { - mds[i].update(chunkContentPrefix); - } - try { - //计算一个DataSource从inputOffset开始的,chunkSize长度的内容的md5 - input.feedIntoMessageDigests(apk.getChannel(), mds, inputOffset, chunkSize); - } catch (IOException e) { - throw new DigestException( - "Failed to digest chunk #" + chunkIndex + " of section #" - + dataSourceIndex,e); - } - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; - int expectedDigestSizeBytes = - getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); - MessageDigest md = mds[i]; - int actualDigestSizeBytes = - md.digest( - concatenationOfChunkCountAndChunkDigests, - 5 + chunkIndex * expectedDigestSizeBytes, - expectedDigestSizeBytes); - if (actualDigestSizeBytes != expectedDigestSizeBytes) { - throw new RuntimeException( - "Unexpected output size of " + md.getAlgorithm() + " digest: " - + actualDigestSizeBytes); - } - } - inputOffset += chunkSize; - inputRemaining -= chunkSize; - chunkIndex++; - } - dataSourceIndex++; - } - - byte[][] result = new byte[digestAlgorithms.length][]; - for (int i = 0; i < digestAlgorithms.length; i++) { - int digestAlgorithm = digestAlgorithms[i]; - byte[] input = digestsOfChunks[i]; - String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); - MessageDigest md; - try { - md = MessageDigest.getInstance(jcaAlgorithmName); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); - } - byte[] output = md.digest(input); - result[i] = output; - } - return result; - } - - /** - * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. - * - * @throws IOException if an I/O error occurs while reading the file. - * @throws SignatureNotFoundException if the EoCD could not be found. - */ - private static Pair getEocd(RandomAccessFile apk) - throws IOException, SignatureNotFoundException { - Pair eocdAndOffsetInFile = - ZipUtils.findZipEndOfCentralDirectoryRecord(apk); - if (eocdAndOffsetInFile == null) { - throw new SignatureNotFoundException( - "Not an APK file: ZIP End of Central Directory record not found"); - } - return eocdAndOffsetInFile; - } - - private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) - throws SignatureNotFoundException { - // Look up the offset of ZIP Central Directory. - long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); - if (centralDirOffset >= eocdOffset) { - throw new SignatureNotFoundException( - "ZIP Central Directory offset out of range: " + centralDirOffset - + ". ZIP End of Central Directory offset: " + eocdOffset); - } - long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); - if (centralDirOffset + centralDirSize != eocdOffset) { - throw new SignatureNotFoundException( - "ZIP Central Directory is not immediately followed by End of Central" - + " Directory"); - } - return centralDirOffset; - } - - private static final long getChunkCount(long inputSizeBytes) { - return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES; - } - - private static final int CHUNK_SIZE_BYTES = 1024 * 1024; - - private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; - private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; - private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; - private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; - private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; - private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; - private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; - - private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; - private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; - - private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - case SIGNATURE_RSA_PSS_WITH_SHA512: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - case SIGNATURE_ECDSA_WITH_SHA256: - case SIGNATURE_ECDSA_WITH_SHA512: - case SIGNATURE_DSA_WITH_SHA256: - return true; - default: - return false; - } - } - - private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { - int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); - int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); - return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2); - } - - private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) { - switch (digestAlgorithm1) { - case CONTENT_DIGEST_CHUNKED_SHA256: - switch (digestAlgorithm2) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 0; - case CONTENT_DIGEST_CHUNKED_SHA512: - return -1; - default: - throw new IllegalArgumentException( - "Unknown digestAlgorithm2: " + digestAlgorithm2); - } - case CONTENT_DIGEST_CHUNKED_SHA512: - switch (digestAlgorithm2) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 1; - case CONTENT_DIGEST_CHUNKED_SHA512: - return 0; - default: - throw new IllegalArgumentException( - "Unknown digestAlgorithm2: " + digestAlgorithm2); - } - default: - throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1); - } - } - - private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - case SIGNATURE_ECDSA_WITH_SHA256: - case SIGNATURE_DSA_WITH_SHA256: - return CONTENT_DIGEST_CHUNKED_SHA256; - case SIGNATURE_RSA_PSS_WITH_SHA512: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - case SIGNATURE_ECDSA_WITH_SHA512: - return CONTENT_DIGEST_CHUNKED_SHA512; - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { - switch (digestAlgorithm) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return "SHA-256"; - case CONTENT_DIGEST_CHUNKED_SHA512: - return "SHA-512"; - default: - throw new IllegalArgumentException( - "Unknown content digest algorthm: " + digestAlgorithm); - } - } - - private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { - switch (digestAlgorithm) { - case CONTENT_DIGEST_CHUNKED_SHA256: - return 256 / 8; - case CONTENT_DIGEST_CHUNKED_SHA512: - return 512 / 8; - default: - throw new IllegalArgumentException( - "Unknown content digest algorthm: " + digestAlgorithm); - } - } - - private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - case SIGNATURE_RSA_PSS_WITH_SHA512: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - return "RSA"; - case SIGNATURE_ECDSA_WITH_SHA256: - case SIGNATURE_ECDSA_WITH_SHA512: - return "EC"; - case SIGNATURE_DSA_WITH_SHA256: - return "DSA"; - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - private static Pair - getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { - switch (sigAlgorithm) { - case SIGNATURE_RSA_PSS_WITH_SHA256: - return Pair.create( - "SHA256withRSA/PSS", - new PSSParameterSpec( - "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); - case SIGNATURE_RSA_PSS_WITH_SHA512: - return Pair.create( - "SHA512withRSA/PSS", - new PSSParameterSpec( - "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: - return Pair.create("SHA256withRSA", null); - case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: - return Pair.create("SHA512withRSA", null); - case SIGNATURE_ECDSA_WITH_SHA256: - return Pair.create("SHA256withECDSA", null); - case SIGNATURE_ECDSA_WITH_SHA512: - return Pair.create("SHA512withECDSA", null); - case SIGNATURE_DSA_WITH_SHA256: - return Pair.create("SHA256withDSA", null); - default: - throw new IllegalArgumentException( - "Unknown signature algorithm: 0x" - + Long.toHexString(sigAlgorithm & 0xffffffff)); - } - } - - /** - * Returns new byte buffer whose content is a shared subsequence of this buffer's content - * between the specified start (inclusive) and end (exclusive) positions. As opposed to - * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source - * buffer's byte order. - */ - private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { - if (start < 0) { - throw new IllegalArgumentException("start: " + start); - } - if (end < start) { - throw new IllegalArgumentException("end < start: " + end + " < " + start); - } - int capacity = source.capacity(); - if (end > source.capacity()) { - throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); - } - int originalLimit = source.limit(); - int originalPosition = source.position(); - try { - source.position(0); - source.limit(end); - source.position(start); - ByteBuffer result = source.slice(); - result.order(source.order()); - return result; - } finally { - source.position(0); - source.limit(originalLimit); - source.position(originalPosition); - } - } - - /** - * Relative get method for reading {@code size} number of bytes from the current - * position of this buffer. - * - *

This method reads the next {@code size} bytes at this buffer's current position, - * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to - * {@code size}, byte order set to this buffer's byte order; and then increments the position by - * {@code size}. - */ - private static ByteBuffer getByteBuffer(ByteBuffer source, int size) - throws BufferUnderflowException { - if (size < 0) { - throw new IllegalArgumentException("size: " + size); - } - int originalLimit = source.limit(); - int position = source.position(); - int limit = position + size; - if ((limit < position) || (limit > originalLimit)) { - throw new BufferUnderflowException(); - } - source.limit(limit); - try { - ByteBuffer result = source.slice(); - result.order(source.order()); - source.position(limit); - return result; - } finally { - source.limit(originalLimit); - } - } - - private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException { - if (source.remaining() < 4) { - throw new IOException( - "Remaining buffer too short to contain length of length-prefixed field." - + " Remaining: " + source.remaining()); - } - int len = source.getInt(); - if (len < 0) { - throw new IllegalArgumentException("Negative length"); - } else if (len > source.remaining()) { - throw new IOException("Length-prefixed field longer than remaining buffer." - + " Field length: " + len + ", remaining: " + source.remaining()); - } - return getByteBuffer(source, len); - } - - private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException { - int len = buf.getInt(); - if (len < 0) { - throw new IOException("Negative length"); - } else if (len > buf.remaining()) { - throw new IOException("Underflow while reading length-prefixed value. Length: " + len - + ", available: " + buf.remaining()); - } - byte[] result = new byte[len]; - buf.get(result); - return result; - } - - private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { - result[offset] = (byte) (value & 0xff); - result[offset + 1] = (byte) ((value >>> 8) & 0xff); - result[offset + 2] = (byte) ((value >>> 16) & 0xff); - result[offset + 3] = (byte) ((value >>> 24) & 0xff); - } - - private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; - private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; - private static final int APK_SIG_BLOCK_MIN_SIZE = 32; - - private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; - - private static Pair findApkSigningBlock( - RandomAccessFile apk, long centralDirOffset) - throws IOException, SignatureNotFoundException { - // FORMAT: - // OFFSET DATA TYPE DESCRIPTION - // * @+0 bytes uint64: size in bytes (excluding this field) - // * @+8 bytes payload - // * @-24 bytes uint64: size in bytes (same as the one above) - // * @-16 bytes uint128: magic - - if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) { - throw new SignatureNotFoundException( - "APK too small for APK Signing Block. ZIP Central Directory offset: " - + centralDirOffset); - } - // Read the magic and offset in file from the footer section of the block: - // * uint64: size of block - // * 16 bytes: magic - ByteBuffer footer = ByteBuffer.allocate(24); - footer.order(ByteOrder.LITTLE_ENDIAN); - apk.seek(centralDirOffset - footer.capacity()); - apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity()); - if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) - || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { - throw new SignatureNotFoundException( - "No APK Signing Block before ZIP Central Directory"); - } - // Read and compare size fields - long apkSigBlockSizeInFooter = footer.getLong(0); - if ((apkSigBlockSizeInFooter < footer.capacity()) - || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { - throw new SignatureNotFoundException( - "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); - } - int totalSize = (int) (apkSigBlockSizeInFooter + 8); - long apkSigBlockOffset = centralDirOffset - totalSize; - if (apkSigBlockOffset < 0) { - throw new SignatureNotFoundException( - "APK Signing Block offset out of range: " + apkSigBlockOffset); - } - ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize); - apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); - apk.seek(apkSigBlockOffset); - apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity()); - long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); - if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { - throw new SignatureNotFoundException( - "APK Signing Block sizes in header and footer do not match: " - + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); - } - return Pair.create(apkSigBlock, apkSigBlockOffset); - } - - private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock) - throws SignatureNotFoundException { - checkByteOrderLittleEndian(apkSigningBlock); - // 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 = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); - - int entryCount = 0; - while (pairs.hasRemaining()) { - entryCount++; - if (pairs.remaining() < 8) { - throw new 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 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 SignatureNotFoundException( - "APK Signing Block entry #" + entryCount + " size out of range: " + len - + ", available: " + pairs.remaining()); - } - int id = pairs.getInt(); - if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) { - return getByteBuffer(pairs, len - 4); - } - pairs.position(nextEntryPos); - } - - throw new SignatureNotFoundException( - "No APK Signature Scheme v2 block in APK Signing Block"); - } - - private static void checkByteOrderLittleEndian(ByteBuffer buffer) { - if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { - throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); - } - } - - public static class SignatureNotFoundException extends Exception { - private static final long serialVersionUID = 1L; - - public SignatureNotFoundException(String message) { - super(message); - } - - public SignatureNotFoundException(String message, Throwable cause) { - super(message, cause); - } - } - - /** - * Source of data to be digested. - */ - private static interface DataSource { - - /** - * Returns the size (in bytes) of the data offered by this source. - */ - long size(); - - /** - * Feeds the specified region of this source's data into the provided digests. Each digest - * instance gets the same data. - * - * @param offset offset of the region inside this data source. - * @param size size (in bytes) of the region. - */ - void feedIntoMessageDigests(FileChannel channel, MessageDigest[] mds, long offset, int size) throws IOException; - } - - /** - * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections - * of the file requested by - * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}. - */ - private static final class MemoryMappedFileDataSource implements DataSource { -// private static final Os OS = Libcore.os; - //TODO hardy 这里临时替换为一个比较大的值 -// private static final long MEMORY_PAGE_SIZE_BYTES = OS.sysconf(OsConstants._SC_PAGESIZE); - private static final long MEMORY_PAGE_SIZE_BYTES = 10 * 1024 *1024; - - private final FileDescriptor mFd; - private final long mFilePosition; - private final long mSize; - - public FileDescriptor getFD(){ - return mFd; - } - /** - * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file. - * - * @param position start position of the region in the file. - * @param size size (in bytes) of the region. - */ - public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) { - mFd = fd; - mFilePosition = position; - mSize = size; - } - - @Override - public long size() { - return mSize; - } - - @Override - public void feedIntoMessageDigests(FileChannel channel, - MessageDigest[] mds, long offset, int size) throws IOException { - long filePosition = mFilePosition + offset; - MappedByteBuffer inputBuffer = channel.map(FileChannel.MapMode.READ_ONLY, filePosition, size);// 读取大文件 - for (MessageDigest md : mds) { - inputBuffer.position(0); - md.update(inputBuffer); - } - } - } - - /** - * {@link DataSource} which provides data from a {@link ByteBuffer}. - */ - private static final class ByteBufferDataSource implements DataSource { - /** - * Underlying buffer. The data is stored between position 0 and the buffer's capacity. - * The buffer's position is 0 and limit is equal to capacity. - */ - private final ByteBuffer mBuf; - - public ByteBufferDataSource(ByteBuffer buf) { - // Defensive copy, to avoid changes to mBuf being visible in buf. - mBuf = buf.slice(); - } - - @Override - public long size() { - return mBuf.capacity(); - } - - @Override - public void feedIntoMessageDigests(FileChannel channel, - MessageDigest[] mds, long offset, int size) throws IOException { - // There's no way to tell MessageDigest to read data from ByteBuffer from a position - // other than the buffer's current position. We thus need to change the buffer's - // position to match the requested offset. - // - // In the future, it may be necessary to compute digests of multiple regions in - // parallel. Given that digest computation is a slow operation, we enable multiple - // such requests to be fulfilled by this instance. This is achieved by serially - // creating a new ByteBuffer corresponding to the requested data range and then, - // potentially concurrently, feeding these buffers into MessageDigest instances. - ByteBuffer region; - synchronized (mBuf) { - mBuf.position((int) offset); - mBuf.limit((int) offset + size); - region = mBuf.slice(); - } - - for (MessageDigest md : mds) { - // Need to reset position to 0 at the start of each iteration because - // MessageDigest.update below sets it to the buffer's limit. - region.position(0); - md.update(region); - } - } - } - - /** - * For legacy reasons we need to return exactly the original encoded certificate bytes, instead - * of letting the underlying implementation have a shot at re-encoding the data. - */ - private static class VerbatimX509Certificate extends WrappedX509Certificate { - private byte[] encodedVerbatim; - - public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { - super(wrapped); - this.encodedVerbatim = encodedVerbatim; - } - - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return encodedVerbatim; - } - } - - private static class WrappedX509Certificate extends X509Certificate { - private final X509Certificate wrapped; - - public WrappedX509Certificate(X509Certificate wrapped) { - this.wrapped = wrapped; - } - - @Override - public Set getCriticalExtensionOIDs() { - return wrapped.getCriticalExtensionOIDs(); - } - - @Override - public byte[] getExtensionValue(String oid) { - return wrapped.getExtensionValue(oid); - } - - @Override - public Set getNonCriticalExtensionOIDs() { - return wrapped.getNonCriticalExtensionOIDs(); - } - - @Override - public boolean hasUnsupportedCriticalExtension() { - return wrapped.hasUnsupportedCriticalExtension(); - } - - @Override - public void checkValidity() - throws CertificateExpiredException, CertificateNotYetValidException { - wrapped.checkValidity(); - } - - @Override - public void checkValidity(Date date) - throws CertificateExpiredException, CertificateNotYetValidException { - wrapped.checkValidity(date); - } - - @Override - public int getVersion() { - return wrapped.getVersion(); - } - - @Override - public BigInteger getSerialNumber() { - return wrapped.getSerialNumber(); - } - - @Override - public Principal getIssuerDN() { - return wrapped.getIssuerDN(); - } - - @Override - public Principal getSubjectDN() { - return wrapped.getSubjectDN(); - } - - @Override - public Date getNotBefore() { - return wrapped.getNotBefore(); - } - - @Override - public Date getNotAfter() { - return wrapped.getNotAfter(); - } - - @Override - public byte[] getTBSCertificate() throws CertificateEncodingException { - return wrapped.getTBSCertificate(); - } - - @Override - public byte[] getSignature() { - return wrapped.getSignature(); - } - - @Override - public String getSigAlgName() { - return wrapped.getSigAlgName(); - } - - @Override - public String getSigAlgOID() { - return wrapped.getSigAlgOID(); - } - - @Override - public byte[] getSigAlgParams() { - return wrapped.getSigAlgParams(); - } - - @Override - public boolean[] getIssuerUniqueID() { - return wrapped.getIssuerUniqueID(); - } - - @Override - public boolean[] getSubjectUniqueID() { - return wrapped.getSubjectUniqueID(); - } - - @Override - public boolean[] getKeyUsage() { - return wrapped.getKeyUsage(); - } - - @Override - public int getBasicConstraints() { - return wrapped.getBasicConstraints(); - } - - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return wrapped.getEncoded(); - } - - @Override - public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, - InvalidKeyException, NoSuchProviderException, SignatureException { - wrapped.verify(key); - } - - @Override - public void verify(PublicKey key, String sigProvider) - throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchProviderException, SignatureException { - wrapped.verify(key, sigProvider); - } - - @Override - public String toString() { - return wrapped.toString(); - } - - @Override - public PublicKey getPublicKey() { - return wrapped.getPublicKey(); - } - } -} diff --git a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/CheckAndroidSignature.java b/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/CheckAndroidSignature.java deleted file mode 100644 index 8ab7614..0000000 --- a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/CheckAndroidSignature.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.bihe0832.checksignature; - -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.RandomAccessFile; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.util.Enumeration; -import java.util.List; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import com.bihe0832.checksignature.ApkSignatureSchemeV2Verifier.SignatureNotFoundException; - -/** - * - * @author bihe0832 blog.bihe0832.com - * @version 1 - * @version 1.0.0 - * - */ -public class CheckAndroidSignature { - - private static final int VERSION_CODE = 3; - private static final String VERSION_NAME = "1.0.2"; - - public static final String KEY_RESULT_IS_V2 = "isV2"; - public static final String KEY_RESULT_IS_V2_OK = "isV2OK"; - public static final String KEY_RESULT_RET = "ret"; - public static final String KEY_RESULT_MSG = "msg"; - //成功 - private static final int RET_OK = 0; - //文件路径错误 - private static final int RET_FILE_NOT_FOUND = -1; - //文件类型错误 - private static final int RET_FILE_NOT_GOOD = -2; - //获取签名异常 - private static final int RET_GET_SIG_BAD = -3; - - - public static void main(String args[]) { - if(args.length > 0){ - String para = args[0]; - if(null != para && para.length() > 0){ - if(para.toLowerCase().startsWith("--help")){ - showHelp(); - }else if(para.toLowerCase().startsWith("--version")){ - showVersion(); - }else if(para.toLowerCase().endsWith(".apk")){ - System.out.println(checkSig(para)); - }else{ - System.out.println(getFailedCheckResult(RET_FILE_NOT_GOOD, para +"is not an android apk file")); - } - }else{ - showHelp(); - } - }else{ - showHelp(); - } - } - - private static void showHelp(){ - StringBuilder sb = new StringBuilder(); - sb.append("\nusage: java -jar ./CheckAndroidV2Signature.jar [--version] [--help] [filePath]\n\n"); - sb.append("such as:\n\n"); - sb.append("\t java -jar ./CheckAndroidV2Signature.jar --version \n"); - sb.append("\t java -jar ./CheckAndroidV2Signature.jar --help \n"); - sb.append("\t java -jar ./CheckAndroidV2Signature.jar ./test.apk \n\n"); - sb.append("after check,the result will be a string json such as:\n\n"); - sb.append("\t {\"ret\":0,\"msg\":\"ok\",\"isV2\":true,\"isV2OK\":true} \n\n"); - sb.append("\t ret: result code for check\n\n"); - sb.append("\t\t 0 : command exec succ \n"); - sb.append("\t\t -1 : file not found\n"); - sb.append("\t\t -2 : file not an Android APK file\n"); - sb.append("\t\t -3 : check File signature error ,retry again \n\n"); - sb.append("\t msg: result msg for check\n"); - sb.append("\t isV2: whether the file is use Android-V2 signature or not\n"); - sb.append("\t isV2OK: whether the file's Android-V2 signature is ok or not\n"); - System.out.println(sb.toString()); - } - - private static void showVersion(){ - StringBuilder sb = new StringBuilder("com.tencent.ysdk.CheckAndroidV2Signature version " + VERSION_NAME + " (CheckAndroidV2Signature - " + VERSION_CODE + ")\n"); - sb.append("homepage : https://github.com/bihe0832/AndroidGetAPKInfo \n"); - sb.append("blog : http://blog.bihe0832.com \n"); - sb.append("github : https://github.com/bihe0832 \n"); - System.out.println(sb.toString()); - } - - static final String SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR = "X-Android-APK-Signed"; - - public static String checkSig(String filePath){ - boolean isV2 = true; - try { - - isV2 = ApkSignatureSchemeV2Verifier.hasSignature(filePath); - if(!isV2){ - int apkSignatureNum = getApkSignSchemeVersion(filePath); - isV2 = 2 == apkSignatureNum ? true : isV2; - } - - if(isV2){ - X509Certificate[][] isV2OK = ApkSignatureSchemeV2Verifier.verify(filePath); - if(isV2OK.length > 0){ - return getSuccssedCheckResult(RET_OK,"ok",true,true); - }else{ - return getSuccssedCheckResult(RET_OK,"ok",true,false); - } - }else{ - return getSuccssedCheckResult(RET_OK,"ok",false,false); - } - }catch (FileNotFoundException e) { - return getFailedCheckResult(RET_FILE_NOT_FOUND,"get signature failed, File:"+ filePath +" Not Found"); - }catch (IOException e) { - return getFailedCheckResult(RET_GET_SIG_BAD,"get signature failed, throw an IOException"); - }catch (SignatureNotFoundException e) { - if(isV2){ - return getSuccssedCheckResult(RET_GET_SIG_BAD,e.toString(),true,false); - }else{ - return getSuccssedCheckResult(RET_OK,e.toString(),false,false); - } - - }catch (SecurityException e) { - return getSuccssedCheckResult(RET_OK,"get signature failed, throw an SecurityException",true,false); - } - } - - private static String getSuccssedCheckResult(int ret,String Msg, boolean isV2, boolean isV2Ok){ - return "{\""+ KEY_RESULT_RET +"\":" + ret + ",\""+ KEY_RESULT_MSG +"\":\"" + Msg + "\",\""+ KEY_RESULT_IS_V2 +"\":" + isV2 + ",\""+ KEY_RESULT_IS_V2_OK +"\":" + isV2Ok + "}"; - } - - private static String getFailedCheckResult(int ret,String Msg){ - return "{\""+ KEY_RESULT_RET +"\":" + ret + ",\""+ KEY_RESULT_MSG +"\":\"" + Msg + "\"}"; - } - - private static int getApkSignSchemeVersion(String apkFilePath) { - byte[] readBuffer = new byte[8192]; - try { - JarFile e = new JarFile(apkFilePath); - Enumeration entries = e.entries(); - while(entries.hasMoreElements()) { - JarEntry je = (JarEntry)entries.nextElement(); - if(!je.isDirectory() && je.getName().startsWith("META-INF") && je.getName().endsWith(".SF")) { - //FIX 临时方案:逐行读取,然后检查是不是包含两个key - InputStream jarEntryInputStream = e.getInputStream(je); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(jarEntryInputStream)); - String readLine = null; - while ((readLine = bufferedReader.readLine()) != null) { - if(readLine.contains(SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR)){ - if(readLine.contains("2")){ - return 2; - }else{ - return 1; - } - } - } - } - } - e.close(); - } catch (Exception var10) { - var10.printStackTrace(); - } - return 1; - } -} diff --git a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/Pair.java b/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/Pair.java deleted file mode 100644 index 5a82029..0000000 --- a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/Pair.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.bihe0832.checksignature; - -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.util.Objects; -/** - * Container to ease passing around a tuple of two objects. This object provides a sensible - * implementation of equals(), returning true if equals() is true on each of the contained - * objects. - */ -public class Pair { - public final F first; - public final S second; - /** - * Constructor for a Pair. - * - * @param first the first object in the Pair - * @param second the second object in the pair - */ - public Pair(F first, S second) { - this.first = first; - this.second = second; - } - /** - * Checks the two objects for equality by delegating to their respective - * {@link Object#equals(Object)} methods. - * - * @param o the {@link Pair} to which this one is to be checked for equality - * @return true if the underlying objects of the Pair are both considered - * equal - */ - @Override - public boolean equals(Object o) { - if (!(o instanceof Pair)) { - return false; - } - Pair p = (Pair) o; - return Objects.equals(p.first, first) && Objects.equals(p.second, second); - } - /** - * Compute a hash code using the hash codes of the underlying objects - * - * @return a hashcode of the Pair - */ - @Override - public int hashCode() { - return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); - } - @Override - public String toString() { - return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}"; - } - /** - * Convenience method for creating an appropriately typed pair. - * @param a the first object in the Pair - * @param b the second object in the pair - * @return a Pair that is templatized with the types of a and b - */ - public static Pair create(A a, B b) { - return new Pair(a, b); - } -} \ No newline at end of file diff --git a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/ZipUtils.java b/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/ZipUtils.java deleted file mode 100644 index 7042c1c..0000000 --- a/CheckAndroidV2Signature/src/main/java/com/bihe0832/checksignature/ZipUtils.java +++ /dev/null @@ -1,234 +0,0 @@ -package com.bihe0832.checksignature; - -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * Assorted ZIP format helpers. - * - *

NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte - * order of these buffers is little-endian. - */ -abstract class ZipUtils { - private ZipUtils() {} - private static final int ZIP_EOCD_REC_MIN_SIZE = 22; - private static final int ZIP_EOCD_REC_SIG = 0x06054b50; - private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12; - private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; - private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20; - private static final int ZIP64_EOCD_LOCATOR_SIZE = 20; - private static final int ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER = 0x504b0607; - private static final int UINT16_MAX_VALUE = 0xffff; - /** - * Returns the ZIP End of Central Directory record of the provided ZIP file. - * - * @return contents of the ZIP End of Central Directory record and the record's offset in the - * file or {@code null} if the file does not contain the record. - * - * @throws IOException if an I/O error occurs while reading the file. - */ - static Pair findZipEndOfCentralDirectoryRecord(RandomAccessFile zip) - throws IOException { - // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. - // The record can be identified by its 4-byte signature/magic which is located at the very - // beginning of the record. A complication is that the record is variable-length because of - // the comment field. - // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from - // end of the buffer for the EOCD record signature. Whenever we find a signature, we check - // the candidate record's comment length is such that the remainder of the record takes up - // exactly the remaining bytes in the buffer. The search is bounded because the maximum - // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. - long fileSize = zip.length(); - if (fileSize < ZIP_EOCD_REC_MIN_SIZE) { - return null; - } - // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus - // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily - // reading more data. - Pair result = findZipEndOfCentralDirectoryRecord(zip, 0); - if (result != null) { - return result; - } - // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment - // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because - // the comment length field is an unsigned 16-bit number. - return findZipEndOfCentralDirectoryRecord(zip, UINT16_MAX_VALUE); - } - /** - * Returns the ZIP End of Central Directory record of the provided ZIP file. - * - * @param maxCommentSize maximum accepted size (in bytes) of EoCD comment field. The permitted - * value is from 0 to 65535 inclusive. The smaller the value, the faster this method - * locates the record, provided its comment field is no longer than this value. - * - * @return contents of the ZIP End of Central Directory record and the record's offset in the - * file or {@code null} if the file does not contain the record. - * - * @throws IOException if an I/O error occurs while reading the file. - */ - private static Pair findZipEndOfCentralDirectoryRecord( - RandomAccessFile zip, int maxCommentSize) throws IOException { - // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. - // The record can be identified by its 4-byte signature/magic which is located at the very - // beginning of the record. A complication is that the record is variable-length because of - // the comment field. - // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from - // end of the buffer for the EOCD record signature. Whenever we find a signature, we check - // the candidate record's comment length is such that the remainder of the record takes up - // exactly the remaining bytes in the buffer. The search is bounded because the maximum - // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. - if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) { - throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize); - } - long fileSize = zip.length(); - if (fileSize < ZIP_EOCD_REC_MIN_SIZE) { - // No space for EoCD record in the file. - return null; - } - // Lower maxCommentSize if the file is too small. - maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE); - ByteBuffer buf = ByteBuffer.allocate(ZIP_EOCD_REC_MIN_SIZE + maxCommentSize); - buf.order(ByteOrder.LITTLE_ENDIAN); - long bufOffsetInFile = fileSize - buf.capacity(); - zip.seek(bufOffsetInFile); - zip.readFully(buf.array(), buf.arrayOffset(), buf.capacity()); - int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf); - if (eocdOffsetInBuf == -1) { - // No EoCD record found in the buffer - return null; - } - // EoCD found - buf.position(eocdOffsetInBuf); - ByteBuffer eocd = buf.slice(); - eocd.order(ByteOrder.LITTLE_ENDIAN); - return Pair.create(eocd, bufOffsetInFile + eocdOffsetInBuf); - } - /** - * Returns the position at which ZIP End of Central Directory record starts in the provided - * buffer or {@code -1} if the record is not present. - * - *

NOTE: Byte order of {@code zipContents} must be little-endian. - */ - private static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) { - assertByteOrderLittleEndian(zipContents); - // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. - // The record can be identified by its 4-byte signature/magic which is located at the very - // beginning of the record. A complication is that the record is variable-length because of - // the comment field. - // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from - // end of the buffer for the EOCD record signature. Whenever we find a signature, we check - // the candidate record's comment length is such that the remainder of the record takes up - // exactly the remaining bytes in the buffer. The search is bounded because the maximum - // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. - int archiveSize = zipContents.capacity(); - if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { - return -1; - } - int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE); - int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; - for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; - expectedCommentLength++) { - int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; - if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) { - int actualCommentLength = - getUnsignedInt16( - zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); - if (actualCommentLength == expectedCommentLength) { - return eocdStartPos; - } - } - } - return -1; - } - /** - * Returns {@code true} if the provided file contains a ZIP64 End of Central Directory - * Locator. - * - * @param zipEndOfCentralDirectoryPosition offset of the ZIP End of Central Directory record - * in the file. - * - * @throws IOException if an I/O error occurs while reading the file. - */ - static final boolean isZip64EndOfCentralDirectoryLocatorPresent( - RandomAccessFile zip, long zipEndOfCentralDirectoryPosition) throws IOException { - // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central - // Directory Record. - long locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE; - if (locatorPosition < 0) { - return false; - } - zip.seek(locatorPosition); - // RandomAccessFile.readInt assumes big-endian byte order, but ZIP format uses - // little-endian. - return zip.readInt() == ZIP64_EOCD_LOCATOR_SIG_REVERSE_BYTE_ORDER; - } - /** - * Returns the offset of the start of the ZIP Central Directory in the archive. - * - *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. - */ - static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) { - assertByteOrderLittleEndian(zipEndOfCentralDirectory); - return getUnsignedInt32( - zipEndOfCentralDirectory, - zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); - } - /** - * Sets the offset of the start of the ZIP Central Directory in the archive. - * - *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. - */ - static void setZipEocdCentralDirectoryOffset( - ByteBuffer zipEndOfCentralDirectory, long offset) { - assertByteOrderLittleEndian(zipEndOfCentralDirectory); - setUnsignedInt32( - zipEndOfCentralDirectory, - zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, - offset); - } - /** - * Returns the size (in bytes) of the ZIP Central Directory. - * - *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. - */ - static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) { - assertByteOrderLittleEndian(zipEndOfCentralDirectory); - return getUnsignedInt32( - zipEndOfCentralDirectory, - zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET); - } - private static void assertByteOrderLittleEndian(ByteBuffer buffer) { - if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { - throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); - } - } - private static int getUnsignedInt16(ByteBuffer buffer, int offset) { - return buffer.getShort(offset) & 0xffff; - } - private static long getUnsignedInt32(ByteBuffer buffer, int offset) { - return buffer.getInt(offset) & 0xffffffffL; - } - private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) { - if ((value < 0) || (value > 0xffffffffL)) { - throw new IllegalArgumentException("uint32 value of out range: " + value); - } - buffer.putInt(buffer.position() + offset, (int) value); - } -} \ No newline at end of file diff --git a/GetAPKInfo.jar b/GetAPKInfo.jar index ed89b01..ef73b7d 100644 Binary files a/GetAPKInfo.jar and b/GetAPKInfo.jar differ diff --git a/GetApkInfo/README.md b/GetApkInfo/README.md index 2687d6f..2ab857f 100644 --- a/GetApkInfo/README.md +++ b/GetApkInfo/README.md @@ -4,21 +4,6 @@ 具体的实现原理包括怎么生成可执行jar,怎么混淆jar等,我会在另一篇文章中说明。 -## 目录介绍 - - - ├── getPackageInfo-inner.jar : 混淆前的可执行jar - │  - ├── libs : 第三方包依赖 - │ │ - │   ├── AXMLPrinter2.jar : 解析AndroidMainfest - │ │  - │   └── jdom.jar : 解析Xml - │  - ├── proguard.pro : 代码混淆规则 - │  - └── src : 源码 - 根据 [https://github.com/bihe0832/Android-GetAPKInfo/issues/2](https://github.com/bihe0832/Android-GetAPKInfo/issues/2) 的需求,增加了获取更多信息的jar:getMorePackageInfo.jar;可以直接在根目录下载。由于个人实际开发中并不需要其余信息,因此在代码中**新增字段在打印时被注释了**。如果有相关的需求,可以删除com.bihe0832.packageinfo.bean.ApkInfo的toString中相关的注释。如果有更多字段的添加需求,可以参照com.bihe0832.packageinfo.utils.ApkUtil中的代码实现添加。 @@ -26,29 +11,31 @@ ### 查看帮助 - ➜ java -jar ./getPackageInfo.jar + ➜ java -jar ./GetAPKInfo.jar - usage: java -jar ./getPackageInfo.jar [--version] [--help] [filePath] + usage: java -jar ./GetAPKInfo.jar [--version] [--help] [filePath] such as: - java -jar ./getPackageInfo.jar --version - java -jar ./getPackageInfo.jar --help - java -jar ./getPackageInfo.jar ./test.apk + java -jar ./GetAPKInfo.jar --version + java -jar ./GetAPKInfo.jar --help + java -jar ./GetAPKInfo.jar ./test.apk ### 查看版本 - ➜ java -jar ./getPackageInfo.jar --version - com.bihe0832.getPackageInfo version 1.0.0 (getPackageInfo - 1) + ➜ java -jar ./GetAPKInfo.jar --version + + com.bihe0832.packageinfo.Main version 2.0 (GetApkInfo - 6) + homepage : https://github.com/bihe0832/AndroidGetAPKInfo blog : http://blog.bihe0832.com github : https://github.com/bihe0832 - + ### 查看应用信息 - ➜ java -jar ./getPackageInfo.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk + ➜ java -jar ./GetAPKInfo.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk 执行结果: 成功 应用信息: @@ -61,7 +48,7 @@ V2签名验证通过: false - ➜ java -jar ./getMorePackageInfo.jar ./test.apk + ➜ java -jar ./GetMorePackageInfo.jar ./test.apk 执行结果: 成功 应用信息: diff --git a/GetApkInfo/src/main/java/com/bihe0832/packageinfo/Main.java b/GetApkInfo/src/main/java/com/bihe0832/packageinfo/Main.java index 23d5c1b..219508a 100644 --- a/GetApkInfo/src/main/java/com/bihe0832/packageinfo/Main.java +++ b/GetApkInfo/src/main/java/com/bihe0832/packageinfo/Main.java @@ -36,7 +36,7 @@ public static void main(String[] params) throws Exception { printUsage(HELP_PAGE_GENERAL); return; } else if (params[0].toLowerCase().startsWith("--version")) { - System.out.println(Main.class.toString() + " version " + VERSION_NAME + " (GetApkInfo - " + VERSION_CODE + ")\n"); + System.out.println(Main.class.getName() + " version " + VERSION_NAME + " (GetApkInfo - " + VERSION_CODE + ")\n"); printUsage(VERSION_PAGE_GENERAL); return; } else if(params[0].toLowerCase().endsWith(".apk")){ diff --git a/README.md b/README.md index fa5c3c0..5e5d48b 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,16 @@ Eclipse:[https://github.com/bihe0832/Android-GetAPKInfo/tree/eclipse](https:// ├── AXMLPrinter2_zixie.jar :对于官方工具AXMLPrinter2的优化,解决因为不同api下apk的AndroidMainfest编码引起的问题 │ ├── AXMLPrinter2_zixie :AXMLPrinter2_zixie.jar的源码 + │ │ + ├── CheckAndroidSignature.jar :基于官方签名相关的工具apksigner的源码改造的安卓签名校验工具 │ - ├── CheckAndroidV2SignatureByAPKSig :CheckAndroidV2SignatureByAPKSig.jar的源码 + ├── CheckAndroidSignatureByAPKSig :CheckAndroidSignature.jar的源码 │ - ├── CheckAndroidV2SignatureByAPKSig.jar :基于官方签名相关的工具apksigner的源码改造的安卓签名校验工具 + ├── GetAPKInfo.jar :一款基于Java环境的读取apk的包名、版本号、签名、是否使用V2签名,V2签名校验是否通过的工具 │ - ├── CheckAndroidV2Signature :CheckAndroidV2Signature.jar的源码 + ├── GetApkInfo : GetAPKInfo.jar的源码 │ - ├── GetApkInfo.jar :一款基于Java环境的读取apk的包名、版本号、签名、是否使用V2签名,V2签名校验是否通过的工具 - │ - ├── GetApkInfo : getPackageInfo.jar的源码 - │ - ├── apksig : Android SDK Build Tools中关于签名相关的工具apksigner的源码(提供了V2签名以及校验的方法) + ├── apksig : Android SDK Build Tools中关于签名相关的工具apksigner的源码(提供了V2、V3签名以及校验的方法) │ └── README.md @@ -45,17 +43,12 @@ Eclipse:[https://github.com/bihe0832/Android-GetAPKInfo/tree/eclipse](https:// 非可执行jar,主要是对官方工具AXMLPrinter2针对不同api下AndroidMainfest编码不同导致解析异常的优化,解决[https://github.com/bihe0832/Android-GetAPKInfo/issues/1](https://github.com/bihe0832/Android-GetAPKInfo/issues/1) 和 [https://github.com/bihe0832/Android-GetAPKInfo/issues/5](https://github.com/bihe0832/Android-GetAPKInfo/issues/5)遇到的问题 -### CheckAndroidV2Signature.jar - - ➜ java -jar ./CheckAndroidV2Signature.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk - {"ret":0,"msg":"ok","isV2":false,"isV2OK":false} +### CheckAndroidSignature.jar -### CheckAndroidV2Signature.jar - - ➜ java -jar ./CheckAndroidV2Signature.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk - {"ret":0,"msg":"","isV1OK":true,"isV2":false,"isV2OK":false,"keystoreMd5":"252e3ded833125ed3e3bb010bc24f4dc"} + ➜ java -jar ./CheckAndroidSignature.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk + {"ret":0,"msg":"","isV1OK":false,"isV2":true,"isV2OK":true,"isV3":true,"isV3OK":true,"keystoreMd5":"80fa5a8552e418f6bd805c65bcddf4c8"} -### getPackageInfo.jar +### GetAPKInfo.jar ➜ java -jar ./getPackageInfo.jar ./YSDK_Android_1.3.1_629-debug-ysdktest-inner.apk @@ -68,11 +61,13 @@ Eclipse:[https://github.com/bihe0832/Android-GetAPKInfo/tree/eclipse](https:// V1签名验证通过: false 使用V2签名: false V2签名验证通过: false - 签名验证失败原因: ERROR: JAR signer CERT.RSA: JAR signature META-INF/CERT.SF indicates the APK is signed using APK Signature Scheme v2 but no such signature was found. Signature stripped? - -### getMorePackageInfo.jar + 使用V3签名: false + V3签名验证通过: false + 签名验证详细信息: {"ret":0,"msg":"","isV1OK":false,"isV2":true,"isV2OK":true,"isV3":true,"isV3OK":true,"keystoreMd5":"80fa5a8552e418f6bd805c65bcddf4c8"} + +### GetMoreAPKInfo.jar -➜ java -jar ./getMorePackageInfo.jar ./test.apk +➜ java -jar ./GetMorePackageInfo.jar ./test.apk 执行结果: 成功 应用信息: @@ -86,6 +81,9 @@ Eclipse:[https://github.com/bihe0832/Android-GetAPKInfo/tree/eclipse](https:// V1签名验证通过: true 使用V2签名: true V2签名验证通过: true + 使用V3签名: false + V3签名验证通过: false + 签名验证详细信息: {"ret":0,"msg":"","isV1OK":false,"isV2":true,"isV2OK":true,"isV3":true,"isV3OK":true,"keystoreMd5":"80fa5a8552e418f6bd805c65bcddf4c8"} 使用权限列表: android.permission.INTERNET android.permission.VIBRATE