Android 9支持APK密钥轮替,这使得应用程序能够在APK更新过程中更改其签名密钥。为了实现这一功能,需要对APK签名方案进行升级,从v2版本切换到v3版本,以便在新的旧密钥之间进行切换。v3版本在APK签名分块中添加了关于受支持的SDK版本信息和proof-of-rotation结构体。

APK签名分块

为了保持与v1 APK格式的向后兼容性,v2和v3版本的APK签名都存储在APK签名分块内。这些数据紧邻ZIP中央目录且位于该目录之前。v3版本的APK签名分块格式与v2版本相同。一个典型的v3签名会被表示为“ID-值”对,其中ID为0xf05368c0。

APK签名方案v3分块

v3方案的设计思路与v2方案非常类似,两者都采用了相同的常规格式,并支持相同的签名算法ID、密钥大小以及EC曲线。然而,v3方案增加了一些关于受支持的SDK版本和proof-of-rotation结构体的信息。

格式

在v3方案中,APK签名分块沿用

签名数据部分中的 proof-of-rotation 属性包含一个单链表,其中每个节点都包含用于为之前版本的应用签名的签名证书。此属性旨在包含概念性 proof-of-rotation 和 self-trusted-old-certs 数据结构。该单链表按版本排序,最旧的签名证书对应于根节点。在构建 proof-of-rotation 数据结构时,系统会让每个节点中的证书为列表中的下一个证书签名,从而为每个新密钥提供证据来证明它应该与旧密钥一样可信。

signer

此格式排除了多个签名密钥的情况和将不同祖先签名证书收敛到一个证书的情况(多个起始节点指向一个通用接收器)。

格式

0x3ba06f8c

levelssigned datacertificatesignature algorithm IDflagssignature algorithm IDsigned datasignature

多个证书

Android 目前将使用多个证书签名的 APK 视为具有与所含证书不同的签名身份。因此,签名数据部分中的 proof-of-rotation 属性构成了一个有向无环图,最好将其视为单链表,其中给定版本的每组签名者都表示一个节点。这为 proof-of-rotation 结构(下面的多签名者版本)带来了额外的复杂性。排序成为一个特别突出的问题。更重要的是,无法再单独为 APK 签名,因为 proof-of-rotation 结构体必须让旧签名证书为新的证书集签名,而不是逐个签名。例如,如果希望由两个新密钥 B 和 C 签名的 APK 是由密钥 A 签名的,则它不能让 B 签名者仅包含 A 或 B 的签名,因为这是与 B 和 C 不同的签名身份。这意味着签名者必须在构建此类结构之前进行协调。

多个签名者 proof-of-rotation 属性

setssigned datacertificatescertificatesignature algorithm IDs flags signaturessignature algorithm IDsigned datasignature

proof-of-rotation 结构体中有多个祖先

3 方案无法处理两个不同密钥轮替到同一个应用的同一签名密钥的情况。这与收购情形有所不同,在收购情形中,收购公司希望转移收购的应用,以使用其签名密钥来共享权限。收购被视为受支持的用例,因为新应用将通过其软件包名称来区分,并且可以包含自己的 proof-of-rotation 结构体。不受支持的用例是,同一应用有两个不同的路径指向相同的证书,这打破了在密钥轮替设计中做出的许多假设。

在 Android 9 及更高版本中,可以根据 APK 签名方案 v3、v2 或 v1 验证 APK。较旧的平台会忽略 v3 签名而尝试验证 v2 签名,然后尝试验证 v1 签名。

图 1. APK 签名验证过程

APK 签名方案 v3 验证

signersignaturessignature algorithm IDpublic keysigned datasignaturessignaturesigned datasignerdigestssignaturesdigestsdigestcertificatescertificatepublic keysignersignersignersigner

验证:cts/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java