Android P v3簽名新特性

新版v3簽名在v2的基礎上,仍然採用檢查整個壓縮包的校驗方式。不同的是在簽名部分增可以添加新的證書,即可以不用修改ApplicationID來完成證書的更新迭代。

本文引用自 https://xuanxuanblingbling.github.io/ctf/android/2018/12/30/signature/

概述

在這裏插入圖片描述
簽名機制主要有兩種用途:

  • 使用特殊的key簽名可以獲取到一些不同的權限
  • 驗證數據保證不被篡改,防止應用被惡意的第三方覆蓋

這裏我們主要討論第二個用途,即驗證數據是否是可信的。應用程序的作者使用自己的私鑰簽名APK文件,並將簽名與公鑰一起發佈到APK中,這個過程稱之爲簽名。當應用程序被安裝時,用發佈的公鑰去解析簽名,並與文件的hash進行比對,這個過程叫驗籤。
顯然這裏我們嘗試修改被簽名數據的任何一部分都會導致驗籤失敗,但是我們並不能防止重新簽名。於是就存在一個問題:如何相信一個應用是正版應用?AOSP原生中並沒有這種校驗機制,如果是第一次安裝,則默認相信自簽名的應用。
但是當我們更新應用時,android根據應用的ApplicationID(一般與包名相同)來判斷是否是同一個應用,並且要驗證原來的應用與更新應用的證書是否匹配。但是在v1v2的簽名版本中一個應用只允許用一個證書來校驗,這時如果軟件開發者想要更新證書並且完成軟件的更新,是沒有辦法的,只能換用新的ApplicationID重新安裝。
所以在v3新版本簽名中加入了證書的旋轉校驗,即可以在一次的升級安裝中使用新的證書,新的私鑰來簽名APK。當然這個新的證書是需要老證書來保證的,類似一個證書鏈。

簽名塊結構

在v1版本的簽名中,簽名以文件的形式存在於apk包中,這個版本的apk包就是一個標準的zip包。但是在v2版本的簽名中,簽名信息被塞到了apk文件本身中,這時apk已經不符合一個標準的zip壓縮包的文件結構。v3版本簽名中延續了v2的簽名方式,仍然是將簽名信息放到壓縮包本身的結構中。但是在v3中添加了一種更新證書的方式,這部分更新證書的數據同樣被放在了簽名信息中。所以爲了理解簽名數據的具體結構,我們先了解正常ZIP結構

ZIP

這裏我用010editor打開自己壓得一個內容flag.txt的壓縮包,簡單的說一下ZIP文件格式由一下三部分組成:

  • 文件數據區(灰,白)
  • 中央目錄結構(粉)
  • 中央目錄結束標誌(黃)
    在這裏插入圖片描述
    真正的數據內容就是那段白色的數據,zip解析時,通過結束標誌中找到中央目錄的偏移,然後找到中心目錄,然後從中心目錄的每一條數據裏找到文件數據的偏移,最後讀取文件數據。可以簡單的理解這種數據解析方式爲:倒着往上找。
    參考:https://blog.csdn.net/a200710716/article/details/51644421

APK

使用v1簽名的APK就是標準的ZIP結構,但使用v2v3簽名的APK,文件本身已經不符合ZIP結構了,具體的變化就是:在文件數據區與中央目錄結構之間插入了簽名數據塊,關於簽名的各種數據,以及v3簽名新添加的更新證書的數據,都保存在這個數據塊中。
在這裏插入圖片描述
可看到010editor的ZIP解析模板已經無法識別通過v2v3版本簽名工具生成的APK文件。

結構分析

在這裏插入圖片描述
v2版本簽名塊(APK Signing Block)本身又主要分成三部分:

  • SignerData(簽名者數據):主要包括簽名者的證書,整個APK完整性校驗hash,以及一些必要信息
  • Signature(簽名):開發者對SignerData部分數據的簽名數據
  • PublicKey(公鑰):用於驗籤的公鑰數據

v3版本簽名塊也分成同樣的三部分,與v2不同的是在SignerData部分,v3新增了attr塊,其中是由更小的level塊組成。每個level塊中可以存儲一個證書信息。前一個level塊證書驗證下一個level證書,以此類推。最後一個level塊的證書,要符合SignerData中本身的證書,即用來簽名整個APK的公鑰所屬於的證書。兩個版本的簽名塊結構如下:
在這裏插入圖片描述

驗證簽名

所謂驗證簽名,就是檢查APK中的簽名結構是否符合一定的要求,這裏的簽名實際上是APK的整體簽名。而在簽名塊中,存在很多項數據需要驗證,比如APK的摘要信息,證書信息,SDK版本信息等,這些都是APK的簽名數據。所以在整個簽名的驗證中,以上信息是全部都要驗證的。不過在v3版本中添加的新特性是針對驗證證書信息的修訂,所以接下來也是重點分析驗籤中的證書驗證的部分。

驗證簽名流程

