首页
社区
课程
招聘
[原创]Key Attestation原理理解
发表于: 2024-8-31 02:00 3090

[原创]Key Attestation原理理解

2024-8-31 02:00
3090

结合github项目KeyAttestation来学习KeyAttestation原理



页面展示来自于attestationResult这个回调结果

从代码流程中可以分为三步

doAttestation的入参有三个

获取方式是

是从PackageManager中获取系统属性,这三个属性指的是什么意思呢?这里需要引入Android KeyStore的演变历史

KeyStore是借助系统芯片 (SoC) 中提供的可信执行环境,由硬件支持的密钥库

在 Android 6.0 之前的版本中,Android 已有一个非常简单的由硬件支持的加密服务 API(由 0.2 和 0.3 版的 Keymaster 硬件抽象层 (HAL) 提供)。该密钥库能够提供数字签名和验证操作,以及不对称签名密钥对的生成和导入操作。该 API 在许多设备上都已实现,但有许多安全目标无法只通过一个签名 API 来轻松达成。Android 6.0 中的密钥库在该密钥库 API 的基础上进行了扩展,能够提供更广泛的功能

在 Android 6.0 中,密钥库不仅增加了对称加密基元(AES 和 HMAC),还增加了针对由硬件支持的密钥的访问权限控制系统。访问权限控制在密钥生成期间指定,并会在密钥的整个生命周期内被强制执行。可以将密钥限定为仅在用户通过身份验证后才可使用,并且只能用于指定的目的或只有在具有指定的加密参数时才可使用。如需了解详情,请参阅授权标记和函数页面。

在 Android 7.0 中,Keymaster 2 增加了对密钥认证和版本绑定的支持。密钥认证提供公钥证书,这些证书中包含密钥及其访问权限控制的详细描述,以使密钥存在于安全硬件中并使其配置可以远程验证。

在 Android 8.0 中,Keymaster 3 从旧式 C 结构硬件抽象层 (HAL) 转换到根据新硬件接口定义语言 (HIDL) 中的定义生成的 C++ HAL 接口。在此变更过程中,很多参数类型发生了变化,但这些类型和方法与旧的类型和 HAL 结构体方法一一对应。如需了解详情,请参阅函数页面

除了此接口修订之外,Android 8.0 还扩展了 Keymaster 2 的认证功能,以支持 ID 认证。 ID 认证提供了一种受限且可选的机制来严格认证硬件标识符,例如设备序列号、产品名称和手机 ID (IMEI/MEID)。为了实现此新增功能,Android 8.0 更改了 ASN.1 认证架构,添加了 ID 认证。Keymaster 实现需要通过某种安全方式来检索相关的数据项,还需要定义一种安全永久地停用该功能的机制。

Android 9 纳入了以下更新:
更新到 Keymaster 4
对嵌入式安全元件的支持
对安全密钥导入的支持
对 3DES 加密的支持
更改了版本绑定,以便 boot.img 和 system.img 分别设置版本以允许独立更新

从KeyStore的版本演变上看,在迭代过程中逐步加入了新的认证方式,而FEATURE_STRONGBOX_KEYSTORE、FEATURE_KEYSTORE_APP_ATTEST_KEY就是判断设备是否支持某种认证方式(原因是因为OEM厂商不一定会紧跟着Google的架构演变方案)

这里区分了生成key的类型,如果设备开启了App Attest Key特性的话生成的密钥可以用来做密钥认证(Key Attestation),否则就是正常的数字签名密钥
这里优先根据是否开启了App Attest Key特性及KeyStore中是否包含attestKeyAlias的密钥来进行密钥生成

此时,KeyStore中包含了名称为alias的密钥对

在上一步生成key之后根据alias获取到对应的证书链,证书链是一个认证过程,最终指向可信的根证书,因此获取到的证书链实际的形式是

终端证书->中间证书->根证书

遍历证书列表,进行三次校验

在checkStatus函数中存在状态流转的过程,涉及到两个函数verify、checkValidity,主要目的是为了确保一个证书的签名是有效的、且被信任的上级证书所签发

这一步是为了确定根证书的颁发机构,其中获取OEM Key的方式如下

根据代码逻辑的理解,是根据证书颁发机构来选择各自的认证方式

EatAttestation使用实体认证令牌 (Entity Attestation Token, EAT) 进行设备认证。EAT是一种基于IETF标准的轻量级认证格式,场景更多的是存在嵌入到IoT设备中

