【Android安全-KeyAttestation原理理解】此文章归类为:Android安全。
结合github项目KeyAttestation来学习KeyAttestation原理
页面展示来自于attestationResult这个回调结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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 ) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | // 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) } |
从代码流程中可以分为三步
doAttestation的入参有三个
获取方式是
1 2 3 4 5 | // 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" ) |
是从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的架构演变方案)
1 2 3 4 | if (useAttestKey && !keyStore.containsAlias(attestKeyAlias)) { generateKey(attestKeyAlias!!, useStrongBox, includeProps, attestKeyAlias) } generateKey(alias, useStrongBox, includeProps, attestKeyAlias) |
这里区分了生成key的类型,如果设备开启了App Attest Key特性的话生成的密钥可以用来做密钥认证(Key Attestation),否则就是正常的数字签名密钥
这里优先根据是否开启了App Attest Key特性及KeyStore中是否包含attestKeyAlias的密钥来进行密钥生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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() } |
此时,KeyStore中包含了名称为alias的密钥对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)) } } |
在上一步生成key之后根据alias获取到对应的证书链,证书链是一个认证过程,最终指向可信的根证书,因此获取到的证书链实际的形式是
终端证书->中间证书->根证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 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); |
遍历证书列表,进行三次校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 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; } } |
在checkStatus函数中存在状态流转的过程,涉及到两个函数verify、checkValidity,主要目的是为了确保一个证书的签名是有效的、且被信任的上级证书所签发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 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 ; } } } } |
这一步是为了确定根证书的颁发机构,其中获取OEM Key的方式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java private boolean checkAttestation() { boolean terminate; try { attestation = Attestation.loadFromCertificate(cert); // If key purpose included KeyPurpose::SIGN, // then it could be used to sign arbitrary data, including any tbsCertificate, // and so an attestation produced by the key would have no security properties. // If the parent certificate can attest that the key purpose is only KeyPurpose::ATTEST_KEY, // then the child certificate can be trusted. var purposes = attestation.getTeeEnforced().getPurposes(); terminate = purposes == null || !purposes.contains(AuthorizationList.KM_PURPOSE_ATTEST_KEY); } catch (CertificateParsingException e) { certException = e; terminate = false ; checkProvisioningInfo(); } return terminate; } // app/src/main/java/io/github/vvb2060/keyattestation/attestation/Attestation.java public static Attestation loadFromCertificate(X509Certificate x509Cert) throws CertificateParsingException { if (x509Cert.getExtensionValue(EAT_OID) == null && x509Cert.getExtensionValue(ASN1_OID) == null ) { throw new CertificateParsingException( "No attestation extensions found" ); } if (x509Cert.getExtensionValue(EAT_OID) != null ) { if (x509Cert.getExtensionValue(ASN1_OID) != null ) { throw new CertificateParsingException( "Multiple attestation extensions found" ); } try { return new EatAttestation(x509Cert); } catch (CborException cbe) { throw new CertificateParsingException( "Unable to parse EAT extension" , cbe); } } if (x509Cert.getExtensionValue(CRL_DP_OID) != null ) { Log.w(AppApplication.TAG, "CRL Distribution Points extension found in leaf certificate." ); } if (x509Cert.getExtensionValue(KNOX_OID) != null ) { return new KnoxAttestation(x509Cert); } return new Asn1Attestation(x509Cert); } |
根据代码逻辑的理解,是根据证书颁发机构来选择各自的认证方式
EatAttestation使用实体认证令牌 (Entity Attestation Token, EAT) 进行设备认证。EAT是一种基于IETF标准的轻量级认证格式,场景更多的是存在嵌入到IoT设备中
三星电子提供的一种设备认证和安全机制,核心逻辑还是走的Asn1
重点关注下Asn1的认证方式,也是官方选择的认证方式,参考文档security-key-attestation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // app/src/main/java/io/github/vvb2060/keyattestation/attestation/Asn1Attestation.java public Asn1Attestation(X509Certificate x509Cert) throws CertificateParsingException { super (x509Cert); ASN1Sequence seq = getAttestationSequence(x509Cert); attestationVersion = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX)); attestationSecurityLevel = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)); keymasterVersion = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX)); keymasterSecurityLevel = Asn1Utils.getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)); attestationChallenge = Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(ATTESTATION_CHALLENGE_INDEX)); uniqueId = Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(UNIQUE_ID_INDEX)); softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX)); teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX)); } |
根据传入的x509Cert解析成各个字段,这里可以参考ASN.1架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | KeyDescription :: = SEQUENCE { attestationVersion INTEGER, # KM2 value is 1. KM3 value is 2. KM4 value is 3. attestationSecurityLevel SecurityLevel, keymasterVersion INTEGER, keymasterSecurityLevel SecurityLevel, attestationChallenge OCTET_STRING, uniqueId OCTET_STRING, softwareEnforced AuthorizationList, teeEnforced AuthorizationList, } SecurityLevel :: = ENUMERATED { Software ( 0 ), TrustedEnvironment ( 1 ), StrongBox ( 2 ), } AuthorizationList :: = SEQUENCE { purpose [ 1 ] EXPLICIT SET OF INTEGER OPTIONAL, algorithm [ 2 ] EXPLICIT INTEGER OPTIONAL, keySize [ 3 ] EXPLICIT INTEGER OPTIONAL. digest [ 5 ] EXPLICIT SET OF INTEGER OPTIONAL, padding [ 6 ] EXPLICIT SET OF INTEGER OPTIONAL, ecCurve [ 10 ] EXPLICIT INTEGER OPTIONAL, rsaPublicExponent [ 200 ] EXPLICIT INTEGER OPTIONAL, rollbackResistance [ 303 ] EXPLICIT NULL OPTIONAL, # KM4 activeDateTime [ 400 ] EXPLICIT INTEGER OPTIONAL originationExpireDateTime [ 401 ] EXPLICIT INTEGER OPTIONAL usageExpireDateTime [ 402 ] EXPLICIT INTEGER OPTIONAL noAuthRequired [ 503 ] EXPLICIT NULL OPTIONAL, userAuthType [ 504 ] EXPLICIT INTEGER OPTIONAL, authTimeout [ 505 ] EXPLICIT INTEGER OPTIONAL, allowWhileOnBody [ 506 ] EXPLICIT NULL OPTIONAL, trustedUserPresenceRequired [ 507 ] EXPLICIT NULL OPTIONAL, # KM4 trustedConfirmationRequired [ 508 ] EXPLICIT NULL OPTIONAL, # KM4 unlockedDeviceRequired [ 509 ] EXPLICIT NULL OPTIONAL, # KM4 allApplications [ 600 ] EXPLICIT NULL OPTIONAL, applicationId [ 601 ] EXPLICIT OCTET_STRING OPTIONAL, creationDateTime [ 701 ] EXPLICIT INTEGER OPTIONAL, origin [ 702 ] EXPLICIT INTEGER OPTIONAL, rollbackResistant [ 703 ] EXPLICIT NULL OPTIONAL, # KM2 and KM3 only. rootOfTrust [ 704 ] EXPLICIT RootOfTrust OPTIONAL, osVersion [ 705 ] EXPLICIT INTEGER OPTIONAL, osPatchLevel [ 706 ] EXPLICIT INTEGER OPTIONAL, attestationApplicationId [ 709 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdBrand [ 710 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdDevice [ 711 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdProduct [ 712 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdSerial [ 713 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdImei [ 714 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdMeid [ 715 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdManufacturer [ 716 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 attestationIdModel [ 717 ] EXPLICIT OCTET_STRING OPTIONAL, # KM3 vendorPatchLevel [ 718 ] EXPLICIT INTEGER OPTIONAL, # KM4 bootPatchLevel [ 719 ] EXPLICIT INTEGER OPTIONAL, # KM4 } RootOfTrust :: = SEQUENCE { verifiedBootKey OCTET_STRING, deviceLocked BOOLEAN, verifiedBootState VerifiedBootState, verifiedBootHash OCTET_STRING, # KM4 } VerifiedBootState :: = ENUMERATED { Verified ( 0 ), SelfSigned ( 1 ), Unverified ( 2 ), Failed ( 3 ), } |
checkAttestation的核心是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // app/src/main/java/io/github/vvb2060/keyattestation/attestation/CertificateInfo.java // If key purpose included KeyPurpose::SIGN, // then it could be used to sign arbitrary data, including any tbsCertificate, // and so an attestation produced by the key would have no security properties. // If the parent certificate can attest that the key purpose is only KeyPurpose::ATTEST_KEY, // then the child certificate can be trusted. var purposes = attestation.getTeeEnforced().getPurposes(); terminate = purposes == null || !purposes.contains(AuthorizationList.KM_PURPOSE_ATTEST_KEY); // app/src/main/java/io/github/vvb2060/keyattestation/attestation/AuthorizationList.java public AuthorizationList(ASN1Encodable asn1Encodable) throws CertificateParsingException { if (!(asn1Encodable instanceof ASN1Sequence sequence)) { throw new CertificateParsingException( "Expected sequence for authorization list, found " + asn1Encodable.getClass().getName()); } for (var entry : sequence) { if (!(entry instanceof ASN1TaggedObject taggedObject)) { throw new CertificateParsingException( "Expected tagged object, found " + entry.getClass().getName()); } int tag = taggedObject.getTagNo(); var value = taggedObject.getBaseObject().toASN1Primitive(); switch (tag) { default : throw new CertificateParsingException( "Unknown tag " + tag + " found" ); case KM_TAG_PURPOSE & KEYMASTER_TAG_TYPE_MASK: purposes = Asn1Utils.getIntegersFromAsn1Set(value); break ; } } } |
核心是要证明teeEnforced的purpose字段,根据上面那段注释我理解是将密钥用途分为了签名和认证
可以利用KeyAttestation来做什么呢?
更多【Android安全-KeyAttestation原理理解】相关视频教程:www.yxfzedu.com