因爲簽名的驗證就是發生在一個apk包的安裝過程中,所以爲了更清楚驗證簽名的時機,有必要了解整個安裝的分類與大致流程。Android安裝應用主要有如下四種方式:

  • 系統應用安裝:開機時完成,沒有安裝界面
  • 網絡下載的應用安裝:通過市場應用完成,沒有安裝界面
  • ADB工具安裝:沒有安裝界面
  • 第三方應用安裝:通過packageinstall.apk應用安裝,有安裝界面

但是其實無論通過哪種方式安裝都要通過PackageManagerService來完成安裝的主要工作,最終在PMS中會去驗證簽名信息,流程如下
在這裏插入圖片描述
安裝過程中如果發現有v3簽名塊,則必須使用v3簽名的驗證機制,不能繞過。否則才使用v2簽名的驗證機制,以此類推。

驗證完整性

數據完整性校驗v3與v2版本相同,原理如下:
在這裏插入圖片描述
簽名塊包括對apk第一部分,第二部分,第三部分的二進制內容做加密保護,摘要算法以及簽名算法。簽名塊本身不做加密,這裏需要特殊注意的是由於第三部分包含了對第二部分的引用偏移,因此如果簽名塊做了改變,比如在簽名過程中增加一種簽名算法,或者增加簽名者等信息就會導致這個引用偏移發生改變,因此在算摘要的時候需要剔除這個因素要以第三部分對簽名塊的偏移來做計算。

驗證證書

v2

v2版本簽名驗證證書步驟:

  • 利用PublicKey解密Signature,得到SignerData的hash明文
  • 計算SignerData的hash值
  • 兩個值進行比較,如果相同則認爲APK沒有被修改過,解析出SignerData中的證書。否則安裝失敗
  • 如果是第一次安裝,直接將證書保存在應用信息中
  • 如果是更新安裝,即設備中原來存在這個應用,驗證之前的證書是否與本次解析的證書相同。若相同,則安裝成功,否則失敗
    在這裏插入圖片描述

v3

v3版本簽名驗證證書步驟:(前三步同v2)

  • 利用PublicKey解密Signature,得到SignerData的hash明文
  • 計算SignerData的hash值
  • 兩個值進行比較,如果相同則認爲APK沒有被修改過,解析出SignerData中的證書。否則安裝失敗
  • 逐個解析出level塊證書並驗證,並保存爲這個應用的歷史證書
  • 如果是第一次安裝,直接將證書與歷史證書一併保存在應用信息中
  • 如果是更新安裝,驗證之前的證書與歷史證書,是否與本次解析的證書或者歷史證書中存在相同的證書,其中任意一個證書符合即可安裝
    在這裏插入圖片描述

新特性場景舉例

其實就是當開發者需要更換證書時,即可直接用新證書新的私鑰進行簽名。不過爲了讓老應用相信新的證書,則需要用老證書來保證。舉個例子,有兩個level塊:level 1與level 2:

  • level 1放置老證書的信息
  • level 2中放置新證書的信息以及這段數據的簽名
  • level 2中的簽名是由老私鑰進行簽名的,則需要用老證書的公鑰來驗證
  • 校驗原來的證書與level 1 相同,則相信本次更新的level 2 的證書,即簽名APK的證書
  • 完成安裝並記錄新證書信息

關於v3簽名的google註釋

以下主要是在ApkSignatureSchemeV3Verifier.java文件中的有關於v3簽名的一些函數的註釋,官方原文可供參考

/**
    * Returns the certificates associated with each signer for the given APK without verification.
    * This method is dangerous and should not be used, unless the caller is absolutely certain the
    * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v3
    * Block while gathering signer information.  The APK contents are not verified.
    *
    * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
    * @throws IOException if an I/O error occurs while reading the APK file.
    */

    /**
    * Verifies APK Signature Scheme v3 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 v3.
    * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
    *         verify.
    * @throws IOException if an I/O error occurs while reading the APK file.
    */

    /**
    * Returns the APK Signature Scheme v3 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 v3.
    * @throws IOException if an I/O error occurs while reading the APK file.
    */

    /**
    * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
    * Block.
    *
    * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
    *        against the APK file.
    */
    // make sure that the last certificate in the Proof-of-rotation record matches
    // the one used to sign this APK.


    // Proof-of-rotation struct:
    // A uint32 version code followed by basically a singly linked list of nodes, called levels
    // here, each of which have the following structure:
    // * length-prefix for the entire level
    //     - length-prefixed signed data (if previous level exists)
    //         * length-prefixed X509 Certificate
    //         * uint32 signature algorithm ID describing how this signed data was signed
    //     - uint32 flags describing how to treat the cert contained in this level
    //     - uint32 signature algorithm ID to use to verify the signature of the next level. The
    //         algorithm here must match the one in the signed data section of the next level.
    //     - length-prefixed signature over the signed data in this level.  The signature here
    //         is verified using the certificate from the previous level.
    // The linking is provided by the certificate of each level signing the one of the next.

v3驗籤代碼分析

在這裏插入圖片描述

PackageManagerService.InstallPackageLI()

