Android 9 支持APK 密鑰輪換,這使應用能夠在 APK 更新中更改其簽名密鑰。為了使輪換切實可行,APK 必須指明新舊簽名密鑰之間的信任級別。為了支持密鑰輪換,我們將APK 簽名方案從 v2 更新到 v3,以允許使用新舊密鑰。 V3 向 APK 簽名塊添加了有關支持的 SDK 版本和旋轉證明結構的信息。
APK 簽名塊
為了保持與 v1 APK 格式的向後兼容性,v2 和 v3 APK 簽名存儲在 APK 簽名塊中,位於 ZIP 中央目錄之前。
v3 APK 簽名塊格式與 v2 相同。 APK 的 v3 簽名以 ID 值對的形式存儲,ID 為 0xf05368c0。
APK 簽名方案 v3 塊
v3 方案的設計與v2 方案非常相似。它具有相同的通用格式,並支持相同的簽名算法 ID 、密鑰大小和 EC 曲線。
但是,v3 方案添加了有關支持的 SDK 版本和旋轉證明結構的信息。
格式
APK 簽名方案 v3 塊存儲在 APK 簽名塊中,ID 0xf05368c0
。
APK 簽名方案 v3 塊的格式遵循 v2 的格式:
- 長度前綴
signer
的長度前綴序列:- 以長度為前綴的有
signed data
:- 長度前綴
digests
的長度前綴序列:-
signature algorithm ID
(4字節) -
digest
(長度前綴)
-
- X.509
certificates
的長度前綴序列:- 以長度為前綴的 X.509
certificate
(ASN.1 DER 形式)
- 以長度為前綴的 X.509
-
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 形式)
- 以長度為前綴的有
旋轉證明和自信任舊證書結構
輪換證明結構允許應用輪換其簽名證書,而不會被與其通信的其他應用程序阻止。為此,應用簽名包含兩條新數據:
- 向第三方斷言應用程序的簽名證書可以在其前身受信任的任何地方被信任
- 應用程序本身仍然信任的應用程序的舊簽名證書
簽名數據部分中的旋轉證明屬性由一個單鍊錶組成,每個節點都包含一個簽名證書,用於對應用程序的先前版本進行簽名。此屬性旨在包含概念性的旋轉證明和自信任舊證書數據結構。該列表按版本排序,其中最早的簽名證書對應於根節點。旋轉證明數據結構是通過讓每個節點中的證書籤署列表中的下一個證書來構建的,從而為每個新密鑰注入證據,證明它應該與舊密鑰一樣受信任。
self-trusted-old-certs 數據結構是通過向每個節點添加標誌來構建的,這些標誌指示其在集合中的成員資格和屬性。例如,可能存在一個標誌,指示給定節點上的簽名證書是可信的,以獲得 Android 簽名權限。此標誌允許由舊證書籤名的其他應用程序仍被授予由使用新簽名證書籤名的應用程序定義的簽名權限。因為整個循環證明屬性駐留在 v3 signer
字段的已簽名數據部分中,所以它受到用於簽署包含 apk 的密鑰的保護。
這種格式排除了多個簽名密鑰和不同祖先簽名證書聚合為一個(多個起始節點到一個公共接收器)。
格式
旋轉證明存儲在 ID 0x3ba06f8c
下的 APK 簽名方案 v3 塊中。它的格式是:
- 長度前綴
levels
的長度前綴序列:- 以長度為前綴的
signed data
(由先前的證書 - 如果存在)- 以長度為前綴的 X.509
certificate
(ASN.1 DER 形式) -
signature algorithm ID
(uint32) - 證書在上一級使用的算法
- 以長度為前綴的 X.509
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 形式)
- 以長度為前綴的 X.509
-
signature algorithm IDs
序列 (uint32) - 一個用於前一組中的每個證書,順序相同。
- 以長度為前綴的
-
flags
(uint32) - 指示這組證書是否應該在 self-trusted-old-certs 結構中以及針對哪些操作的標誌。 - 長度前綴
signatures
的長度前綴序列:-
signature algorithm ID
(uint32) - 必須與簽名數據部分中的匹配 - 上述
signed data
的長度前綴signature
-
-
旋轉證明結構中的多個祖先
v3 方案也不處理兩個不同的密鑰輪換到同一個應用程序的同一個簽名密鑰。這與收購的情況不同,收購公司希望移動被收購的應用程序以使用其簽名密鑰來共享權限。此次收購被視為受支持的用例,因為新應用程序將通過其包名稱來區分,並且可能包含其自己的旋轉證明結構。不支持的情況,即同一個應用程序有兩條不同的路徑來獲得相同的證書,打破了密鑰輪換設計中的許多假設。
確認
在 Android 9 及更高版本中,可以根據 APK 簽名方案 v3、v2 方案或 v1 方案驗證 APK。舊平台忽略 v3 簽名並嘗試驗證 v2 簽名,然後是 v1。
APK 簽名方案 v3 驗證
- 找到 APK 簽名塊並驗證:
- APK 簽名塊的兩個大小字段包含相同的值。
- ZIP Central Directory 後緊跟 ZIP End of Central Directory 記錄。
- 中央目錄的 ZIP 結尾後面沒有更多數據。
- 在 APK 簽名塊內找到第一個 APK 簽名方案 v3 塊。如果存在 v3 塊,請繼續執行步驟 3。否則,回退到使用 v2 方案驗證 APK。
- 對於具有當前平台範圍內的最小和最大 SDK 版本的 APK 簽名方案 v3 塊中的每個
signer
:- 從
signatures
中選擇支持的最強signature algorithm ID
。強度排序取決於每個實現/平台版本。 - 使用
public key
驗證signatures
signed data
中的相應signature
。 (現在解析signed data
是安全的。) - 驗證簽名數據中的最小和最大 SDK 版本是否與為
signer
指定的版本匹配。 - 驗證
digests
和signatures
中籤名算法 ID 的有序列表是否相同。 (這是為了防止簽名剝離/添加。) - 使用與簽名算法使用的摘要算法相同的摘要算法計算 APK 內容的摘要。
- 驗證計算出的摘要與來自
digests
的相應digest
相同。 - 驗證第一個
certificate
的certificates
是否與public key
相同。 - 如果
signer
的旋轉證明屬性存在,則驗證該結構是有效的,並且此signer
是列表中的最後一個證書。
- 從
- 如果在當前平台的範圍內恰好找到一個
signer
,並且該signer
的步驟 3 成功,則驗證成功。
驗證
要測試您的設備是否正確支持 v3,請在cts/hostsidetests/appsecurity/src/android/appsecurity/cts/
中運行PkgInstallSignatureVerificationTest.java
CTS 測試。