三星电子提供的一种设备认证和安全机制,核心逻辑还是走的Asn1

重点关注下Asn1的认证方式,也是官方选择的认证方式,参考文档security-key-attestation

根据传入的x509Cert解析成各个字段,这里可以参考ASN.1架构

checkAttestation的核心是

核心是要证明teeEnforced的purpose字段,根据上面那段注释我理解是将密钥用途分为了签名和认证

这里引入了认证的源头:根证书的可信度问题
Andorid通过Trusty TEE完成证书的存储,TEE在硬件层面解决了安全性问题,其中类似RootOfTrust这类数据都是由厂商在设备出产时烧录到硬件存储当中的,从根本上解决了根密钥不可信的问题,并以此根密钥为信任链根,派生密钥

可以利用KeyAttestation来做什么呢?

// app/src/main/java/io/github/vvb2060/keyattestation/home/HomeViewModel.kt
 
val useStrongBox = hasStrongBox && preferStrongBox
val includeProps = hasDeviceIds && preferIncludeProps
val useAttestKey = hasAttestKey && preferAttestKey
val result = try {
    val attestationResult = doAttestation(useStrongBox, includeProps, useAttestKey)
    Resource.success(attestationResult)
} catch (e: Throwable) {
    val cause = if (e is AttestationException) e.cause else e
    Log.w(AppApplication.TAG, "Do attestation error.", cause)
 
    when (e) {
        is AttestationException -> Resource.error(e, null)
        else -> Resource.error(AttestationException(CODE_UNKNOWN, e), null)
    }
}
// app/src/main/java/io/github/vvb2060/keyattestation/home/HomeViewModel.kt
 
val useStrongBox = hasStrongBox && preferStrongBox
val includeProps = hasDeviceIds && preferIncludeProps
val useAttestKey = hasAttestKey && preferAttestKey
val result = try {
    val attestationResult = doAttestation(useStrongBox, includeProps, useAttestKey)
    Resource.success(attestationResult)
} catch (e: Throwable) {
    val cause = if (e is AttestationException) e.cause else e
    Log.w(AppApplication.TAG, "Do attestation error.", cause)
 
    when (e) {
        is AttestationException -> Resource.error(e, null)
        else -> Resource.error(AttestationException(CODE_UNKNOWN, e), null)
    }
}
// app/src/main/java/io/github/vvb2060/keyattestation/home/HomeViewModel.kt
 
@Throws(AttestationException::class)
private fun doAttestation(useStrongBox: Boolean,
                            includeProps: Boolean,
                            useAttestKey: Boolean): AttestationResult {
    val certs = ArrayList<Certificate>()
    val alias = if (useStrongBox) "${AppApplication.TAG}_strongbox" else AppApplication.TAG
    val attestKeyAlias = if (useAttestKey) "${alias}_persistent" else null
    try {
        // 1. generateKey
        if (useAttestKey && !keyStore.containsAlias(attestKeyAlias)) {
            generateKey(attestKeyAlias!!, useStrongBox, includeProps, attestKeyAlias)
        }
        generateKey(alias, useStrongBox, includeProps, attestKeyAlias)
 
        // 2. certs collect
        val certChain = keyStore.getCertificateChain(alias)
                ?: throw CertificateException("Unable to get certificate chain")
        for (cert in certChain) {
            val buf = ByteArrayInputStream(cert.encoded)
            certs.add(certificateFactory.generateCertificate(buf))
        }
        if (useAttestKey) {
            val persistChain = keyStore.getCertificateChain(attestKeyAlias)
                    ?: throw CertificateException("Unable to get certificate chain")
            for (cert in persistChain) {
                val buf = ByteArrayInputStream(cert.encoded)
                certs.add(certificateFactory.generateCertificate(buf))
            }
        }
    } catch (e: ProviderException) {
        // 异常流程,可忽略
        ......
    } catch (e: Exception) {
        throw AttestationException(CODE_UNKNOWN, e)
    }
    @Suppress("UNCHECKED_CAST")
    // 3. parseCertificateChain
    currentCerts = certs as List<X509Certificate>
    return parseCertificateChain(certs)
}
// app/src/main/java/io/github/vvb2060/keyattestation/home/HomeViewModel.kt
 