無論是哪種方式的安裝應用,最後都會執行到這個真正安裝函數,這個函數位於PMS,這個函數代碼比較長,這裏保留比較關鍵的代碼來說明

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
		PackageParser pp = new PackageParser();
        pp.setSeparateProcesses(mSeparateProcesses);
        pp.setDisplayMetrics(mMetrics);
        pp.setCallback(mPackageParserCallback);

        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
        final PackageParser.Package pkg;
        try {
            pkg = pp.parsePackage(tmpPackageFile, parseFlags);
            DexMetadataHelper.validatePackageDexMetadata(pkg);
        } catch (PackageParserException e) {
            res.setError("Failed parse during installPackageLI", e);
            return;
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
        try {
            // either use what we've been given or parse directly from the APK
            if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
                pkg.setSigningDetails(args.signingDetails);
            } else {
                PackageParser.collectCertificates(pkg, false /* skipVerify */);
            }
        } catch (PackageParserException e) {
            res.setError("Failed collect during installPackageLI", e);
            return;
        }
}
  • 首先實例化一個PackageParser的對象pp,利用這個對象的parsePackage()方法,返回一個位於PackageParser類中的內部類Package的實例對象pkg,其中包含着要安裝的應用的一些信息
  • 利用PackageParser的一個靜態方法collectCertificates(pkg,false)收集證書,這裏就是驗籤的入口了

PackageParser.collectCertificates()

frameworks/base/core/java/android/content/pm/PackageParser.java
public static void collectCertificates(Package pkg, boolean skipVerify)
            throws PackageParserException {
        collectCertificatesInternal(pkg, skipVerify);
        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
            Package childPkg = pkg.childPackages.get(i);
            childPkg.mSigningDetails = pkg.mSigningDetails;
        }
    }

    private static void collectCertificatesInternal(Package pkg, boolean skipVerify)
            throws PackageParserException {
        pkg.mSigningDetails = SigningDetails.UNKNOWN;

        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
        try {
            collectCertificates(pkg, new File(pkg.baseCodePath), skipVerify);

            if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
                for (int i = 0; i < pkg.splitCodePaths.length; i++) {
                    collectCertificates(pkg, new File(pkg.splitCodePaths[i]), skipVerify);
                }
            }
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }
 private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify)
            throws PackageParserException {
        final String apkPath = apkFile.getAbsolutePath();

        int minSignatureScheme = SigningDetails.SignatureSchemeVersion.JAR;
        if (pkg.applicationInfo.isStaticSharedLibrary()) {
            // must use v2 signing scheme
            minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
        }
        SigningDetails verified;
        if (skipVerify) {
            // systemDir APKs are already trusted, save time by not verifying
            verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
                        apkPath, minSignatureScheme);
        } else {
            verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
        }

        // Verify that entries are signed consistently with the first pkg
        // we encountered. Note that for splits, certificates may have
        // already been populated during an earlier parse of a base APK.
        if (pkg.mSigningDetails == SigningDetails.UNKNOWN) {
            pkg.mSigningDetails = verified;
        } else {
            if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) {
                throw new PackageParserException(
                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                        apkPath + " has mismatched certificates");
            }
        }
    }
  • collectCertificates重載到三個參數的方法,因爲跳過檢查的參數爲false,最終調用ApkSignatureVerifier類中的verify方法

ApkSignatureVerifier.verify()

frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java
public static PackageParser.SigningDetails verify(String apkPath,
            @SignatureSchemeVersion int minSignatureSchemeVersion)
            throws PackageParserException {

        if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
            // V3 and before are older than the requested minimum signing version
            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "No signature found in package of version " + minSignatureSchemeVersion
            + " or newer for package " + apkPath);
        }

        // first try v3
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
        try {
            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
                    ApkSignatureSchemeV3Verifier.verify(apkPath);
            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
            Signature[] signerSigs = convertToSignatures(signerCerts);
            Signature[] pastSignerSigs = null;
            int[] pastSignerSigsFlags = null;
            if (vSigner.por != null) {
                // populate proof-of-rotation information
                pastSignerSigs = new Signature[vSigner.por.certs.size()];
                pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
                for (int i = 0; i < pastSignerSigs.length; i++) {
                    pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
                    pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
                }
            }
            return new PackageParser.SigningDetails(
                    signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
                    pastSignerSigs, pastSignerSigsFlags);
  • 這裏會首先嚐試v3的簽名方案,然後v2v1依次嘗試,這裏只給出v3的部分,最終返回一個PackageParser類中的內部類SigningDetails的對象
  • 檢查v3版本簽名調用ApkSignatureSchemeV3Verifier類中的verify()方法

ApkSignatureSchemeV3Verifier.verify()

frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
 private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
public static VerifiedSigner verify(String apkFile)
            throws SignatureNotFoundException, SecurityException, IOException {
        return verify(apkFile, true);
    }

    /**
     * Returns the certificates associated with each signer for the given APK without verification.
     * This method is dangerous and should not be used, unless the caller is absolutely certain the
     * APK is trusted.  Specifically, verification is only done for the APK Signature Scheme v3
     * Block while gathering signer information.  The APK contents are not verified.
     *
     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
     * @throws IOException if an I/O error occurs while reading the APK file.
     */
    public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
            throws SignatureNotFoundException, SecurityException, IOException {
        return verify(apkFile, false);
    }

    private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
            throws SignatureNotFoundException, SecurityException, IOException {
        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
            return verify(apk, verifyIntegrity);
        }
    }

    /**
     * Verifies APK Signature Scheme v3 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 v3.
     * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
     *         verify.
     * @throws IOException if an I/O error occurs while reading the APK file.
     */
    private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
            throws SignatureNotFoundException, SecurityException, IOException {
        SignatureInfo signatureInfo = findSignature(apk);
        return verify(apk, signatureInfo, verifyIntegrity);
    }

    /**
     * Returns the APK Signature Scheme v3 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 v3.
     * @throws IOException if an I/O error occurs while reading the APK file.
     */
    private static SignatureInfo findSignature(RandomAccessFile apk)
            throws IOException, SignatureNotFoundException {
        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
    }

    /**
     * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
     * Block.
     *
     * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
     *        against the APK file.
     */
    private static VerifiedSigner verify(
            RandomAccessFile apk,
            SignatureInfo signatureInfo,
            boolean doVerifyIntegrity) throws SecurityException, IOException {

    }

  • 通過函數重載,最終的verify()接受三個參數:apk的二進制文件數據對象,一個SignatureInfo對象,是否檢查完整性的bool。並返回一個VerifiedSigner的對象
  • 其中傳遞的SignatureInfo對象,是由ApkSigningBlockUtils類中的findSignature()獲得
  • 可見plsCertsNoVerifyOnlyCerts()與verify()的區別是完整性校驗的bool值不同,最終調用的函數殊途同歸,這個值最終會判斷是否跳過位於ApkSigningBlockUtils類中的verifyIntegrity()方法的校驗

