APK 签名方案 v3

Android 9 支持APK 密钥轮换,这使应用程序能够在 APK 更新过程中更改其签名密钥。为了使轮换切实可行,APK 必须指示新旧签名密钥之间的信任级别。为了支持密钥轮换,我们将APK 签名方案从 v2 更新为 v3,以允许使用新旧密钥。 V3 将有关支持的 SDK 版本的信息和旋转证明结构添加到 APK 签名块中。

APK签名块

为了保持与 v1 APK 格式的向后兼容性,v2 和 v3 APK 签名存储在紧邻 ZIP 中央目录之前的 APK 签名块内。

v3 APK 签名块格式与 v2 相同。 APK 的 v3 签名存储为 ID 值对,ID 为 0xf05368c0。

APK签名方案v3块

v3 方案的设计与v2 方案非常相似。它具有相同的通用格式,并支持相同的签名算法 ID 、密钥大小和 EC 曲线。

然而,v3方案添加了有关支持的SDK版本和proof-of-rotation结构的信息。

格式

APK 签名方案 v3 块存储在 ID 为0xf05368c0的 APK 签名块内。

APK签名方案v3块的格式遵循v2的格式:

  • 长度前缀signer的长度前缀序列:
    • 长度前缀的signed data
      • 长度前缀digests的长度前缀序列:
        • signature algorithm ID (4字节)
        • digest (长度前缀)
      • X.509 certificates的长度前缀序列:
        • 带长度前缀的 X.509 certificate (ASN.1 DER 形式)
      • minSDK (uint32) - 如果平台版本低于此数字,则应忽略此签名者。
      • maxSDK (uint32) - 如果平台版本高于此数字,则应忽略此签名者。
      • 长度前缀附加additional attributes的长度前缀序列:
        • ID (uint32)
        • value (可变长度:附加属性的长度 - 4 个字节)
        • ID - 0x3ba06f8c
        • value -旋转证明结构
    • minSDK (uint32) - 签名数据部分中 minSDK 值的重复 - 如果当前平台不在范围内,则用于跳过此签名的验证。必须匹配带符号的数据值。
    • maxSDK (uint32) - 签名数据部分中 maxSDK 值的重复 - 如果当前平台不在范围内,则用于跳过此签名的验证。必须匹配带符号的数据值。
    • 长度前缀signatures的长度前缀序列:
      • signature algorithm ID (uint32)
      • signed data进行长度前缀signature
    • 长度前缀的public key (SubjectPublicKeyInfo,ASN.1 DER 形式)

轮换证明和自信任的旧证书结构

轮换证明结构允许应用程序轮换其签名证书,而不会被与其通信的其他应用程序阻止。为了实现这一点,应用程序签名包含两条新数据:

  • 第三方断言应用程序的签名证书在其前身受信任的任何地方都可以信任
  • 应用程序本身仍然信任的旧签名证书

签名数据部分中的proof-of-rotation 属性由一个单链表组成,每个节点都包含一个用于对应用程序的早期版本进行签名的签名证书。该属性旨在包含概念性的轮换证明和自信任的旧证书数据结构。该列表按版本排序,最旧的签名证书对应于根节点。旋转证明数据结构是通过让每个节点中的证书签署列表中的下一个节点来构建的,从而为每个新密钥注入证据,表明它应该与旧密钥一样受信任。

self-trusted-old-certs 数据结构是通过向每个节点添加标志来构建的,指示其在集合中的成员身份和属性。例如,可能存在一个标志,指示给定节点处的签名证书对于获取 Android 签名权限是可信的。此标志允许由旧证书签名的其他应用程序仍被授予由使用新签名证书签名的应用程序定义的签名权限。由于整个旋转证明属性驻留在 v3 signer字段的签名数据部分中,因此它受到用于对包含的 apk 进行签名的密钥的保护。

这种格式排除了多个签名密钥以及不同祖先签名证书聚合为一个(多个起始节点到一个公共接收器)。

格式