@Throws(AttestationException::class)
private fun doAttestation(useStrongBox: Boolean,
                            includeProps: Boolean,
                            useAttestKey: Boolean): AttestationResult {
    val certs = ArrayList<Certificate>()
    val alias = if (useStrongBox) "${AppApplication.TAG}_strongbox" else AppApplication.TAG
    val attestKeyAlias = if (useAttestKey) "${alias}_persistent" else null
    try {
        // 1. generateKey
        if (useAttestKey && !keyStore.containsAlias(attestKeyAlias)) {
            generateKey(attestKeyAlias!!, useStrongBox, includeProps, attestKeyAlias)
        }
        generateKey(alias, useStrongBox, includeProps, attestKeyAlias)
 
        // 2. certs collect
        val certChain = keyStore.getCertificateChain(alias)
                ?: throw CertificateException("Unable to get certificate chain")
        for (cert in certChain) {
            val buf = ByteArrayInputStream(cert.encoded)
            certs.add(certificateFactory.generateCertificate(buf))
        }
        if (useAttestKey) {
            val persistChain = keyStore.getCertificateChain(attestKeyAlias)
                    ?: throw CertificateException("Unable to get certificate chain")
            for (cert in persistChain) {
                val buf = ByteArrayInputStream(cert.encoded)
                certs.add(certificateFactory.generateCertificate(buf))
            }
        }
    } catch (e: ProviderException) {
        // 异常流程,可忽略
        ......
    } catch (e: Exception) {
        throw AttestationException(CODE_UNKNOWN, e)
    }
    @Suppress("UNCHECKED_CAST")
    // 3. parseCertificateChain
    currentCerts = certs as List<X509Certificate>
    return parseCertificateChain(certs)
}
// android.hardware.strongbox_keystore
useStrongBox = pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
// android.hardware.keystore.app_attest_key
hasAttestKey = pm.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY)
hasDeviceIds = pm.hasSystemFeature("android.software.device_id_attestation")
// android.hardware.strongbox_keystore
useStrongBox = pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
// android.hardware.keystore.app_attest_key
hasAttestKey = pm.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY)
hasDeviceIds = pm.hasSystemFeature("android.software.device_id_attestation")
if (useAttestKey && !keyStore.containsAlias(attestKeyAlias)) {
    generateKey(attestKeyAlias!!, useStrongBox, includeProps, attestKeyAlias)
}
generateKey(alias, useStrongBox, includeProps, attestKeyAlias)
if (useAttestKey && !keyStore.containsAlias(attestKeyAlias)) {
    generateKey(attestKeyAlias!!, useStrongBox, includeProps, attestKeyAlias)
}
generateKey(alias, useStrongBox, includeProps, attestKeyAlias)
private fun generateKey(alias: String,
                        useStrongBox: Boolean,
                        includeProps: Boolean,
                        attestKeyAlias: String?) {
    val now = Date()
    val attestKey = alias == attestKeyAlias
    // 密钥用途判定
    val purposes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && attestKey) {
        KeyProperties.PURPOSE_ATTEST_KEY
    } else {
        KeyProperties.PURPOSE_SIGN
    }
    // 设置 KeyGenParameterSpec
    val builder = KeyGenParameterSpec.Builder(alias, purposes)
            .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
            .setDigests(KeyProperties.DIGEST_SHA256)
            .setCertificateNotBefore(now)
            .setAttestationChallenge(now.toString().toByteArray())
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && useStrongBox) {
        builder.setIsStrongBoxBacked(true)
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        if (includeProps) {
            builder.setDevicePropertiesAttestationIncluded(true)
        }
        if (attestKey) {
            builder.setCertificateSubject(X500Principal("CN=App Attest Key"))
        } else {
            builder.setAttestKeyAlias(attestKeyAlias)
        }
    }
    // 获取 KeyPairGenerator 实例
    val keyPairGenerator = KeyPairGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
    // 用 KeyGenParameterSpec 初始化 KeyPairGenerator
    keyPairGenerator.initialize(builder.build())
    // 生成密钥对
    keyPairGenerator.generateKeyPair()
}
private fun generateKey(alias: String,
                        useStrongBox: Boolean,
                        includeProps: Boolean,
                        attestKeyAlias: String?) {
    val now = Date()
    val attestKey = alias == attestKeyAlias
    // 密钥用途判定
    val purposes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && attestKey) {
        KeyProperties.PURPOSE_ATTEST_KEY
    } else {
        KeyProperties.PURPOSE_SIGN
    }
    // 设置 KeyGenParameterSpec
    val builder = KeyGenParameterSpec.Builder(alias, purposes)
            .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
            .setDigests(KeyProperties.DIGEST_SHA256)
            .setCertificateNotBefore(now)
            .setAttestationChallenge(now.toString().toByteArray())
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && useStrongBox) {
        builder.setIsStrongBoxBacked(true)
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        if (includeProps) {
            builder.setDevicePropertiesAttestationIncluded(true)
        }
        if (attestKey) {
            builder.setCertificateSubject(X500Principal("CN=App Attest Key"))
        } else {
            builder.setAttestKeyAlias(attestKeyAlias)
        }
    }
    // 获取 KeyPairGenerator 实例
    val keyPairGenerator = KeyPairGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
    // 用 KeyGenParameterSpec 初始化 KeyPairGenerator
    keyPairGenerator.initialize(builder.build())
    // 生成密钥对
    keyPairGenerator.generateKeyPair()
}
val certChain = keyStore.getCertificateChain(alias)
        ?: throw CertificateException("Unable to get certificate chain")