所以爲了繼續分析這個verify()我們先要知道SignatureInfo這個對象是什麼

ApkSigningBlockUtils.findSignature()

frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java

    static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
            throws IOException, SignatureNotFoundException {
        // Find the ZIP End of Central Directory (EoCD) record.
        Pair<ByteBuffer, Long> 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<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
                findApkSigningBlock(apk, centralDirOffset);
        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;

        // Find the APK Signature Scheme Block inside the APK Signing Block.
        ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
                blockId);

        return new SignatureInfo(
                apkSignatureSchemeBlock,
                apkSigningBlockOffset,
                centralDirOffset,
                eocdOffset,
                eocd);
    }

到這裏就已經真的開始對整個apk文件進行檢查了,通過獲得apk尾部的EOCD塊中獲得中央目錄的偏移,由中央目錄開始處往上找24個字節,獲取8個字節的小端長整型,這個值即爲簽名塊的長度減8。不過這個長度的值是從簽名塊開始到中央目錄開始,所以這裏要從中央目錄開始處往前跳轉找到簽名塊的偏移。這裏主要看到findApkSigningBlock(),findApkSignatureSchemeBlock()這兩個函數

ApkSigningBlockUtils.findApkSigningBlock()

frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java
 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;
    static Pair<ByteBuffer, Long> 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(getLengthPrefixedSlice
                    "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);
    }
  • 通過中心目錄偏移往上16字節找到apk簽名的魔術字APK Sig Block 42
  • 在往上8字節找到簽名塊的長度,拿到的長度加8字節爲整個簽名塊的長度
  • 從中心目錄往上找整個簽名塊的長度,即爲簽名塊的開始位置
  • 函數返回整個簽名塊的數據,以及簽名塊開始的偏移
    在這裏插入圖片描述
    整個簽名塊就是從apkSigBlockSizeInHeader到APK_SIG_BLOCK_MAGIC_HI的所有數據

ApkSigningBlockUtils.findApkSignatureSchemeBlock()

frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java
 static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
            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 == blockId) {
                return getByteBuffer(pairs, len - 4);
            }
            pairs.position(nextEntryPos);
        }

        throw new SignatureNotFoundException(
                "No block with ID " + blockId + " in APK Signing Block.");
    }
  • 這個函數接受之前解析出的簽名部分的數據,通過sliceFromTo剪掉前8個字節和後24個字節
  • 利用剩下的部分再次獲取八個字節的長度數據,代表着第一個簽名部分的長度,一般也只有一個
  • 繼續獲取一個四個字節的id,與blockID進行比較,這個參數來源於ApkSignatureSchemeV3Verifier.verify()函數中傳入的常量:APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0 (這個簽名塊的標記v3v2不同)
  • 最終返回的是剪掉簽名長度和簽名標記數據塊
    在這裏插入圖片描述

SignatureInfo.SignatureInfo()

frameworks/base/core/java/android/util/apk/SignatureInfo.java
class SignatureInfo {
    /** Contents of APK Signature Scheme v2 block. */
    public final ByteBuffer signatureBlock;

    /** Position of the APK Signing Block in the file. */
    public final long apkSigningBlockOffset;

    /** Position of the ZIP Central Directory in the file. */
    public final long centralDirOffset;

    /** Position of the ZIP End of Central Directory (EoCD) in the file. */
    public final long eocdOffset;

