From 19aafad21e69ec49f348c9fa49c1ffbd5e1f32bf Mon Sep 17 00:00:00 2001 From: Ylarod Date: Tue, 30 Sep 2025 23:07:09 +0800 Subject: [PATCH 1/5] feat: warn for testkey verifiedBootKey --- .../attestation/RootPublicKey.java | 74 +++++++++++++++++++ .../keyattestation/home/HomeAdapter.kt | 50 +++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 5 ++ app/src/main/res/values/strings.xml | 5 ++ 4 files changed, 134 insertions(+) diff --git a/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java b/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java index 3620dd31..94436fce 100644 --- a/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java +++ b/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java @@ -14,6 +14,12 @@ import io.github.vvb2060.keyattestation.AppApplication; public class RootPublicKey { + public enum TestKey { + NONE, + RSA2048, + RSA4096, + RSA8192, + } public enum Status { NULL, FAILED, @@ -106,6 +112,74 @@ private static Set getOemPublicKey() { return set; } + private static byte[] sha256(byte[] input) { + try { + var md = MessageDigest.getInstance("SHA-256"); + return md.digest(input); + } catch (NoSuchAlgorithmException e) { + // Should never happen on Android + throw new RuntimeException(e); + } + } + + private static byte[] hexToBytes(String hex) { + int len = hex.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + + Character.digit(hex.charAt(i + 1), 16)); + } + return data; + } + + /** + * Detect if verifiedBootKey matches known AVB test public keys by SHA-256 digest. + */ + public static TestKey checkVbmetaTestKeyBySha256(byte[] verifiedBootKey) { + if (verifiedBootKey == null) return TestKey.NONE; + + // Known SHA-256 fingerprints for AVB test keys + final String SHA256_RSA2048 = "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c"; + final String SHA256_RSA4096 = "7728e30f50bfa5cea165f473175a08803f6a8346642b5aa10913e9d9e6defef6"; + final String SHA256_RSA8192 = "e15e2365469ce672a91d02cc8d9c2f29b787481e574d3b56ac774153d7ced614"; + + var sha256_2048 = hexToBytes(SHA256_RSA2048); + var sha256_4096 = hexToBytes(SHA256_RSA4096); + var sha256_8192 = hexToBytes(SHA256_RSA8192); + + if (Arrays.equals(verifiedBootKey, sha256_2048)) return TestKey.RSA2048; + if (Arrays.equals(verifiedBootKey, sha256_4096)) return TestKey.RSA4096; + if (Arrays.equals(verifiedBootKey, sha256_8192)) return TestKey.RSA8192; + + return TestKey.NONE; + } + + /** + * Check if the provided value is the SHA-256 digest of a known root public key. + * This is useful for fields like verifiedBootKey which store a hash, not the raw key. + */ + public static Status checkDigest(byte[] digest) { + if (Arrays.equals(digest, sha256(googleKey))) { + return Status.GOOGLE; + } else if (Arrays.equals(digest, sha256(aospEcKey)) + || Arrays.equals(digest, sha256(aospRsa2048Key)) + || Arrays.equals(digest, sha256(aospRsa4096Key)) + || Arrays.equals(digest, sha256(aospRsa8192Key))) { + return Status.AOSP; + } else if (Arrays.equals(digest, sha256(knoxSakv2Key)) + || Arrays.equals(digest, sha256(knoxSakv1Key)) + || Arrays.equals(digest, sha256(knoxSakmv1Key))) { + return Status.KNOX; + } else if (oemKeys != null) { + for (var key : oemKeys) { + if (Arrays.equals(digest, sha256(key.getEncoded()))) { + return Status.OEM; + } + } + } + return Status.UNKNOWN; + } + public static Status check(byte[] publicKey) { if (Arrays.equals(publicKey, googleKey)) { return Status.GOOGLE; diff --git a/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt b/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt index e3aea98f..0c70ba21 100644 --- a/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt +++ b/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt @@ -105,6 +105,54 @@ class HomeAdapter(listener: Listener) : IdBasedRecyclerViewAdapter() { private fun updateData(attestationData: AttestationData) { addItemAt(1, BootStateViewHolder.CREATOR, attestationData, ID_BOOT_STATUS) + // If verifiedBootKey matches known AVB test keys (by SHA-1), show a warning header + var testKeyShown = false + attestationData.rootOfTrust?.verifiedBootKey?.let { vbk -> + when (RootPublicKey.checkVbmetaTestKeyBySha256(vbk)) { + RootPublicKey.TestKey.RSA2048 -> { + addItemAt(2, HeaderViewHolder.CREATOR, HeaderData( + R.string.vbmeta_testkey, + R.string.vbmeta_testkey_rsa2048_summary, + R.drawable.ic_error_outline_24, + rikka.material.R.attr.colorAlert + ), ID_VB_TESTKEY_WARNING) + testKeyShown = true + } + RootPublicKey.TestKey.RSA4096 -> { + addItemAt(2, HeaderViewHolder.CREATOR, HeaderData( + R.string.vbmeta_testkey, + R.string.vbmeta_testkey_rsa4096_summary, + R.drawable.ic_error_outline_24, + rikka.material.R.attr.colorAlert + ), ID_VB_TESTKEY_WARNING) + testKeyShown = true + } + RootPublicKey.TestKey.RSA8192 -> { + addItemAt(2, HeaderViewHolder.CREATOR, HeaderData( + R.string.vbmeta_testkey, + R.string.vbmeta_testkey_rsa8192_summary, + R.drawable.ic_error_outline_24, + rikka.material.R.attr.colorAlert + ), ID_VB_TESTKEY_WARNING) + testKeyShown = true + } + else -> {} + } + } + + // If verifiedBootKey hash corresponds to AOSP, show a warning header + attestationData.rootOfTrust?.verifiedBootKey?.let { vbk -> + val status = RootPublicKey.checkDigest(vbk) + if (status == RootPublicKey.Status.AOSP) { + addItemAt(if (testKeyShown) 3 else 2, HeaderViewHolder.CREATOR, HeaderData( + R.string.aosp_root_cert, + R.string.aosp_root_cert_summary, + R.drawable.ic_error_outline_24, + rikka.material.R.attr.colorWarning + ), ID_VB_AOSP_WARNING) + } + } + var id = ID_DESCRIPTION_START val attestation = attestationData.showAttestation ?: return addItem(CommonItemViewHolder.SECURITY_LEVEL_CREATOR, SecurityLevelData( @@ -270,6 +318,8 @@ class HomeAdapter(listener: Listener) : IdBasedRecyclerViewAdapter() { private const val ID_ERROR = 0L private const val ID_CERT_STATUS = 1L private const val ID_BOOT_STATUS = 2L + private const val ID_VB_TESTKEY_WARNING = 4L + private const val ID_VB_AOSP_WARNING = 3L private const val ID_CERT_INFO_START = 1000L private const val ID_RKP_HOSTNAME = 2000L private const val ID_DESCRIPTION_START = 3000L diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 914a6dcb..14540265 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -40,6 +40,11 @@ 设备制造商的根证书 此设备信任该根证书,但它可能不被其它人信任。 + vbmeta 被测试密钥签名 + verifiedBootKey 是 testkey_rsa2048 + verifiedBootKey 是 testkey_rsa4096 + verifiedBootKey 是 testkey_rsa8192 + 证书链 证书链是用于验证密钥的证书列表。从该密钥的证书开始,每个证书均由链中下一个证书签名,到根证书为止。认证的可信度取决于证书链的根证书。 证书信息 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5620044a..4545c9dd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,6 +40,11 @@ OEM root certificate This device trusts this root certificate, but it may not be trusted by others. + VBMeta signed with test key + verifiedBootKey is testkey_rsa2048 + verifiedBootKey is testkey_rsa4096 + verifiedBootKey is testkey_rsa8192 + Certificate chain Certificate chain is a list of certificates used to authenticate a key. The chain starts with the certificate associated with that key, and each certificate is signed by the next in the chain. The chain ends with a root certificate, and the trustworthiness of the attestation depends on the root certificate of the chain.. Certificate info From 10d28e4c959094dcfa3dc40af08a9e724d75cc37 Mon Sep 17 00:00:00 2001 From: Ylarod Date: Tue, 30 Sep 2025 23:12:25 +0800 Subject: [PATCH 2/5] fix --- .../vvb2060/keyattestation/home/HomeAdapter.kt | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt b/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt index 0c70ba21..97206e13 100644 --- a/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt +++ b/app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt @@ -140,19 +140,6 @@ class HomeAdapter(listener: Listener) : IdBasedRecyclerViewAdapter() { } } - // If verifiedBootKey hash corresponds to AOSP, show a warning header - attestationData.rootOfTrust?.verifiedBootKey?.let { vbk -> - val status = RootPublicKey.checkDigest(vbk) - if (status == RootPublicKey.Status.AOSP) { - addItemAt(if (testKeyShown) 3 else 2, HeaderViewHolder.CREATOR, HeaderData( - R.string.aosp_root_cert, - R.string.aosp_root_cert_summary, - R.drawable.ic_error_outline_24, - rikka.material.R.attr.colorWarning - ), ID_VB_AOSP_WARNING) - } - } - var id = ID_DESCRIPTION_START val attestation = attestationData.showAttestation ?: return addItem(CommonItemViewHolder.SECURITY_LEVEL_CREATOR, SecurityLevelData( @@ -318,8 +305,7 @@ class HomeAdapter(listener: Listener) : IdBasedRecyclerViewAdapter() { private const val ID_ERROR = 0L private const val ID_CERT_STATUS = 1L private const val ID_BOOT_STATUS = 2L - private const val ID_VB_TESTKEY_WARNING = 4L - private const val ID_VB_AOSP_WARNING = 3L + private const val ID_VB_TESTKEY_WARNING = 3L private const val ID_CERT_INFO_START = 1000L private const val ID_RKP_HOSTNAME = 2000L private const val ID_DESCRIPTION_START = 3000L From d1eb05a7c1f708bd19fe46e0fea9cf591582026a Mon Sep 17 00:00:00 2001 From: Ylarod Date: Tue, 30 Sep 2025 23:14:03 +0800 Subject: [PATCH 3/5] fix2 --- .../attestation/RootPublicKey.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java b/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java index 94436fce..d4a3c3bb 100644 --- a/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java +++ b/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java @@ -154,32 +154,6 @@ public static TestKey checkVbmetaTestKeyBySha256(byte[] verifiedBootKey) { return TestKey.NONE; } - /** - * Check if the provided value is the SHA-256 digest of a known root public key. - * This is useful for fields like verifiedBootKey which store a hash, not the raw key. - */ - public static Status checkDigest(byte[] digest) { - if (Arrays.equals(digest, sha256(googleKey))) { - return Status.GOOGLE; - } else if (Arrays.equals(digest, sha256(aospEcKey)) - || Arrays.equals(digest, sha256(aospRsa2048Key)) - || Arrays.equals(digest, sha256(aospRsa4096Key)) - || Arrays.equals(digest, sha256(aospRsa8192Key))) { - return Status.AOSP; - } else if (Arrays.equals(digest, sha256(knoxSakv2Key)) - || Arrays.equals(digest, sha256(knoxSakv1Key)) - || Arrays.equals(digest, sha256(knoxSakmv1Key))) { - return Status.KNOX; - } else if (oemKeys != null) { - for (var key : oemKeys) { - if (Arrays.equals(digest, sha256(key.getEncoded()))) { - return Status.OEM; - } - } - } - return Status.UNKNOWN; - } - public static Status check(byte[] publicKey) { if (Arrays.equals(publicKey, googleKey)) { return Status.GOOGLE; From 53de06e954e07a39a948592dc1de9a335d160556 Mon Sep 17 00:00:00 2001 From: Ylarod Date: Tue, 30 Sep 2025 23:16:07 +0800 Subject: [PATCH 4/5] fix3 --- .../kotlin-compiler-14193937394583190350.salive | 0 .../keyattestation/attestation/RootPublicKey.java | 10 ---------- 2 files changed, 10 deletions(-) create mode 100644 .kotlin/sessions/kotlin-compiler-14193937394583190350.salive diff --git a/.kotlin/sessions/kotlin-compiler-14193937394583190350.salive b/.kotlin/sessions/kotlin-compiler-14193937394583190350.salive new file mode 100644 index 00000000..e69de29b diff --git a/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java b/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java index d4a3c3bb..512e5655 100644 --- a/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java +++ b/app/src/main/java/io/github/vvb2060/keyattestation/attestation/RootPublicKey.java @@ -112,16 +112,6 @@ private static Set getOemPublicKey() { return set; } - private static byte[] sha256(byte[] input) { - try { - var md = MessageDigest.getInstance("SHA-256"); - return md.digest(input); - } catch (NoSuchAlgorithmException e) { - // Should never happen on Android - throw new RuntimeException(e); - } - } - private static byte[] hexToBytes(String hex) { int len = hex.length(); byte[] data = new byte[len / 2]; From 3a85538fddf1c315ab4da2e0efb39c40b97753e0 Mon Sep 17 00:00:00 2001 From: Ylarod Date: Tue, 30 Sep 2025 23:16:22 +0800 Subject: [PATCH 5/5] fix4 --- .kotlin/sessions/kotlin-compiler-14193937394583190350.salive | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .kotlin/sessions/kotlin-compiler-14193937394583190350.salive diff --git a/.kotlin/sessions/kotlin-compiler-14193937394583190350.salive b/.kotlin/sessions/kotlin-compiler-14193937394583190350.salive deleted file mode 100644 index e69de29b..00000000