for (cert in certChain) {
    val buf = ByteArrayInputStream(cert.encoded)
    certs.add(certificateFactory.generateCertificate(buf))
}
if (useAttestKey) {
    val persistChain = keyStore.getCertificateChain(attestKeyAlias)
            ?: throw CertificateException("Unable to get certificate chain")
    for (cert in persistChain) {
        val buf = ByteArrayInputStream(cert.encoded)
        certs.add(certificateFactory.generateCertificate(buf))
    }
}
val certChain = keyStore.getCertificateChain(alias)
        ?: throw CertificateException("Unable to get certificate chain")
for (cert in certChain) {
    val buf = ByteArrayInputStream(cert.encoded)
    certs.add(certificateFactory.generateCertificate(buf))
}
if (useAttestKey) {
    val persistChain = keyStore.getCertificateChain(attestKeyAlias)
            ?: throw CertificateException("Unable to get certificate chain")
    for (cert in persistChain) {
        val buf = ByteArrayInputStream(cert.encoded)
        certs.add(certificateFactory.generateCertificate(buf))
    }
}
// app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java
 
public static AttestationResult parseCertificateChain(List<X509Certificate> certs) {
    var infoList = new ArrayList<CertificateInfo>();
 
    // 在certs中最后一个指向的是终端证书,逐步向上遍历
    var parent = certs.get(certs.size() - 1);
    for (int i = certs.size() - 1; i >= 0; i--) {
        var parentKey = parent.getPublicKey();
        var info = new CertificateInfo(certs.get(i));
        infoList.add(info);
        info.checkStatus(parentKey);
        if (parent == info.cert) {
            info.checkIssuer();
        } else {
            parent = info.cert;
        }
        if (info.checkAttestation()) {
            break;
        }
    }
 
return AttestationResult.form(infoList);
// app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java
 
public static AttestationResult parseCertificateChain(List<X509Certificate> certs) {
    var infoList = new ArrayList<CertificateInfo>();
 
    // 在certs中最后一个指向的是终端证书,逐步向上遍历
    var parent = certs.get(certs.size() - 1);
    for (int i = certs.size() - 1; i >= 0; i--) {
        var parentKey = parent.getPublicKey();
        var info = new CertificateInfo(certs.get(i));
        infoList.add(info);
        info.checkStatus(parentKey);
        if (parent == info.cert) {
            info.checkIssuer();
        } else {
            parent = info.cert;
        }
        if (info.checkAttestation()) {
            break;
        }
    }
 
return AttestationResult.form(infoList);
// app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java
 
private void checkStatus(PublicKey parentKey) {
    try {
        status = CERT_SIGN;
        cert.verify(parentKey);
        status = CERT_REVOKED;
        var certStatus = RevocationList.get(cert.getSerialNumber());
        if (certStatus != null) {
            throw new CertificateException("Certificate revocation " + certStatus);
        }
        status = CERT_EXPIRED;
        cert.checkValidity();
        status = CERT_NORMAL;
    } catch (GeneralSecurityException e) {
        securityException = e;
    }
}
// app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java
 
private void checkStatus(PublicKey parentKey) {
    try {
        status = CERT_SIGN;
        cert.verify(parentKey);
        status = CERT_REVOKED;
        var certStatus = RevocationList.get(cert.getSerialNumber());
        if (certStatus != null) {
            throw new CertificateException("Certificate revocation " + certStatus);
        }
        status = CERT_EXPIRED;
        cert.checkValidity();
        status = CERT_NORMAL;
    } catch (GeneralSecurityException e) {
        securityException = e;
    }
}
// app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java
 
private void checkIssuer() {
    var publicKey = cert.getPublicKey().getEncoded();
    if (Arrays.equals(publicKey, googleKey)) {
        issuer = KEY_GOOGLE;
    } else if (Arrays.equals(publicKey, aospEcKey)) {
        issuer = KEY_AOSP;
    } else if (Arrays.equals(publicKey, aospRsaKey)) {
        issuer = KEY_AOSP;
    } else if (Arrays.equals(publicKey, knoxSakv2Key)) {
        issuer = KEY_KNOX;
    } else if (oemKeys != null) {
        for (var key : oemKeys) {
            if (Arrays.equals(publicKey, key.getEncoded())) {
                issuer = KEY_OEM;
                break;
            }
        }
    }
}
// app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java
 
private void checkIssuer() {
    var publicKey = cert.getPublicKey().getEncoded();
    if (Arrays.equals(publicKey, googleKey)) {
        issuer = KEY_GOOGLE;
    } else if (Arrays.equals(publicKey, aospEcKey)) {
        issuer = KEY_AOSP;
    } else if (Arrays.equals(publicKey, aospRsaKey)) {
        issuer = KEY_AOSP;
    } else if (Arrays.equals(publicKey, knoxSakv2Key)) {
        issuer = KEY_KNOX;
    } else if (oemKeys != null) {
        for (var key : oemKeys) {
            if (Arrays.equals(publicKey, key.getEncoded())) {
                issuer = KEY_OEM;
                break;
            }
        }
    }
}
private static Set<PublicKey> getOemPublicKey() {
    var resName = "android:array/vendor_required_attestation_certificates";
    var res = AppApplication.app.getResources();
    // noinspection DiscouragedApi
    var id = res.getIdentifier(resName, null, null);
    if (id == 0) {
        return null;
    }
    var set = new HashSet<PublicKey>();
    try {
        var cf = CertificateFactory.getInstance("X.509");
        for (var s : res.getStringArray(id)) {
            var cert = s.replaceAll("\\s+", "\n")
                    .replaceAll("-BEGIN\\nCERTIFICATE-", "-BEGIN CERTIFICATE-")
                    .replaceAll("-END\\nCERTIFICATE-", "-END CERTIFICATE-");
            var input = new ByteArrayInputStream(cert.getBytes());
            var publicKey = cf.generateCertificate(input).getPublicKey();
            set.add(publicKey);
        }
    } catch (CertificateException e) {
        Log.e(AppApplication.TAG, "getOemKeys: ", e);
        return null;
    }
    set.removeIf(key -> Arrays.equals(key.getEncoded(), googleKey));
    if (set.isEmpty()) {
        return null;
    }
    set.forEach(key -> Log.i(AppApplication.TAG, "getOemKeys: " + key));
    return set;
}
private static Set<PublicKey> getOemPublicKey() {
    var resName = "android:array/vendor_required_attestation_certificates";
    var res = AppApplication.app.getResources();
    // noinspection DiscouragedApi
    var id = res.getIdentifier(resName, null, null);
    if (id == 0) {
        return null;
    }
    var set = new HashSet<PublicKey>();
    try {
        var cf = CertificateFactory.getInstance("X.509");
        for (var s : res.getStringArray(id)) {
            var cert = s.replaceAll("\\s+", "\n")
                    .replaceAll("-BEGIN\\nCERTIFICATE-", "-BEGIN CERTIFICATE-")

[招生]系统0day安全班,企业级设备固件漏洞挖掘,Linux平台漏洞挖掘!

最后于 2024-8-31 09:33 被tcc0lin编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (2)
雪    币: 47
活跃值: (437)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
很清晰
2024-9-2 11:45
0
雪    币: 2925
活跃值: (5510)
能力值: ( LV11,RANK:185 )
在线值:
发帖
回帖
粉丝
3
感谢
2024-9-2 14:08
0
游客
登录 | 注册 方可回帖
返回