    /** Contents of ZIP End of Central Directory (EoCD) of the file. */
    public final ByteBuffer eocd;

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

最終獲取到SignatureInfo包含如下成員:

  • APK包含簽名信息的數據(signatureBlock)
  • APK簽名數據塊的偏移(apkSigningBlockOffset)
  • APK中央目錄偏移(centralDirOffset)
  • APK中央目錄結束塊偏移(eocdOffset)
  • APK中央目錄結束塊數據(eocd)

其中重要的就是signatureBlock,就是剛纔返回的apkSignatureSchemeBlock,現在可以回到ApkSignatureSchemeV3Verifier.verify()函數

ApkSignatureSchemeV3Verifier.verify()

frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
private static VerifiedSigner verify(
            RandomAccessFile apk,
            SignatureInfo signatureInfo,
            boolean doVerifyIntegrity) throws SecurityException, IOException {
        int signerCount = 0;
        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
        VerifiedSigner result = null;
        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()) {
            try {
                ByteBuffer signer = getLengthPrefixedSlice(signers);
                result = verifySigner(signer, contentDigests, certFactory);
                signerCount++;
            } catch (PlatformNotSupportedException e) {
                // this signer is for a different platform, ignore it.
                continue;
            } catch (IOException | BufferUnderflowException | SecurityException e) {
                throw new SecurityException(
                        "Failed to parse/verify signer #" + signerCount + " block",
                        e);
            }
        }

        if (signerCount < 1 || result == null) {
            throw new SecurityException("No signers found");
        }

        if (signerCount != 1) {
            throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
                    + "multiple signers found.");
        }

        if (contentDigests.isEmpty()) {
            throw new SecurityException("No content digests found");
        }

        if (doVerifyIntegrity) {
            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
        }

        if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
            byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
            result.verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
                    verityDigest, apk.length(), signatureInfo);
        }

        return result;
    }
  • 這裏首先生成了一個certFactory對象,證書標準爲X.509
  • 構造一個空的contentDigests,準備存放簽名塊中的完整性校驗信息
  • 通過getLengthPrefixedSlice()函數剪掉signatureBlock(剛纔獲得的簽名數據塊)兩個四字節的長度數據,獲得signer數據塊
    這裏我們發現當我們嘗試獲取一個大數據塊中的小數據塊時候總是有如下的代碼寫法:
	try {
            signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
        } catch (IOException e) {}

        while (signers.hasRemaining()) {
            try {
                ByteBuffer signer = getLengthPrefixedSlice(signers);
            } catch (PlatformNotSupportedException e) {}
        }

這其實是一種帶長度前綴的數據塊的構造方法,而且是爲了一個大塊下可以包含多個字塊。第一個try是通過塊前長度獲取整個塊,第二個在循環裏的try是通過每一個字塊長度獲得每一個字塊,但是這裏一般簽名塊不存在並列,所以一般早簽名塊前就會有兩個長度標記,第一個比第二個數值大4。

在這裏插入圖片描述
最終將signer部分數據傳入verifySigner()函數

ApkSignatureSchemeV3Verifier.verifySigner()

這個函數雖然比較長,但是這裏正是校驗簽名真正的實現部分,所以我們分段來分析:

frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
 private static VerifiedSigner verifySigner(
            ByteBuffer signerBlock,
            Map<Integer, byte[]> contentDigests,
            CertificateFactory certFactory)
            throws SecurityException, IOException, PlatformNotSupportedException {
        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
        int minSdkVersion = signerBlock.getInt();
        int maxSdkVersion = signerBlock.getInt();

        if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
            // this signature isn't meant to be used with this platform, skip it.
            throw new PlatformNotSupportedException(
                    "Signer not supported by this platform "
                    + "version. This platform: " + Build.VERSION.SDK_INT
                    + ", signer minSdkVersion: " + minSdkVersion
                    + ", maxSdkVersion: " + maxSdkVersion);
        }

        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);

這裏將傳入的signer部分數據繼續拆分,主要是三個部分:

  • signedData
  • signatures
  • publicKeyBytes
    具體如下:
    在這裏插入圖片描述

回到函數繼續分析:

    int signatureCount = 0;
        int bestSigAlgorithm = -1;
        byte[] bestSigAlgorithmSignatureBytes = null;
        List<Integer> 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");
            }
        }

這段就是繼續拆分signatures塊,分出四個字節的sigAlgorithm與加密過的hash值bestSigAlgorithmSignatureBytes

在這裏插入圖片描述

  String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
        Pair<String, ? extends AlgorithmParameterSpec> 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.