旋转证明存储在 APK 签名方案 v3 块内,ID 为0x3ba06f8c 。其格式为:

  • 长度前缀levels的长度前缀序列:
    • 长度前缀signed data (通过先前的证书 - 如果存在)
      • 带长度前缀的 X.509 certificate (ASN.1 DER 形式)
      • signature algorithm ID (uint32) - 上一级证书使用的算法
    • flags (uint32) - 指示此证书是否应位于 self-trusted-old-certs 结构中以及用于哪些操作的标志。
    • signature algorithm ID (uint32) - 必须与下一级中签名数据部分的 ID 匹配。
    • 对上述signed data进行长度前缀signature

多项证书

Android 目前将使用多个证书签名的 APK 视为具有独立于组成证书的唯一签名身份。因此,签名数据部分中的旋转证明属性形成一个有向非循环图,最好将其视为单链表,给定版本的每组签名者代表一个节点。这增加了旋转证明结构的额外复杂性(下面的多签名者版本)。特别是,订购成为一个问题。更重要的是,不再可能独立签署 APK,因为轮换证明结构必须让旧的签名证书签署新的一组证书,而不是逐一签署它们。例如,由密钥 A 签名的 APK 希望由两个新密钥 B 和 C 签名,则 B 签名者不能只包含 A 或 B 的签名,因为这是与 B 和 C 不同的签名身份。这将意味着签名者必须在建立这样的结构之前进行协调。

多签名者旋转证明属性

  • 长度前缀集合的长度前缀sets
    • signed data (按之前的设置 - 如果存在)
      • certificates的长度前缀序列
        • 带长度前缀的 X.509 certificate (ASN.1 DER 形式)
      • signature algorithm IDs序列 (uint32) - 对应前一组中的每个证书,顺序相同。
    • flags (uint32) - 指示这组证书是否应位于 self-trusted-old-certs 结构中以及用于哪些操作的标志。
    • 长度前缀signatures的长度前缀序列:
      • signature algorithm ID (uint32) - 必须与签名数据部分中的算法 ID 匹配
      • 对上述signed data进行长度前缀signature

旋转证明结构中的多个祖先

v3 方案也不处理同一应用程序的两个不同密钥轮换为同一签名密钥。这与收购的情况不同,收购公司希望将收购的应用程序转移到使用其签名密钥来共享权限。此次收购被视为受支持的用例,因为新应用程序将通过其包名称进行区分,并且可以包含自己的旋转证明结构。不受支持的情况是,同一应用程序有两个不同的路径来获取同一证书,这打破了密钥轮换设计中的许多假设。

确认

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

APK签名验证流程

图1. APK签名验证流程

APK签名方案v3验证

  1. 找到 APK 签名块并验证:
    1. APK 签名块的两个大小字段包含相同的值。
    2. ZIP 中央目录后面紧跟着 ZIP 中央目录结束记录。
    3. ZIP 中央目录末尾后面没有更多数据。
  2. 找到 APK 签名块内的第一个 APK 签名方案 v3 块。如果存在 v3 块,请继续执行步骤 3。否则,请回退到使用 v2 方案验证 APK。
  3. 对于 APK 签名方案 v3 块中的每个signer ,其最小和最大 SDK 版本都在当前平台的范围内:
    1. signatures中选择支持的最强signature algorithm ID 。强度排序取决于每个实现/平台版本。
    2. 使用public key根据signatures signed data验证相应的signature 。 (现在可以安全地解析signed data 。)
    3. 验证签名数据中的最小和最大 SDK 版本是否与为signer指定的版本匹配。
    4. 验证digestssignatures中的签名算法 ID 的有序列表是否相同。 (这是为了防止签名剥离/添加。)
    5. 使用与签名算法使用的摘要算法相同的摘要算法来计算 APK 内容的摘要
    6. 验证计算出的摘要与digests中相应的digest相同。
    7. 验证certificate中第一个certificates的SubjectPublicKeyInfo 是否与public key相同。
    8. 如果signer存在旋转证明属性,请验证该结构是否有效并且该signer是列表中的最后一个证书。
  4. 如果在当前平台的范围内恰好找到一个signer并且步骤 3 对于该signer成功,则验证成功。

验证

要测试您的设备是否正确支持 v3,请运行cts/hostsidetests/appsecurity/src/android/appsecurity/cts/中的PkgInstallSignatureVerificationTest.java CTS 测试。