用最後的公鑰,中間的hash,驗證前面的SignatureData:即公鑰解密中間的hash,並計算SignatureData的hash進行比對

 byte[] contentDigest = null;
        signedData.clear();
        ByteBuffer digests = getLengthPrefixedSlice(signedData);
        List<Integer> 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<X509Certificate> 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");
        }

        int signedMinSDK = signedData.getInt();
        if (signedMinSDK != minSdkVersion) {
            throw new SecurityException(
                    "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
        }

        int signedMaxSDK = signedData.getInt();
        if (signedMaxSDK != maxSdkVersion) {
            throw new SecurityException(
                    "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
        }

        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
        return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
    }

繼續拆分塊,並在拆分過程中校驗數據是否匹配:

singer
signedDataLength (8Byte)
digestsLength (4Byte)
digestLength (4Byte)
sigAlgorithm (4Byte)
contentDigestLength (4Byte)
contentDigest (Byte[])
certificatesLength (4Byte)
encodedCertLength (4Byte)
encodedCert (byte[])
signedMinSDK (4Byte)
signedMaxSDK (4Byte)
additionalAttrsLength (4Byte)
additionalAttrs <-v3版本簽名新特性
minSdkVersion (4Byte)
maxSdkVersion (4Byte)
signaturesLength (4Byte)
signatureLength (4Byte)
sigAlgorithm (4Byte)
bestSigAlgorithmSignatureBytesLength (4Byte)
bestSigAlgorithmSignatureBytes (Byte[])
publicKeyBytesLength(8Byte)
publicKeyBytes(byte[])

這裏解析出證書,添加到certs列表裏,並做了一系列的校驗,校驗signedData中解析的數據,與其他塊中的數據是否相匹配:

  • signedData塊中的sigAlgorithm,與signatures塊中的sigAlgorithm
  • signedMinSDK與minSdkVersion
  • signedMaxSDK與maxSdkVersion
  • encodedCert解析出的公鑰與整個簽名塊尾部個公鑰
    其中拆分出的additionalAttrs就是v3版本中新添加的一個數據塊,繼續下一個函數

ApkSignatureSchemeV3Verifier.verifyAdditionalAttributes()

frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
  private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
    private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
            List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
        X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
        VerifiedProofOfRotation por = null;

        while (attrs.hasRemaining()) {
            ByteBuffer attr = getLengthPrefixedSlice(attrs);
            if (attr.remaining() < 4) {
                throw new IOException("Remaining buffer too short to contain additional attribute "
                        + "ID. Remaining: " + attr.remaining());
            }
            int id = attr.getInt();
            switch(id) {
                case PROOF_OF_ROTATION_ATTR_ID:
                    if (por != null) {
                        throw new SecurityException("Encountered multiple Proof-of-rotation records"
                                + " when verifying APK Signature Scheme v3 signature");
                    }
                    por = verifyProofOfRotationStruct(attr, certFactory);
                    // make sure that the last certificate in the Proof-of-rotation record matches
                    // the one used to sign this APK.
                    try {
                        if (por.certs.size() > 0
                                && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
                                        certChain[0].getEncoded())) {
                            throw new SecurityException("Terminal certificate in Proof-of-rotation"
                                    + " record does not match APK signing certificate");
                        }
                    } catch (CertificateEncodingException e) {
                        throw new SecurityException("Failed to encode certificate when comparing"
                                + " Proof-of-rotation record and signing certificate", e);
                    }

                    break;
                default:
                    // not the droid we're looking for, move along, move along.
                    break;
            }
        }
        return new VerifiedSigner(certChain, por);
    }

繼續拆分attrs,就是上一個函數傳入的additionalAttrs,把減去一個長度和魔術字的數據塊attr繼續丟進下一個函數verifyProofOfRotationStruct獲得por這個變量,最後返回一個VerifiedSigner

ApkSignatureSchemeV3Verifier.verifyProofOfRotationStruct()

frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
private static VerifiedProofOfRotation verifyProofOfRotationStruct(
            ByteBuffer porBuf,
            CertificateFactory certFactory)
            throws SecurityException, IOException {
        int levelCount = 0;
        int lastSigAlgorithm = -1;
        X509Certificate lastCert = null;
        List<X509Certificate> certs = new ArrayList<>();
        List<Integer> flagsList = new ArrayList<>();

        // Proof-of-rotation struct:
        // A uint32 version code followed by basically a singly linked list of nodes, called levels
        // here, each of which have the following structure:
        // * length-prefix for the entire level
        //     - length-prefixed signed data (if previous level exists)
        //         * length-prefixed X509 Certificate
        //         * uint32 signature algorithm ID describing how this signed data was signed
        //     - uint32 flags describing how to treat the cert contained in this level
        //     - uint32 signature algorithm ID to use to verify the signature of the next level. The
        //         algorithm here must match the one in the signed data section of the next level.
        //     - length-prefixed signature over the signed data in this level.  The signature here
        //         is verified using the certificate from the previous level.
        // The linking is provided by the certificate of each level signing the one of the next.

        try {

            // get the version code, but don't do anything with it: creator knew about all our flags
            porBuf.getInt();
            while (porBuf.hasRemaining()) {
                levelCount++;
                ByteBuffer level = getLengthPrefixedSlice(porBuf);
                ByteBuffer signedData = getLengthPrefixedSlice(level);
                int flags = level.getInt();
                int sigAlgorithm = level.getInt();
                byte[] signature = readLengthPrefixedByteArray(level);

                if (lastCert != null) {
                    // Use previous level cert to verify current level
                    Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
                            getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
                    PublicKey publicKey = lastCert.getPublicKey();
                    Signature sig = Signature.getInstance(sigAlgParams.first);
                    sig.initVerify(publicKey);
                    if (sigAlgParams.second != null) {
                        sig.setParameter(sigAlgParams.second);
                    }
                    sig.update(signedData);
                    if (!sig.verify(signature)) {
                        throw new SecurityException("Unable to verify signature of certificate #"
                                + levelCount + " using " + sigAlgParams.first + " when verifying"
                                + " Proof-of-rotation record");
                    }
                }

                signedData.rewind();
                byte[] encodedCert = readLengthPrefixedByteArray(signedData);
                int signedSigAlgorithm = signedData.getInt();
                if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
                    throw new SecurityException("Signing algorithm ID mismatch for certificate #"
                            + levelCount + " when verifying Proof-of-rotation record");
                }
                lastCert = (X509Certificate)
                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
                lastCert = new VerbatimX509Certificate(lastCert, encodedCert);

                lastSigAlgorithm = sigAlgorithm;
                certs.add(lastCert);
                flagsList.add(flags);
            }
        } catch (IOException | BufferUnderflowException e) {
            throw new IOException("Failed to parse Proof-of-rotation record", e);
        } catch (NoSuchAlgorithmException | InvalidKeyException
                | InvalidAlgorithmParameterException | SignatureException e) {
            throw new SecurityException(
                    "Failed to verify signature over signed data for certificate #"
                            + levelCount + " when verifying Proof-of-rotation record", e);
        } catch (CertificateException e) {
            throw new SecurityException("Failed to decode certificate #" + levelCount
                    + " when verifying Proof-of-rotation record", e);
        }
        return new VerifiedProofOfRotation(certs, flagsList);
    }

這裏的註釋已經詳細的給出了v3新特性的中Proof-of-rotation的結構說明,圖示如下:

ProofOfRotationStruct
ProofOfRotationStruct
version code (4Byte)
levelLength (4Byte) <- level 0
signedDataLength (4Byte)
encodedCertLength (4Byte)
encodedCert (Byte[])
signedSigAlgorithm (4Byte)
flags (4Byte)
sigAlgorithm (4Byte)
signatureLength (4Byte)
signature (Byte[])
levelLength (4Byte) <- level 1
signedDataLength (4Byte)
encodedCertLength (4Byte)
encodedCert (Byte[])
signedSigAlgorithm (4Byte)
flags (4Byte)
sigAlgorithm (4Byte)
signatureLength (4Byte)
signature (Byte[])
levelLength (4Byte) <- level 2
……
……
……

這裏其實就是一個證書鏈的驗證:

  • 利用level 0證書中的公鑰去驗證level 1的數據
  • 同理level 1證書中的公鑰去驗證level 2的數據
  • 若最後的證書是level 2,則保證整個簽名的證書需與level 2匹配

即如果我們想換新證書的時候,需要在por中添加最老的證書爲level 0,利用最老的證書去保證新的證書,然後在利用新的證書籤名即可。這個函數最終會返回VerifiedProofOfRotation對象,即之前將賦值給por的對象,參數爲證書列表和標記列表。

ApkSignatureSchemeV3Verifier.VerifiedProofOfRotation()

frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
 public static class VerifiedProofOfRotation {
        public final List<X509Certificate> certs;
        public final List<Integer> flagsList;

        public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
            this.certs = certs;
            this.flagsList = flagsList;
        }
    }

就是一個成員是兩個列表的類,返回到verifyAdditionalAttributes()函數,這個函數就是驗證了,por中最後的證書是否匹配我們簽名整個apk包的證書,最後返回VerifiedSigner(certChain, por),第一個參數裏就一個證書,第二個參數裏有一個證書鏈

ApkSignatureSchemeV3Verifier.VerifiedSigner()

frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
    public static class VerifiedSigner {
        public final X509Certificate[] certs;
        public final VerifiedProofOfRotation por;

        public byte[] verityRootHash;

        public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
            this.certs = certs;
            this.por = por;
        }

    }

就是把傳進來的兩個參數放入成員變量中,回到ApkSignatureSchemeV3Verifier.verify()函數中,最終的返回值賦給result這個變量裏,不過在返回result之前,因爲沒有跳過完整性檢查doVerifyIntegrity,這個值爲真所以繼續執行ApkSigningBlockUtils.verifyIntegrity()這個函數,這個完整性檢查與v2版本完全相同,就是將apk文件分段分成1M大小的數據然後hash,比較的數據已經通過verifySigner()函數解析出來了,就是contentDigests。繼續這個函數的深入分析先留白。

ApkSigningBlockUtils.verifyIntegrity()

frameworks/base/core/java/android/util/apk/ApkSigningBlockUtils.java

檢查完畢從ApkSignatureSchemeV3Verifier.verify()返回result到ApkSignatureVerifier.verify()

ApkSignatureVerifier.verify()

frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java
 try {
            ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
                    ApkSignatureSchemeV3Verifier.verify(apkPath);
            Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
            Signature[] signerSigs = convertToSignatures(signerCerts);
            Signature[] pastSignerSigs = null;
            int[] pastSignerSigsFlags = null;
            if (vSigner.por != null) {
                // populate proof-of-rotation information
                pastSignerSigs = new Signature[vSigner.por.certs.size()];
                pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
                for (int i = 0; i < pastSignerSigs.length; i++) {
                    pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
                    pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
                }
            }
            return new PackageParser.SigningDetails(
                    signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
                    pastSignerSigs, pastSignerSigsFlags);

這裏可以看出我們最後的por證書鏈信息放到了返回的SigningDetails中的pastSignerSigs, pastSignerSigsFlags與成員中。最後會把這個變量賦值給pkg.mSigningDetails,返回到PMS

PackageManagerService.InstallPackageLI()

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
 if (bp != null) {
                    // If the defining package is signed with our cert, it's okay.  This
                    // also includes the "updating the same package" case, of course.
                    // "updating same package" could also involve key-rotation.
                    final boolean sigsOk;
                    final String sourcePackageName = bp.getSourcePackageName();
                    final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
                    final KeySetManagerService ksms = mSettings.mKeySetManagerService;
                    if (sourcePackageName.equals(pkg.packageName)
                            && (ksms.shouldCheckUpgradeKeySetLocked(
                                    sourcePackageSetting, scanFlags))) {
                        sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
                    } else {

                        // in the event of signing certificate rotation, we need to see if the
                        // package's certificate has rotated from the current one, or if it is an
                        // older certificate with which the current is ok with sharing permissions
                        if (sourcePackageSetting.signatures.mSigningDetails.checkCapability(
                                        pkg.mSigningDetails,
                                        PackageParser.SigningDetails.CertCapabilities.PERMISSION)) {
                            sigsOk = true;
                        } else if (pkg.mSigningDetails.checkCapability(
                                        sourcePackageSetting.signatures.mSigningDetails,
                                        PackageParser.SigningDetails.CertCapabilities.PERMISSION)) {

                            // the scanned package checks out, has signing certificate rotation
                            // history, and is newer; bring it over
                            sourcePackageSetting.signatures.mSigningDetails = pkg.mSigningDetails;
                            sigsOk = true;
                        } else {
                            sigsOk = false;
                        }
                    }

這裏首先通過包名獲取到包的設置信息,如果不爲空則證明已經安裝過這個應用,過了一堆判斷最終進入checkCapability()函數信,新的簽名和老的簽名信息互相檢查,如果是第一次安裝則不會對證書進行進一步的檢查

PackageParser.SigningDetails.checkCapability()

frameworks/base/core/java/android/content/pm/PackageParser.java
    public boolean checkCapability(SigningDetails oldDetails, @CertCapabilities int flags) {
            if (this == UNKNOWN || oldDetails == UNKNOWN) {
                return false;
            }
            if (oldDetails.signatures.length > 1) {

                // multiple-signer packages cannot rotate signing certs, so we must have an exact
                // match, which also means all capabilities are granted
                return signaturesMatchExactly(oldDetails);
            } else {

                // we may have signing certificate rotation history, check to see if the oldDetails
                // was one of our old signing certificates, and if we grant it the capability it's
                // requesting
                return hasCertificate(oldDetails.signatures[0], flags);
            }
        }

根據註釋可以看出,如果是多簽名的則不被允許使用新特性。最終進入hasCertificate()函數

PackageParser.SigningDetails.hasCertificate()

frameworks/base/core/java/android/content/pm/PackageParser.java
 public boolean hasCertificate(Signature signature, @CertCapabilities int flags) {
            return hasCertificateInternal(signature, flags);
        }

        /** Convenient wrapper for calling {@code hasCertificate} with certificate's raw bytes. */
        public boolean hasCertificate(byte[] certificate) {
            Signature signature = new Signature(certificate);
            return hasCertificate(signature);
        }

        private boolean hasCertificateInternal(Signature signature, int flags) {
            if (this == UNKNOWN) {
                return false;
            }

            // only single-signed apps can have pastSigningCertificates
            if (hasPastSigningCertificates()) {

                // check all past certs, except for the current one, which automatically gets all
                // capabilities, since it is the same as the current signature
                for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
                    if (pastSigningCertificates[i].equals(signature)) {
                        if (flags == PAST_CERT_EXISTS
                                || (flags & pastSigningCertificatesFlags[i]) == flags) {
                            return true;
                        }
                    }
                }
            }

            // not in previous certs signing history, just check the current signer and make sure
            // we are singly-signed
            return signatures.length == 1 && signatures[0].equals(signature);
        }

即驗證現在簽名的證書是否在以前的包信息中的證書中以及por證書列表中存在過,如果存在則返回真,即認證通過。返回到PMS繼續安裝,至此爲止整個校驗簽名的流程就大致分析完畢

參考
分析 Android V2 新簽名打包機制

Android中籤名、證書、公鑰密鑰的概念及使用

一文弄懂關於證書的一切,ssl協議,android包簽名機制

Android簽名機制之—簽名驗證過程詳解

發佈了128 篇原創文章 · 獲贊 14 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章